劃分樹 詳解(轉)

轉自:   https://blog.csdn.net/Akatsuki__Itachi/article/details/80030929

有這樣一類題目,求的是區間內的第k大數php

劃分樹的定義就是對總體的區間進行劃分,把相對於原來序列中較小的值放在左子樹,較大的放在右子樹,最後按照它的性質進行查詢以此找到要查詢的區間裏的第k大數。ios

例圖(圖是偷的~~~)
這裏寫圖片描述web

1.建樹
建樹是一個不停遞歸的過程
第一步:首先咱們要根據排序後的數組找到當前層數的中值(中值即中位數。注意,是中位數,不是中間的數),將沒有排序的序列(即輸入的原序列)裏面的數這樣安排:小於中位數的放進左子樹,大於等於中位數的放進右子樹。固然了,這是針對中值只有惟一一個時候的作法,一會再說多箇中值應該怎麼處理。數組

第二步:對於每個子區間,咱們都採用第一步的方法去劃分,直到左右區間相等的時候,即爲終止遞歸的條件。markdown

第三步:在咱們向左子樹裏放數的時候,咱們還要統計出區間 [left,right ] 裏有多少個數進入了左子樹(這個主要用於查詢操做)。svg

在劃分樹的時候,有幾點須要注意:
1.建樹是分層的,因此咱們要用二維數組去存儲,第一維只須要20就夠了,由於100000的數據量的話,它的層數爲logN。
2.劃分的標準是中值,在第一步裏已經特別強調過。
3.劃分的數永遠存放在它的下一層,爲何呢?下面舉個例子模擬一下過程就知道了。ui

那麼下面先列出咱們要用到的數組:spa

 
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
const int MAXL(1e5); int tree[20][MAXL+50];//第一維表明當前的樹的層數, //第二維表明這一層通過劃分後的序列值 int toLeft[20][MAXL+50];//第一維表明當前的樹的層數, //第二維表明當前層區間[left,right]進入左子樹的數目 int sorted[MAXL+50];//將初始序列排序後的數組

按照圖中給出的原始序列爲.net

 
 
 
 
 
  • 1
4 2 5 7 1 8 3 6

排序後的序列爲code

 
 
 
 
 
  • 1
1 2 3 4 5 6 7 8

那麼咱們tree [ 0 ]保存的應該是原始序列
而且獲得toLeft [ 0 ] 的序列

 
 
 
 
 
  • 1
  • 2
  • 3
tree[0] = 4 2 5 7 1 8 3 6 toLeft[0]= 1 2 2 2 3 3 4 4

再次強調一遍
toLeft [ i ] [ j ] 存的是 第 i 層,當前劃分區間【 left , right 】裏進入左子樹的個數
至於爲何要這麼存,一會說查詢的時候就知道了。

模擬一下劃分過程
首先是第一層,找到中值4 ( sorted[ ( left + mid) / 2 ] )
那麼tree [ 1 ] 和toLeft [ 1 ] 應該是

 
 
 
 
 
  • 1
  • 2
tree[1]= 4 2 1 3 5 7 8 6 toLeft[1]= 0 1 2 2 1 1 1 2

可能這裏有人注意到問題了,爲何把4劃分到了左區間?上面不是說大於等於中值的劃分到右區間嗎? 別急-

第二層,分別對左子樹和右子樹按照上述的方法劃分

 
 
 
 
 
  • 1
  • 2
tree[2]= 2 1 4 3 5 6 7 8 toLeft[2]= 0 1 0 1 1 1 1 1

在這裏再囉嗦地解釋一下這一組的toLeft數組
很明顯這一組的 2 1 4 3 5 6 7 8
分別在左 右 左 右 子樹
那麼對於左子樹裏的 2 1這個小區間,進入下一層左子樹的數分別爲 0 1
對於右子樹 4 3 這個小區間,進入下一層左子樹的數分別爲 0 1


第三層

 
 
 
 
 
  • 1
  • 2
tree[3]= 1 2 3 4 5 6 7 8 toLeft[3]= 0 0 0 0 0 0 0 0

下面開始說另一個要注意的問題:有多箇中值怎麼辦?

由於咱們要使得左右區間的數量儘量的均等
因此在這裏,咱們用一種特殊的處理方法。

在尚未進行劃分以前,咱們先假設中值左邊的數據都小於中值。
即 設置一個suppose = mid - left + 1。
若是當前的數小於中值,就使suppose減一,即

 
 
 
 
 
  • 1
  • 2
if(tree[level][i]<sorted[mid] suppose--;

若是結果如咱們假設的那樣,那麼suppose最後必定等於1,不然,就說明中值的數量不惟一。那麼在下面進行的時候,若是還剩suppose>1,就先把中值放在左子樹,直到suppose爲0,若是仍還有中值,就把剩下的放進右子樹。
經過這樣操做,就能均分左右子樹了。

再舉個例子增深理解:
3 3 4 4 4 5 7
中值爲4,左子樹要放4個((1+7)/2),右子樹放3個
處理後的suppose爲2
那麼遇到第一個4,放進左子樹,suppose=1;
遇到第二個4,放進左子樹,suppose=0;
遇到第三個4,這時suppose已經等於0,因此放進右子樹。

終於能夠上建樹的代碼了

 
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
void Build_tree(int level,int left,int right)//level爲當前層 { if(left==right)//左右區間相等爲終止條件 return ; int mid=(left+right)>>1; int suppose=mid-left+1;//設定suppose的初值 for(int i=left; i<=right; i++) if(tree[level][i]<sorted[mid])//處理suppose suppose--; int subLeft=left,subRight=mid+1;//進入下層左右子樹的下標 for(int i=left; i<=right; i++) { if(i==left)//初始化 toLeft[level][i]=0; else//初始化 toLeft[level][i]=toLeft[level][i-1]; if(tree[level][i]<sorted[mid]||tree[level][i]==sorted[mid]&&suppose>0) {//這就是上面說的處理多箇中值的狀況,放在一塊兒了 tree[level+1][subLeft++]=tree[level][i];//將數放在下一層 toLeft[level][i]++;//進入左子樹的數目+1 if(tree[level][i]==sorted[mid]) suppose--;//繼續處理suppose } else//進入右子樹 tree[level+1][subRight++]=tree[level][i]; } Build_tree(level+1,left,mid);//遞歸 Build_tree(level+1,mid+1,right);//遞歸 }

在建好樹以後,接下來就是查詢的問題。
假設初始大區間爲【left , right】,要查詢的區間爲【qLeft , qRight】
如今要查詢區間【qLeft , qRight】的第k大數

咱們的想法是,先判斷【qLeft , qRight】在【left , right】的哪一個子樹中,而後找出對應的小區間和k,而後遞歸查找,直到小區間qLeft==qRight時爲止。

那如何解決這個問題呢?這時候前面記錄的進入左子樹的元素個數就派上用場了。經過以前的記錄能夠知道,在區間【left , qLeft】中有toLeft [ level ] [ qLeft - 1 ] 個元素進入了左子樹,記它爲lef,同理,在區間【left , qRight】中有toLeft [ level ] [ qRight ] 個元素進入了左子樹,記它爲rig , 因此在區間【qLeft , qRight】之間就有 rig - lef 個元素進入了左子樹,記爲 toLef。 若是 toLef>= k ,說明 第k大元素確定進入了左子樹,那麼就進入左子樹查找,不然進入右子樹查找。

那麼接下來要解決肯定小區間的問題:

若是進入的是左子樹,那麼小區間就應該是
【 left +( [ left,qLeft-1] )進入左子樹的數目,left +( [ left,qRight ] )進入左子樹的數目-1】
即:【 left + lef , left + lef + tolef-1 】,而且,這時候k的值不用變化。

若是進入的是右子樹,那麼小區間就應該是
【 mid +( [ left,qLeft-1] )進入右子樹的數目+1,mid +( [ left,qRight ] )進入右子樹的數目】
即:【 mid + qLeft - left -lef + 1 , mid + qRight - left - toLef - lef + 1 】
同時,這裏的k要發生變化,變爲k-(【qLeft , qRight】進入左子樹的元素個數)
即 k-toLef

其中mid = ( left + right ) / 2

這裏的區間式子很長,須要仔細思考。

下面舉個例子(又是偷的圖~~~)
這裏寫圖片描述

獻上查詢的代碼

 
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
//[qLeft,qRight]爲查詢的區間,[left,right]爲原始區間 int Query(int level,int qLeft,int qRight,int left,int right,int k) { int mid=(left+right)>>1; if(qLeft==qRight)//終止條件 return tree[level][qLeft]; int lef;//lef 表明[left,qLeft]進入左子樹的個數 int toLef;//toLeft表明[qLeft,qRight]進入左子樹的個數 if(qLeft==left)//若是和原始區間重合 lef=0,toLef=toLeft[level][qRight]; else lef=toLeft[level][qLeft-1],toLef=toLeft[level][qRight]-lef; if(k<=toLef)//進入左子樹 { int newLeft=left+lef; int newRight=left+lef+toLef-1; return Query(level+1,newLeft,newRight,left,mid,k); } else//進入右子樹 { int newLeft=mid+qLeft-left-lef+1; int newRight=mid+qRight-left-toLef-lef+1; return Query(level+1,newLeft,newRight,mid+1,right,k-toLef); } }

好了,說的也差很少了。
接下來就是一個模板題
poj2104

hdu2665
這個加了一個T組樣例

poj2104 AC代碼

 
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 100
  • 101
  • 102
  • 103
  • 104
  • 105
  • 106
  • 107
  • 108
  • 109
#include<iostream> #include<cstdio> #include<cstdlib> #include<cstring> #include<string> #include<cmath> #include<iomanip> #include<map> #include<stack> #include<vector> #include<queue> #include<set> #include<utility> #include<list> #include<algorithm> #define max(a,b) (a>b?a:b) #define min(a,b) (a<b?a:b) #define swap(a,b) (a=a+b,b=a-b,a=a-b) #define memset(a,v) memset(a,v,sizeof(a)) #define X (sqrt(5)+1)/2.0 //Wythoff #define Pi acos(-1) #define e 2.718281828459045 #define eps 1.0e-8 using namespace std; typedef long long int LL; typedef pair<int,int>pa; const int MAXL(1e5); const int INF(0x3f3f3f3f); const int mod(1e9+7); int dir[4][2]= {{-1,0},{1,0},{0,1},{0,-1}}; int tree[20][MAXL+50]; int toLeft[20][MAXL+50]; int sorted[MAXL+50]; void Build_tree(int level,int left,int right) { if(left==right) return ; int mid=(left+right)>>1; int suppose=mid-left+1; for(int i=left; i<=right; i++) if(tree[level][i]<sorted[mid]) suppose--; int subLeft=left,subRight=mid+1; for(int i=left; i<=right; i++) { if(i==left) toLeft[level][i]=0; else toLeft[level][i]=toLeft[level][i-1]; if(tree[level][i]<sorted[mid]||tree[level][i]==sorted[mid]&&suppose>0) { tree[level+1][subLeft++]=tree[level][i]; toLeft[level][i]++; if(tree[level][i]==sorted[mid]) suppose--; } else tree[level+1][subRight++]=tree[level][i]; } Build_tree(level+1,left,mid); Build_tree(level+1,mid+1,right); } int Query(int level,int qLeft,int qRight,int left,int right,int k) { int mid=(left+right)>>1; if(qLeft==qRight) return tree[level][qLeft]; int lef; int toLef; if(qLeft==left) lef=0,toLef=toLeft[level][qRight]; else lef=toLeft[level][qLeft-1],toLef=toLeft[level][qRight]-lef; if(k<=toLef) { int newLeft=left+lef; int newRight=left+lef+toLef-1; return Query(level+1,newLeft,newRight,left,mid,k); } else { int newLeft=mid+qLeft-left-lef+1; int newRight=mid+qRight-left-toLef-lef+1; return Query(level+1,newLeft,newRight,mid+1,right,k-toLef); } } int main() { int n,m; scanf("%d%d",&n,&m); for(int i=1; i<=n; i++) { scanf("%d",&tree[0][i]); sorted[i]=tree[0][i]; } sort(sorted+1,sorted+n+1); Build_tree(0,1,n); while(m--) { int ql,qr,k; scanf("%d%d%d",&ql,&qr,&k); int ans=Query(0,ql,qr,1,n,k); cout<<ans<<endl; } }

作題的過程當中發現了toLeft數組的另外一種存法

下面的模板代碼對於toLeft【i】【j】存的是第 i 層 1到 j 進入左子樹的元素個數
copy下別人的模板

 
 
 
 
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
#include <iostream> #include <cstdio> #include <cstring> #include <algorithm> using namespace std; #define MAX_SIZE 100005 int sorted[MAX_SIZE];//已經排好序的數據 int toleft[25][MAX_SIZE]; int tree[25][MAX_SIZE]; void build_tree(int left, int right, int deep) { int i; if (left == right) return ; int mid = (left + right) >> 1; int same = mid - left + 1; //位於左子樹的數據 for (i = left; i <= right; ++i) {//計算放於左子樹中與中位數相等的數字個數 if (tree[deep][i] < sorted[mid]) { --same; } } int ls = left; int rs = mid + 1; for (i = left; i <= right; ++i) { int flag = 0; if ((tree[deep][i] < sorted[mid]) || (tree[deep][i] == sorted[mid] && same > 0)) { flag = 1; tree[deep + 1][ls++] = tree[deep][i]; if (tree[deep][i] == sorted[mid]) same--; } else { tree[deep + 1][rs++] = tree[deep][i]; } toleft[deep][i] = toleft[deep][i - 1]+flag; } build_tree(left, mid, deep + 1); build_tree(mid + 1, right, deep + 1); } int query(int left, int right, int k, int L, int R, int deep) { if (left == right) return tree[deep][left]; int mid = (L + R) >> 1; int x = toleft[deep][left - 1] - toleft[deep][L - 1];//位於left左邊的放於左子樹中的數字個數 int y = toleft[deep][right] - toleft[deep][L - 1];//到right爲止位於左子樹的個數 int ry = right - L - y;//到right右邊爲止位於右子樹的數字個數 int cnt = y - x;//[left,right]區間內放到左子樹中的個數 int rx = left - L - x;//left左邊放在右子樹中的數字個數 if (cnt >= k) { //printf("sss %d %d %d\n", xx++, x, y); return query(L + x, L + y - 1, k, L, mid, deep + 1); // 由於x不在區間內 因此不要緊 因此先除去,從L+x開始,而後肯定範圍 } else { //printf("qqq %d %d %d\n", xx++, x, y); return query(mid + rx + 1, mid + 1 + ry, k - cnt, mid + 1, R, deep + 1); //同理 把不在區間內的 分到右子樹的元素數目排除,肯定範圍 } } int main() { int m, n; int a, b, k; int i; while (scanf("%d%d", &m, &n) == 2) { for (i = 1; i <= m; ++i) { scanf("%d", &sorted[i]); tree[0][i] = sorted[i]; } sort(sorted + 1, sorted + 1 + m); build_tree(1, m, 0); for (i = 0; i < n; ++i) { scanf("%d%d%d", &a, &b, &k); printf("%d\n", query(a, b, k, 1, m, 0)); } } return 0; }
相關文章
相關標籤/搜索