划分樹


  划分樹是基於線段樹的一種數據結構,主要用於快速求出(log(n)時間的時間復雜度內))序列區間的第K大值

      划分樹主要分為兩部分,建樹和查詢。

建樹:

  建樹是模擬了快速排序和快速排序,所建的樹每一層都有n個元素,但是根據根節點將子層分為左右子節點,但保證的是,左子節點內的所有元素嚴格不大於右子節點內的所有元素。說到這里,建樹的核心已經很清楚了,每次要遞歸的區間,找到其中位數,小於中位數的放到左子節點內,大於中位數的放到右子節點。同時這樣會保證,在同一個節點內,元素的相對位置相比於原序列來說,相對位置沒有發生變化。但是在分配元素的過程中還存在着一個問題,那就是中位數在需要處理的區間內,可能不止一個,這種情況就要單獨分配,以為,根節點所管轄的區間一旦確立,在左右子節點內元素的個數是確定的,所有將中位數按位補充分配到左右字節點內(看代碼實現)。之后不斷遞歸建樹就可以了。直到處理區間左右邊界相等,遞歸終止。可能這樣說不太容易理解。直接上圖。

 

  建樹過程中有十分重要的一部分,就是相對於樹的每層都有一個標記數組toleft,標記的是當前節點之前包括當前節點被分配到左子樹中的元素的個數

以第0層為例:

假設區間為[l,r],則區間內被分配到左子樹的元素個數為toleft[r]-toleft[l-1],則被分配到右子樹的個數就為r-l+1-toleft[r]+toleft[l-1];

代碼實現建樹

 

const int maxn=100010;
int tree[20][maxn];//1000000個元素最多20層
int sorted[maxn];//初始排序,便於快速查找區間中位數
int toleft[20][maxn];//標記數組
int n,m;
void build(int l,int r,int dep)
{
    if(l==r) return ;
    int mid=l+r>>1;
    int same=mid-l+1;
    for(int i=l;i<=r;i++)
        if(tree[dep][i]<sorted[mid]) same--;//單獨處理中位數,看小於中位數的分配完后,左子樹是否有多余的空位
    int lpos=l,rpos=mid+1;
    for(int i=l;i<=r;i++)
    {
        if(tree[dep][i]<sorted[mid]) tree[dep+1][lpos++]=tree[dep][i];
        else if(tree[dep][i]==sorted[mid] && same) tree[dep+1][lpos++]=tree[dep][i],same--;
        else tree[dep+1][rpos++]=tree[dep][i];
        toleft[dep][i]=toleft[dep][l-1]+lpos-l;//標記被分配到左子樹的個數
    }
    build(l,mid,dep+1);
    build(mid+1,r,dep+1);
}

 

查詢:

 查詢是在每一層的toleft的基礎上進行區間的縮小,直到待查詢區間縮小為1即為查詢結果

總區間為[L,R],待查區間為[l,r];k是第K大值,toleft[r]-toleft[l-1]為區間[l,r]內被分配到左子數的個數.

toleft[r]-toleft[l-1]>=k

說明第k大值一定在左子樹此時就可以更新區間,首先大區間二分為[L,L+R>>1],然后考慮小區間,可以確定的是,[l,r]分配在左子樹的元素一定在區間[L,L+R>>1]內,所以,確定左邊界sl=L+toleft[l-1]-toleft[L-1]:toleft[l-1]-toleft[L-1]為[L,l-1]內被分配到左子樹的個數,他們不在查找之列,但相對位置不變,這些元素一定排在前面,以此來確定左邊界,右邊界就好確定了,因為toleft[r]-toleft[l-1]>=k,所以左邊界加上toleft[r]-toleft[l-1]-1即可,即sr=sl+toleft[r]-toleft[l-1]-1,同時必有L<=sl,sr<=L+R>>1;

 toleft[r]-toleft[l-1]<k

此時,元素在右子樹,二分大區間[L+R>>1|1,R],確定右邊界,sr=r+toleft[R]-toleft[r],因為相對位置不變,toleft[R]-toleft[r]是分配在左子樹的必定會往前移動,所以右邊界往后移動,右邊界確定后,確定左邊界sl=sr-(l-r-toleft[l-1]-toleft[L-1]),減去分配到左子樹的,剩下就在右子樹。注意,此時要更新k k=k--toleft[l-1]-toleft[L-1],已經確定前面有toleft[l-1]-toleft[L-1]比其小,所以就是求右子樹內區間[sl,sr]第k--toleft[l-1]-toleft[L-1]小。

具體實現看代碼

 

int query(int L,int R,int l,int r,int dep,int k)
{
    if(l==r) return tree[dep][l]; 
    int mid=L+R>>1;
    int cnt=toleft[dep][r]-toleft[dep][l-1];
    if(cnt>=k)
    {
        int newl=L+toleft[dep][l-1]-toleft[dep][L-1];
        int newr=newl+cnt-1;
        return query(L,mid,newl,newr,dep+1,k);
    }
    else 
    {
        int newr=r+toleft[dep][R]-toleft[dep][r];
        int newl=newr-(r-l-cnt);
        return query(mid+1,R,newl,newr,dep+1,k-cnt);
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM