程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> C語言 >> C++ >> C++入門知識 >> 劃分樹小結

劃分樹小結

編輯:C++入門知識

最近學習了一下劃分樹,下面總結一下。

我們在求區間最值的時候,一般可以用線段樹解決,但是如果要求區間第k小或者第k大值的話線段樹就有點力不從心了,這是我們可以用劃分樹來解決。劃分樹利用了快速排序的思想,首先是建樹,我們設當前區間的中位數為mid,(為了能快速找到區間的中位數,我們一般先對原序列做一次排序)則我們將區間中比mid小的放入左子樹,將區間中比mid大數的放入右子樹中,和mid相等的要討論一下,有些需要放到左子樹中,其他的放到右子樹中,注意我們將數字放入子樹的時候其相對順序是不變的。這樣我們一層一層下去,每次區間都減半,則空間消耗為O(nlogn)。下面看一個例子。

 


假設序列長度為9,依次為 3 5 7 3 4 9 4 2 5,怎我們看看建樹完成後是什麼樣子。

sort[ ][2  3  3  4  4  5  5  7  9]


tree[0][3  5  7  3  4  9  4  2  5]

tree[1][3  3  4  4  2][5  7  9  5]

tree[2][3  3  2][4  4][5  5][7  9]

tree[3][3  2][3][4][4][5][5][7][9]

tree[4][2][3][3][4][4][5][5][7][9]

 


好了,樹建完了,接下來就是最關鍵的查詢了,我們設函數query(p,l,r,s,t,k)表示在第p層子樹區間范圍在[l,r]的子樹中查找區間[s,t]中的第k小值。我們在每一顆子樹中設sum[i]表示區間[l,i]范圍內有多少個數字被放到了左子樹中,那麼我們容易得到,sum[t]-sum[s-1]表示在區間[s,t]有多少個樹被放入了左子樹中,我們不妨設這個值為num,若k<=num,我們就可以知道我們要找的數一定在左子樹中,否則一定在右子樹中,我們接下來只要繼續往下遍歷,當l==r的時候我們就可以確定我們要找的數,容易知道這一步驟的復雜度為O(log n),現在關鍵的一點就是如何再往下便利的時候確定ls,t的值。這個其實自己畫畫圖就很容易推出來的,下面只寫結論,這裡先設區間[l,s-1]中被放入左子樹的數有snum個,當前區間的中點為mid,

則若k<=num,我們返回query(p+1,l,mid,l+snum,l+sum[t]-1,k),(要找的數在左子樹上)


否則,我們返回query(p+1,mid+1,r,mid+1-l+s-snum,mid+1-l+t-sum[t],k-num);(要找的數在右子樹上)

下面再舉個例子,數和上面一樣

我們現在要找到區間[2,7]中的第3小數。

 

sort[ ][2  3  3  4  4  5  5  7  9]


tree[0][3  5  7  3  4  9  4  2  5]

tree[1][3  3  4  4  2][5  7  9  5]

tree[2][3  3  2][4  4][5  5][7  9]

tree[3][3  2][3][4][4][5][5][7][9]

tree[4][2][3][3][4][4][5][5][7][9]

以上橙黃色背景的數字即為我們要求的范圍。


我們首先在第一層樹上尋找,即query(0,1,9,2,7,3),我們發現在[2,7]區間中有3個數被放入了左子樹,滿足k<=num,所以我們往左子樹中找,調用query(1,1,5,2,4,3)。

在第二層樹中我們發現區間[2,4]只有一個數被放入左子樹,所以我們要找的數一定在右子樹中,調用query(2,4,5,4,5,2)。

在第三層樹中,我們發現區間[4,5]只有一個數被放入左子樹,同理我們應該往右子樹找,調用query(3,5,5,5,5,1)。現在可以發現l==r,則我們可以確定已經找到了要找的數,則返回4即可,我們可知在區間[2,7]上第3小的數為4。

基礎的劃分樹就到這裡,下面給出我的代碼:

[cpp] 
#include <iostream>  
#include <string.h>  
#include <stdio.h>  
#include <algorithm>  
#define maxn 100010  
#define mid ((l+r)>>1)  
using namespace std; 
int t[20][maxn],sum[20][maxn]; 
int a[maxn],as[maxn]; 
//以下為查找區間第k小劃分樹  
void build(int p,int l,int r) 

    int lm=0,i,ls=l,rs=mid+1;//lm表示應被放入左子樹且與中位數相等的數有多少個,ls為左子樹的起始位置,rs為右子樹的起始位置  
    for(i=mid;i>=l;i--) //求lm  
    { 
        if(as[i]==as[mid]) 
        lm++; 
        else 
        break; 
    } 
    for(i=l;i<=r;i++) 
    { 
        if(i==l)//這裡要特殊討論  
        sum[p][i]=0; 
        else 
        sum[p][i]=sum[p][i-1]; 
        if(t[p][i]==as[mid])//若與中位數相等則判斷是否應該被放入左子樹  
        { 
            if(lm) 
            { 
                lm--; 
                sum[p][i]++; 
                t[p+1][ls++]=t[p][i]; 
            } 
            else 
            t[p+1][rs++]=t[p][i]; 
        } 
        else if(t[p][i]<as[mid])//查找區間第K大即為>  
        { 
            sum[p][i]++; 
            t[p+1][ls++]=t[p][i]; 
        } 
        else 
        t[p+1][rs++]=t[p][i]; 
    } 
    if(l==r) 
    return; 
    build(p+1,l,mid); 
    build(p+1,mid+1,r); 

int query(int p,int l,int r,int ql,int qr,int k) 

    int s,ss;//s表示l到ql-1的區間內放入左子樹的個數,ss表示區間[ql,qr]被放入左子樹的個數  
    if(l==r)//找到所求的數  
    return t[p][l]; 
    if(ql==l) 
    s=0,ss=sum[p][qr]; 
    else 
    s=sum[p][ql-1],ss=sum[p][qr]-s; 
    if(k<=ss)//要找的數在左子樹中  
    return query(p+1,l,mid,l+s,l+sum[p][qr]-1,k); 
    else//要找的數在右子樹中  
    return query(p+1,mid+1,r,mid+1-l+ql-s,mid+1-l+qr-sum[p][qr],k-ss); 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved