zoukankan      html  css  js  c++  java
  • 笔试算法题(42):线段树(区间树,Interval Tree)

    议题:线段树(Interval Tree)

    分析:

    • 线段树是一种二叉搜索树,将一个大区间划分成单元区间,每个单元区间对应一个叶子节点;内部节点对应部分区间,如对于一个内部节点[a, b]而言,其左子节点表示的区间为[a, (a+b)/2],其右子节点表示的区间为[1+(a+b)/2, b];

    • 对于区间长度为N的线段树,由于其单元节点都是[a, a]的叶子节点,所以其叶子节点数为N,并且整棵树为平衡二叉树,所以总节点数为2N-1,树的深度为log(N)+1;

    • 插入操作:将一条线段[a, b]插入到区间长度为[l, r]的线段树root中,如果root不是单元节点,则求得mid=(l+r)/2;(删除和查找操作类似)


      如果b<=mid,则将[a, b]插入到root->left子树中;
      如果a=b=mid,则将[a, b]插入到root->left子树中,
      如果a=mid<b,则将[a, a]插入到root->left子树中,[a+1, b]插入到root->right子树中;
      如果a>mid,则将[a, b]插入到root->right子树中;
      如果a<mid<b,则将[a, mid]插入到root->left子树,将[mid+1, b]插入到root->right子树中
    • 线段树最主要的应用是判定几个给定区间之间的关系,判定某一个区间A是否在若干个目标区间内出现,时间复杂度为O(MlogN),M为A的区间长度,N为 构建线段树的整个区间长度;但原始的线段树需要表示每一个单元区间,所以空间复杂度较高为2N,优化方案是离散化(Discretization)压缩线 段树区间;但是由于线段树每个节点上需要维护一个int的计数变量(记录其子树被覆盖的次数),所以每次插入或者删除操作都需要O(N)的时间维护线段树 的正确性,可以为每一个节点增加一个延迟标记的delta值(Delay Mark),这个值记录当前节点所在的区间需要进行的修改(但是还没有对其左右子树的节点进行修改),当查询路径需要到其左右子树中时,将这个delta 值传递给其左右子树,而将本节点保存饿delta值去除;

    • 一道利用线段树的题目:给定一个自然数集合,自然数的范围是[0,30000],现在已知N条线段,每条线段以[a,b]的形式给出,现在有M个数字,要求判断每个数字分别在多少条线段上出现过;

    • 一般解法是将M个数字分别于N条线段比较判断,时间复杂度为O(MN);可以利用线段树记录N条线段在[0, 30000]范围内出现的次数和位置,然后利用二叉树的O(logN)查找时间判断每个数字最终出现的线段;

    • 首先是按照给定的元素范围构建线段树的框架;如给定[0, 7],则利用线段树为满二叉树的性质,其节点个数为2N-1=15;基本存储结构为数组,如果当前节点为array[i],则array[2*i]和 array[2*i+1]分别为左右子节点;节点内拥有一个记录本节点代表的线段出现的次数,初始化为0;时间复杂度为O(logK),K为给定的区间范 围;

       
    • 然后将给定的N条线段插入到已经构建好的线段树框架中;如给定三条线段:[2,5],[4,6],[0,7],依次插入则最终成为一棵可用的线段树;时间复杂度为O(NlogK)
       
    • 最后是查询给定的M个点在N条线段出现的次数;如给定数字2,则递归到线段树中的节点[2,2],并将经过的节点的count值累加起来,最终就是数字2在给定的线段集合内出现的次数;

    样例:

      1 /**
      2  * count记录这条线段出现的次数
      3  * */
      4 struct Interval {
      5         int left;
      6         int right;
      7         int count;
      8 };
      9 
     10 /**
     11  * 此函数用于创建线段树的基本结构,InterTree是一个元素为Interval类型的指针数组
     12  * 其大小为2*(max-min+1),也就是满二叉树;
     13  * */
     14 void ConstructIT(int min, int max, Interval** InterTree, int index) {
     15         /**
     16          * 构建当前节点
     17          * index用于索引InterTree中当前元素的位置
     18          * 遵循父节点为i,则左右子节点分别为2*i, 2*i+1
     19          * */
     20         Interval *temp=new Interval();
     21         temp->count=0;
     22         temp->left=min;
     23         temp->right=max;
     24         InterTree[index]=temp;
     25         /**
     26          * 如果min和max相等,则说明已经到叶子节点
     27          * */
     28         if(min==max)
     29                 return;
     30         /**
     31          * 分别递归创建左右子节点
     32          * */
     33         int middle=(min+max)/2;
     34         ConstructIT(min,middle,InterTree,index*2);
     35         ConstructIT(middle+1,max,InterTree,index*2+1);
     36 }
     37 /**
     38  * 此函数用于注入线段树的线段标记信息
     39  * */
     40 void InsertInfor(int min, int max, Interval** InterTree, int index) {
     41         /**
     42          * 构建线段树的时候,middle本身属于left子树
     43          * */
     44         int middle=(InterTree[index]->left + InterTree[index]->right)/2;
     45 
     46         if(max<=middle) {
     47                 /**
     48                  * 插入线段完全在middle左边
     49                  * */
     50                 InsertInfor(min, max, InterTree, 2*index);
     51         } else if(min>middle) {
     52                 /**
     53                  * 插入线段完全在middle右边
     54                  * */
     55                 InsertInfor(min, max, InterTree, 2*index+1);
     56         } else if(min==InterTree[index]->left &&
     57                         max==InterTree[index]->right) {
     58                 /**
     59                  * 插入线段恰好是节点本身的端点范围,有可能是叶子节点
     60                  * */
     61                 InterTree[index]->count+=1;
     62                 return;
     63         } else if(InterTree[index]->left==InterTree[index]->right) {
     64                 /**
     65                  * 已经到达叶子节点,但是min和max仍旧不等,错误返回
     66                  * */
     67                 return;
     68         } else {
     69                 /**
     70                  * middle将待插入的线段分成两段
     71                  * */
     72                 InsertInfor(min, middle, InterTree, 2*index);
     73                 InsertInfor(middle+1, max, InterTree, 2*index+1);
     74         }
     75 }
     76 /**
     77  * 此函数用于检查覆盖区间,注意此时的min和max可能超出
     78  * InterTree的范围
     79  * */
     80 int CheckInterval(int min, int max, Interval** InterTree, int index) {
     81         int middle=(InterTree[index]->left + InterTree[index]->right)/2;
     82         if(max<middle) {
     83                 return CheckInterval(min, max, InterTree, 2*index);
     84         } else if(min>middle) {
     85                 return CheckInterval(min, max, InterTree, 2*index+1);
     86         } else if(min==InterTree[index]->left &&
     87                         max==InterTree[index]->right) {
     88                         return InterTree[index]->count;
     89         } else {
     90                 return CheckInterval(min, middle, InterTree, 2*index)+
     91                 CheckInterval(middle+1, max, InterTree, 2*index+1);
     92         }
     93 }
     94 
     95 void Crossover(int *first, int lfirst, int *second, int lsecond) {
     96         /**
     97          * 获取first和second数组中各自的最大值与最小值的差值
     98          * 选取差值较小的一个范围作为线段树的构建区间
     99          * 节省空间消耗
    100          * */
    101         int minf=0, maxf=0,mins=0,maxs=0;
    102         for(int i=1;i<lfirst;i+=2) {
    103                 if(maxf<first[i])
    104                         maxf=first[i];
    105                 if(minf>first[i])
    106                         minf=first[i];
    107         }
    108         for(int i=1;i<lsecond;i+=2) {
    109                 if(maxs<second[i])
    110                         maxs=second[i];
    111                 if(mins>second[i])
    112                         mins=second[i];
    113         }
    114         int min=minf,max=maxf;
    115         int isFirst=true;
    116         if((max-min)>(maxs-mins)) {
    117                 min=mins;
    118                 max=maxs;
    119                 isFirst=false;
    120         }
    121         /**
    122          * 构建一个数组,数组元素为Interval*类型,
    123          * 数组大小为线段树节点个数,由于线段树是
    124          * 完全二叉树,所以节点数肯定为2N-1,N为区间
    125          * 大小,也为叶节点数
    126          * */
    127         Interval *InterTree[2*(max-min+1)-1];
    128         /**
    129          * 首先构建线段树的基本结构
    130          * */
    131         ConstructIT(min, max, InterTree, 0);
    132         /**
    133          * 然后将线段树范围大小对应的区间数组元素
    134          * 插入到线段树中,如果区间在线段树之外则
    135          * 需要特殊处理
    136          * */
    137         int *temp=NULL, length=0;
    138         int overlapSize=0;
    139         if(isFirst) {
    140                 for(int i=1;i<lfirst;i+=2) {
    141                         if(first[i]<InterTree[0]->left ||
    142                                         first[i-1]>InterTree[0]->right)
    143                                 continue;
    144                         else if(first[i]==InterTree[0]->left ||
    145                                         first[i-1]==InterTree[0]->right)
    146                                 overlapSize++;
    147                         else
    148                                 InsertInfor(first[i-1],first[i], InterTree, 0);
    149                 }
    150                 temp=second;
    151                 length=lsecond;
    152         } else {
    153                 for(int i=1;i<lsecond;i+=2) {
    154                         if(second[i]<InterTree[0]->left ||
    155                                         second[i-1]>InterTree[0]->right)
    156                                 continue;
    157                         else if(second[i]==InterTree[0]->left ||
    158                                         second[i-1]==InterTree[0]->right)
    159                                 overlapSize++;
    160                         else
    161                                 InsertInfor(second[i-1],second[i], InterTree, 0);
    162                 }
    163                 temp=first;
    164                 length=lfirst;
    165         }
    166         /**
    167          * 顺序将temp中的区间元素插入到InterTree中
    168          * */
    169 
    170         for(int i=1;i<length;i+=2) {
    171                 length+=CheckInterval(temp[i-1],temp[i],InterTree,0);
    172         }
    173 }

    参考连接:
    http://hi.baidu.com/alpc62/blog/item/469edeca0043e382c8176875.html

  • 相关阅读:
    自定义拦截器
    MVP模式网络请求购物车
    mvp+RecyclerView实现网络请求
    二维码扫描
    进度条加载
    画圆移动
    简单排序
    批量发货
    Angular服务
    终极购物车
  • 原文地址:https://www.cnblogs.com/leo-chen-2014/p/3754470.html
Copyright © 2011-2022 走看看