经典算法题每日演练——第十二题 线段树
来源:互联网 发布:linux系统添加用户组 编辑:程序博客网 时间:2024/06/06 10:53
这一篇我们来看树状数组的加强版线段树,树状数组能玩的线段树一样可以玩,而且能玩的更好,他们在区间求和,最大,平均
等经典的RMQ问题上有着对数时间的优越表现。
一:线段树
线段树又称"区间树”,在每个节点上保存一个区间,当然区间的划分采用折半的思想,叶子节点只保存一个值,也叫单元节点,所
以最终的构造就是一个平衡的二叉树,拥有CURD的O(lgN)的时间。
从图中我们可以清楚的看到[0-10]被划分成线段的在树中的分布情况,针对区间[0-N],最多有2N个节点,由于是平衡二叉树的形
式也可以像堆那样用数组来玩,不过更加耗费空间,为最多4N个节点,在针对RMQ的问题上,我们常常在每个节点上增加一些sum,
max,min等变量来记录求得的累加值,当然你可以理解成动态规划的思想,由于拥有logN的时间,所以在RMQ问题上比数组更加优美。
二:代码
1:在节点中定义一些附加值,方便我们处理RMQ问题。
1 #region 线段树的节点 2 /// <summary> 3 /// 线段树的节点 4 /// </summary> 5 public class Node 6 { 7 /// <summary> 8 /// 区间左端点 9 /// </summary>10 public int left;11 12 /// <summary>13 /// 区间右端点14 /// </summary>15 public int right;16 17 /// <summary>18 /// 左孩子19 /// </summary>20 public Node leftchild;21 22 /// <summary>23 /// 右孩子24 /// </summary>25 public Node rightchild;26 27 /// <summary>28 /// 节点的sum值29 /// </summary>30 public int Sum;31 32 /// <summary>33 /// 节点的Min值34 /// </summary>35 public int Min;36 37 /// <summary>38 /// 节点的Max值39 /// </summary>40 public int Max;41 }42 #endregion
2:构建(Build)
前面我也说了,构建有两种方法,数组的形式或者链的形式,各有特点,我就采用后者,时间为O(N)。
1 #region 根据数组构建“线段树" 2 /// <summary> 3 /// 根据数组构建“线段树" 4 /// </summary> 5 /// <param name="length"></param> 6 public Node Build(int[] nums) 7 { 8 this.nums = nums; 9 10 return Build(nodeTree, 0, nums.Length - 1);11 }12 #endregion13 14 #region 根据数组构建“线段树"15 /// <summary>16 /// 根据数组构建“线段树"17 /// </summary>18 /// <param name="left"></param>19 /// <param name="right"></param>20 public Node Build(Node node, int left, int right)21 {22 //说明已经到根了,当前当前节点的max,sum,min值(回溯时统计上一层节点区间的值)23 if (left == right)24 {25 return new Node26 {27 left = left,28 right = right,29 Max = nums[left],30 Min = nums[left],31 Sum = nums[left]32 };33 }34 35 if (node == null)36 node = new Node();37 38 node.left = left;39 40 node.right = right;41 42 node.leftchild = Build(node.leftchild, left, (left + right) / 2);43 44 node.rightchild = Build(node.rightchild, (left + right) / 2 + 1, right);45 46 //统计左右子树的值(min,max,sum)47 node.Min = Math.Min(node.leftchild.Min, node.rightchild.Min);48 node.Max = Math.Max(node.leftchild.Max, node.rightchild.Max);49 node.Sum = node.leftchild.Sum + node.rightchild.Sum;50 51 return node;52 }53 #endregion
3:区间查询
在线段树中,区间查询还是有点小麻烦的,存在三种情况。
① 完全包含:也就是节点的线段范围完全在查询区间的范围内,这说明我们要么到了“单元节点",要么到了一个子区间,这种情况
就是我找到了查询区间的某一个子区间,直接累积该区间值就可以了。
② 左交集: 这种情况我们需要到左子树去遍历。
③右交集: 这种情况我们需要到右子树去遍历。
比如说:我要查询Sum[4-8]的值,最终会成为:Sum总=Sum[4-4]+Sum[5-5]+Sum[6-8],时间为log(N)。
1 #region 区间查询 2 /// <summary> 3 /// 区间查询(分解) 4 /// </summary> 5 /// <returns></returns> 6 public int Query(int left, int right) 7 { 8 int sum = 0; 9 10 Query(nodeTree, left, right, ref sum);11 12 return sum;13 }14 15 /// <summary>16 /// 区间查询17 /// </summary>18 /// <param name="left">查询左边界</param>19 /// <param name="right">查询右边界</param>20 /// <param name="node">查询的节点</param>21 /// <returns></returns>22 public void Query(Node node, int left, int right, ref int sum)23 {24 //说明当前节点完全包含在查询范围内,两点:要么是单元节点,要么是子区间25 if (left <= node.left && right >= node.right)26 {27 //获取当前节点的sum值28 sum += node.Sum;29 return;30 }31 else32 {33 //如果当前的left和right 和node的left和right无交集,此时可返回34 if (node.left > right || node.right < left)35 return;36 37 //找到中间线38 var middle = (node.left + node.right) / 2;39 40 //左孩子有交集41 if (left <= middle)42 {43 Query(node.leftchild, left, right, ref sum);44 }45 //右孩子有交集46 if (right >= middle)47 {48 Query(node.rightchild, left, right, ref sum);49 }50 51 }52 }53 #endregion
4:更新操作
这个操作跟树状数组中的更新操作一样,当递归的找到待修改的节点后,改完其值然后在当前节点一路回溯,并且在回溯的过程中一
路修改父节点的附加值直到根节点,至此我们的操作就完成了,复杂度同样为logN。
1 #region 更新操作 2 /// <summary> 3 /// 更新操作 4 /// </summary> 5 /// <param name="index"></param> 6 /// <param name="key"></param> 7 public void Update(int index, int key) 8 { 9 Update(nodeTree, index, key);10 }11 12 /// <summary>13 /// 更新操作14 /// </summary>15 /// <param name="index"></param>16 /// <param name="key"></param>17 public void Update(Node node, int index, int key)18 {19 if (node == null)20 return;21 22 //取中间值23 var middle = (node.left + node.right) / 2;24 25 //遍历左子树26 if (index >= node.left && index <= middle)27 Update(node.leftchild, index, key);28 29 //遍历右子树30 if (index <= node.right && index >= middle + 1)31 Update(node.rightchild, index, key);32 33 //在回溯的路上一路更改,复杂度为lgN34 if (index >= node.left && index <= node.right)35 {36 //说明找到了节点37 if (node.left == node.right)38 {39 nums[index] = key;40 41 node.Sum = node.Max = node.Min = key;42 }43 else44 {45 //回溯时统计左右子树的值(min,max,sum)46 node.Min = Math.Min(node.leftchild.Min, node.rightchild.Min);47 node.Max = Math.Max(node.leftchild.Max, node.rightchild.Max);48 node.Sum = node.leftchild.Sum + node.rightchild.Sum;49 }50 }51 }52 #endregion
最后我们做个例子,在2000000的数组空间中,寻找200-3000区间段的sum值,看看他的表现如何。
- 经典算法题每日演练——第十二题 线段树
- 经典算法题每日演练——第十二题 线段树
- 经典算法题每日演练——第一题 百钱买百鸡
- 经典算法题每日演练——第一题 百钱买百鸡
- 经典算法题每日演练——第二题 五家共井
- 经典算法题每日演练——第一题 百钱买百鸡
- 经典算法题每日演练——第二题 五家共井
- 经典算法题每日演练——第十三题 赫夫曼树
- 经典算法题每日演练——第一题 百钱买百鸡
- 经典算法题每日演练——第二题 五家共井
- 经典算法题每日演练——第十三题 赫夫曼树
- 经典算法题——第十二题 线段树
- 经典算法题每日演练——第六题 协同推荐SlopeOne 算法
- 经典算法题每日演练——第七题 KMP算法
- 经典算法题每日演练——第十一题 Bitmap算法
- 经典算法题每日演练——第十四题 Prim算法
- 经典算法题每日演练——第十六题 Kruskal算法
- 经典算法题每日演练——第十七题 Dijkstra算法
- yourphp二次开发目录
- 经典算法题每日演练——第十一题 Bitmap算法
- 显示转换static_cast、const_cast、reinterpret_cast、dynamic_cast详解
- Hadoop入门进阶课程8--Hive介绍和安装部署
- hdu 5319 Painter (此题纪念不仔细读题而逝去的WA)
- 经典算法题每日演练——第十二题 线段树
- UVa 1635 - Irrelevant Elements(唯一分解)
- SmartPaaS开放平台支撑企业实现“互联网+”
- spring环境搭建以及和struts整合
- poj1664 放苹果 解题报告
- UNIX网络编程卷一:第三章 套接字编程简介
- 经典算法题每日演练——第十三题 赫夫曼树
- 经典算法题每日演练——第十四题 Prim算法
- a different object with the same identifier value was already 。。 。。 。 。 解决方法