划分树
1.划分树是一种基于线段树的数据结构。主要用于快速求出(在log(n)的时间复杂度内)序列区间的第k大值.
2.查找整序列的第k大值方法:
a.采用快速查找法,分治思想。然而此方法会破坏原序列,并且需要O(n)的时间复杂度。
b.使用二叉平衡树进行维护,此方法每次查找时间复杂度仅为O(logn)。然而此方法丢失了原序列的顺序信息,无法查找出某区间内的第k大值
c.划分树:基本思想就是对于某个区间,把它划分成两个子区间,左边区间的数小于右边区间的数。查找的时候通过记录进入左子树的数的个数,确定下一个查找区间,最后范围缩小到1,就找到了。
3.建树:
使用一个辅助数组对原数组进行排序,找到这个区间的中位数a[mid],小于等于a[mid]的数划入左子树[l,mid],大于则划入右子树[mid+1,r]。同时,对于第i个数,记录在[l,i]区间内有多少数被划入左子树.注意对于被分到同一子树的元素,元素间的相对位置不能改变。然后递归建树。
4.查找:
查找的过程中主要问题就是确定将要查找的区间。查找的定义:查找深度为h,在大区间[st,ed]中找小区间[s,e]中的第k元素。
解决方法:
在大区间[st,ed]中找小区间[s,e]中的第k元素:在区间[st,s-1]中有sum[s-1]进入左子树,记它为l。同理区间[st,e]中有sum[e]个数进去左子树,记它为r。所以,我们知道区间小区间[s,e]中有(r-l)个数进入左子树。那么如果(r-l)>=k,那么就在左子树中继续查找,否则就在右子树中继续查找。
如果接下来要查找的是左子树,即在大区间[st,mid]中找小区间[?,?]中的第k元素:那么小区间应该是[st+l,st+r-1]。显然,这里k不用变。
如果接下来要查找的是右子树,即在大区间[mid+1,ed]中找小区间[?,?]中的第k-(r-l)元素:那么小区间应该是[mid+1++([st,s-1]区间中进入右子树的个数),mid+1+[st,e]区间进入右子树的个数)-1]。显然,这里k要减去区间里已经进入左子树的个数,即k变为k-(r-l)。
1 #include <iostream> 2 #include <stdio.h> 3 #include <algorithm> 4 #include <memory.h> 5 using namespace std; 6 //此方法只能对互不相同的元素进行处理 7 const int maxnum=9; 8 struct node 9 { 10 int array[maxnum]; 11 int sum[maxnum]; 12 }tree[10]; //树有10层 13 int sorted[maxnum]; 14 15 void Build_tree(int cur,int l,int r) 16 { 17 if(l==r) 18 return ; 19 int m=(l+r)/2; 20 int i; 21 22 int lsame=m-l+1; //这是往cur+1左子树中一共放入的节点,其中小于中值的一定在左子树中,等于中值的可能在左子树,也可能在右子树 23 for(i=l;i<=r;i++) 24 if(tree[cur].array[i]<sorted[m]) 25 lsame--;//与中值相同的放在左子树中的个数 26 27 int lnum=l,rnum=m+1; 28 for(i=l;i<=r;i++) 29 { 30 if(i==l) 31 tree[cur].sum[i]=0; 32 else 33 tree[cur].sum[i]=tree[cur].sum[i-1]; 34 if(tree[cur].array[i]<sorted[m]) 35 { 36 tree[cur].sum[i]++; 37 tree[cur+1].array[lnum++]=tree[cur].array[i]; 38 } 39 else if(tree[cur].array[i]>sorted[m]) 40 tree[cur+1].array[rnum++]=tree[cur].array[i]; 41 else //if(tree[cur].array[i]==sorted[m]) 42 { 43 if(lsame>0) 44 { 45 lsame--; 46 tree[cur].sum[i]++; 47 tree[cur+1].array[lnum++]=tree[cur].array[i]; 48 } 49 else 50 tree[cur+1].array[rnum++]=tree[cur].array[i]; 51 } 52 } 53 Build_tree(cur+1,l,m); 54 Build_tree(cur+1,m+1,r); 55 } 56 57 int find(int cur,int st,int ed,int l,int r,int k) 58 { 59 cout<<cur<<" "<<st<<" "<<ed<<" "<<l<<" "<<r<<" "<<k<<endl; 60 if(l==r) 61 return tree[cur].array[l]; 62 int m=(st+ed)/2; 63 int lnum,rnum; 64 if(l-1<st) 65 lnum=0; 66 else 67 lnum=tree[cur].sum[l-1]; 68 rnum=tree[cur].sum[r]; 69 if(rnum-lnum>=k)//表示有几个点进入左子树 70 { 71 return find(cur+1,st,m,st+lnum,st+rnum-1,k); 72 } 73 else 74 { 75 // int rightl,rightr; 76 // if(l-1<st) 77 // rightl=0; 78 // else 79 // rightl=l-1-lnum; 80 // rightr=(r-l+1)-rnum-1; 81 return find(cur+1,m+1,ed,m+1+(l-st-lnum),m+1+(r-st-rnum),k-(rnum-lnum)); 82 } 83 } 84 85 int main() 86 { 87 int i,j; 88 for(i=1;i<maxnum;i++) 89 { 90 scanf("%d",&tree[0].array[i]); 91 sorted[i]=tree[0].array[i]; 92 } 93 sort(sorted+1,sorted+maxnum); 94 Build_tree(0,1,maxnum-1); 95 96 for(i=0;i<4;i++) 97 { 98 cout<<i<<endl; 99 for(j=1;j<maxnum;j++) 100 cout<<tree[i].array[j]<<" "; 101 cout<<endl; 102 for(j=1;j<maxnum;j++) 103 cout<<tree[i].sum[j]<<" "; 104 cout<<endl; 105 } 106 107 int res; 108 res=find(0,1,maxnum-1,2,5,2); 109 cout<<res<<endl; 110 return 0; 111 } 112 113 114 /* 115 1 1 1 1 1 1 1 1 116 */ 117 118 119 // for(i=0;i<4;i++) 120 // { 121 // cout<<i<<endl; 122 // for(j=1;j<maxnum;j++) 123 // cout<<tree[i].array[j]<<" "; 124 // cout<<endl; 125 // for(j=1;j<maxnum;j++) 126 // cout<<tree[i].sum[j]<<" "; 127 // cout<<endl; 128 // }
问题:
一开始的处理只能针对互不相同的元素,后来引入一个lsame,解决了重复相同元素的问题。