探讨RMQ问题之二:线段树

    上一篇关于解决RMQ问题中文章探讨了一下ST(Sparse Table)算法,其O(NlgN)的预处理时间复杂度和空间复杂度,以及O(1)的查询时间复杂度使得这个算法已经足够优秀。

    不过,线段树(Segment Tree)方法因为能够支持动态更新数据,所以有更加广泛的适用范围。

    线段树,顾名思义就是一种树结构。具体讲,线段树是一棵二叉树,它的每个节点代表一段数组区间,其左右孩子各是其父节点的左右半区间(例如:一个父节点[a,b] (a!=b)其左孩子为[a,(a+b)/2],右孩子为[(a+b)/2+1,b]),显然叶子节点表示单元素区间如:[l,l]。

    很明显线段树是一棵完全二叉树,只有度为0的节点(叶子节点)和度为2的节点,所以整棵树的节点总数为2N-1(N是全区间的元素数目,也就是长度)。

    对于RMQ问题,正好我们可以构造线段树来解决这个问题,显然其空间复杂度为O(N)。并且,按递归构造子树的算法预处理线段树,其时间度杂度亦为O(N)。下面以取区间最大值为例。

    可以定义线段树节点的数据结构如下:

  4 class Segment
  5 {
  6 public:
  7     Segment(int ln,int rn,Segment* lc,Segment* rc,int max):left_node(ln),rig    ht_node(rn),left_child(lc),right_child(rc),max_index(max){}    //构造函数
  8     int left_node;    //节点区间左端点
  9     int right_node;    //右端点
 10    Segment* left_child;    //左孩子
 11    Segment* right_child;    //右孩子
 12    int max_index;    //当前区间上的最大值下标
 13 };

    构造给定了区间(数组)的线段树的方法如下:

    Built 节点P的子树

        1).如果节点P表示的区间为单元素区间,即left_node==right_node,那么将左右孩子置空(即NULL)。由于P为单元素区间,所以最大值下标为当前元素下标,赋值为left_node即可;

        2).否则,创建节点L作为P的左孩子,并初始化其区间为P的左半区间,Built节点L的子树创建节点R为P的右孩子,并初始化其区间为P的右半区间,Built节点R的子树。显然,当前节点的P所表示区间上的最大值就是左右孩子中最大值中较大者,故而P->max_index只需赋值为L与R的max_inde中对应较大值的那一个。

    接下来就是根据给定的区间进行最大值下标的查询,方法如下:

    在节点PQuery区间[x,y]上的最大值下标:       

        1).如果[x,y]正好等于区间P所表示的区间,即x==P->left_node并且y==P->right_node,那么就是找到了区间,直接返回该区间预处理的极值下标P->max_index

        2).如果[x,y]包含在区间的左半部分,即y<=mid(mid=(P->left_node+P->right_node)/2),那么缩小查找范围,在节点P->left_childQuery区间[x,y]上的最大值下标。反之亦然;

        3).如果mid在[x,y]上,那么递归查询,在节点P->left_childQuery区间[x,mid]上的最大值下标在节点P->right_childQuery区间[mid+1,y]上的最大值下标。显然[x,y]上的最大值就是[x,mid]上的最大值与[mid+1,y]上的最大值中的较大者,所以本次调用只需返回两个极值下标中对应较大值的那一个。

    显然其查询时间复杂度为O(NlgN),虽然远不及ST的O(1),但是效率是完全可以接受的。

    具体CODE如下:

 31 void built_segment_childtree(Segment* root)
 32 {
 33     if(root->left_node==root->right_node)
 34     {
 35         root->left_child=NULL;
 36         root->right_child=NULL;
 37         root->max_index=root->left_node;
 38     }
 39     else
 40     {
 41         int mid=(root->left_node+root->right_node)/2;
 42         root->left_child=new Segment(root->left_node,mid,NULL,NULL,0);
 43         built_segment_childtree(root->left_child);
 44         root->right_child=new Segment(mid+1,root->right_node,NULL,NULL,0);
 45         built_segment_childtree(root->right_child);
 46         root->max_index=array[root->left_child->max_index]>array[root->right_child->max_index]?root->left_child->max_index:root->right_child->max_    index;
 47     }
 48 }
 49
 50 int query(int x,int y,Segment* root)
 51 {
 52     if(x==root->left_node&&y==root->right_node) return root->max_index;
 53     else
 54     {
 55         int mid=(root->left_node+root->right_node)/2;
 56         if(y<=mid) return query(x,y,root->left_child);
 57         else if(x>=mid+1)   return query(x,y,root->right_child);
 58         else
 59         {
 60             int ml=query(x,mid,root->left_child);
 61             int mr=query(mid+1,y,root->right_child);
 62             return array[ml]>array[mr]?ml:mr;
 63         }
 64     }
 65 }

    线段树还有其他的广泛应用,待续……

   

转载于:https://my.oschina.net/llmm/blog/92221

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值