现在的位置: 首页 > 综合 > 正文

划分树的用法(一):查询区间第K大值值(poj2104)

2013年02月04日 ⁄ 综合 ⁄ 共 2457字 ⁄ 字号 评论关闭
划分树的用法(一):查询区间第K大值值(poj2104)

2012-09-28 09:05:22

原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 、作者信息和本声明。否则将追究法律责任。http://sbp810050504.blog.51cto.com/2799422/1008930

    可能是我太笨的原因,一个简单的划分树竟然用了三四天才慢慢能把最基础的用法领悟到。其实也挺好的,用时间去拼聪明人的智力,只要能领悟到,我们的收获是相同的!

    好了,言归正传!划分树是线段树的深化,其本质还是线段树。划分树可以解决这样的问题(我现在也只读懂了怎么解决这一个问题):查询序列中动态区间的第k大值。比如:POJ2104(http://poj.org/problem?id=2104)。

    我看了好多的博客和代码才慢慢明白怎么用划分树解决这一道题。如果还不懂线段树的孩子,建议先把线段树的基础用法领悟到了再看这篇博客。

     用划分树来解决选定区间内的第K大值,其实也就两步!一步是建树,另一步则是查询。

     先说我对建树的理解吧!

    建树的过程很简单:两步就OK了!

   第一步:找到序列的中位数,把大于中位数的扔到中位数的左边,小于中位数的扔到数的右边。这样整个序列就被分成了两个区间。

   第二步:对每个子区间,也分别执行第一步操作,直到序列中只有一个元素为止。

   可以看出,建树是一个递归的过程,与线段树的建树有相似之处。

   划分树的建树需要注意以下几点:

     第一:建树是分层的,所以代码中用的是二维数组tree[20][M]。一般10W级别的数据,20层已经够了。

     第二:建树划分的标准是中位数,所以需要排序。而且只排一次序就OK了,为什么只排一次就OK了,我很久都没明白这一点。其实是这样的:对于任意序列:划分后,左边的数据永远不会大于右边的数据。那么对左边数据单独排序与整体排序的结果是一样的,所以排一次序就OK了!

     第三:划分树划分好的数据永远在存放在下一层。比如数据:

 

tree[0][M]=1 5 2 6 3 7 4
排序后为:1 2 3 4 5 6 7
中位数为:4
划分后的结果为:tree[1][M]=1 2 3 4 5 6 7(这组数据有点特殊,划分后来就已经是排好序的了)
(红色表示划分到中位数的左边,黑色表示划分到中位数的右边)
接着划分:tree[2][M]=1 2 3 4 5 6 7
再接着分:tree[3][M]=1 2 3 4 5 6 0
到这里已经分完了,为什么最后是0呢?在第2层(tree[2][M]),7已经分完了,所以不用再分

 

     第四:划分到最后,实际上已经对序列进行排序了。

     划分的时候还有一点需要处理:如果有多个数据相同怎么办呢?通过一种特殊的处理:尽量使左右两边平均分配相同的数。这个特殊处理是这样的:

    在没分之前,先假设中位数左边的数据suppose都已经分到左边了,所以suppose=mid-left+1;然后如果真的分在左边,即if(tree[level][i]<sorted[mid])

suppose--;suppose就减一!到最后,如果suppos=1,则说明中位数左边的数都小于中位数,如果有等于中位数的,则suppose大于1。 

    最后分配的时候,把suppose个数,分到左边就可以了,剩下的分到右边!因为suppose的初值是mid-left+1,这样就能保证中位数左边和右边的数平衡了!

   第五:划分的过程,需要把每层的数据记录:toLeft[20][M]。toLeft[i][j]定义为:第i层[1,j]之间有多个数据被分到了左边(注意这里用的是闭区间)。

   我能理解到建树的过程,就这么多了!

 

  1. void build(int level,int left,int right){ 
  2.     if(left==right)return ; 
  3.     int mid=(left+right)>>1; 
  4.     int i; 
  5.     int suppose;//假设在中位数sorted[mid]左边的数都全部小于sorted[mid] 
  6.     suppose=mid-left+1; 
  7.     for(i=left;i<=right;i++){ 
  8.         if(tree[level][i]<sorted[mid]){ 
  9.             suppose--; 
  10.         } 
  11.     } 
  12.     //如果suppose==1,则说明数组中值为sorted[mid]只有一个数。比如序列:1 3 4 5 6,sorted[mid]=4 
  13.     /*如果suppose>1,则说明数组中左半边值为sorted[mid]的不止一个数,为mid-suppose。比如序列:1 4 4 4 6,sorted[mid]=4 
  14.      * 
  15.      * */ 
  16.     int lpos=left,rpos=mid+1; 
  17.     for(i=left;i<=right;i++){ 
  18.         if(i==left){//这里是预处理,相当与初始化 
  19.             toLeft[level][i]=0; 
  20.         }else
  21.             toLeft[level][i]=toLeft[level][i-1]; 
  22.         } 
  23.         if(tree[level][i]<sorted[mid]){//划分到中位数左边 
  24.             toLeft[level][i]++; 
  25.             tree[level+1][lpos++]=tree[level][i]; 
  26.         }else if(tree[level][i]>sorted[mid]){//划分到中位数右边 
  27.             tree[level+1][rpos++]=tree[level][i]; 

抱歉!评论已关闭.