高级数据结构 - 线段树(2)
2016-02-15 19:38
405 查看
【回顾】
上一次我们讲了一些线段树的基础,地址是http://t.cn/RbQ9gVH,主要涉及的有对区间和单个点的修改、查询。这一篇则相对偏向效率与正确性的证明。
【线段树的高度】
首先我们假设线段树有n个节点,那么我们断言这棵线段树的高度不会超过
![](http://img.blog.csdn.net/20160605102330951?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
。考虑将n替换成2的整幂次方,使得
![](http://img.blog.csdn.net/20160605102337850?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
,而k最小。这很容易实现,一个简单的while循环之类就可以轻易搞定了。然后,以这个2的整幂次方为节点数建一棵线段树,显然这棵新的线段树必定不会比原来的更浅,如果比原来的更浅,那就干脆将剩余的那些节点用特殊值补掉,反正也可以通过处理令其不会影响结果。那么这颗线段树的高度,其实和二分查找的类似。想想看,点状匹配第p个元素,实际上就是在一个序列1,2,3,4,5……n中二分查找一个元素p,只不过我们将区间化作一个个的节点,这样更加强大。
那么,我们继续考虑,如果我们点状匹配一个元素,那么最后到达的必定是叶节点。让我们继续考虑,如果现在访问到了某一个节点d,那么下一步因为是点状匹配,所以说只会到某一个子节点去,而这个子节点所管理的空间比d少一半,所以我们可以很轻易地得出结论:任意一颗高度为2的正幂次方的线段树的高度不超过
![](http://img.blog.csdn.net/20160605102347091?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
。再有我们上面的另一个结论,就算n不是2的整幂次方,那么这棵线段树仍然高度不会超过
![](http://img.blog.csdn.net/20160605102353709?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
。
【各种操作的效率】
下面我们来考虑各个上一篇文章里所讲述了的操作:点状查询、点状修改、区间查询、区间修改。在这个开头,我们先归约一下。
实际上来说,点状的查询和修改都在本质上属于区间的修改和查询的子集,因此这两个操作完全可以忽略。不过,这会不会对效率有影响呢?我们下面就先简单地分析一下:点状的查询和修改,因为只是查询或修改某一个元素,那么这个元素肯定处在叶节点上,由程序我们可以知道,其的时间复杂度刚好就是线段树的高度!所以说,这是一个和二分查找的时间复杂度非常类似的复杂度。查询和修改在本质上都很相似,因此都是
![](http://img.blog.csdn.net/20160605102407654?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
级别的时间复杂度。
下面继续来分析一下区间级别的操作。
修改和查询类似,只不过查询时将相应的节点进行一定的答案合并操作(如求最值、求和)接着返回,而修改是将标记打上去。这么说,我们就只讨论查询了。
查询过程中,我们都知道,在极限的情况下,查询过程可能会到达叶节点,那么我们的问题来了:在线段树的某一层中,最多可能有多少个节点被线段树包含?如果我们能求出来,就将这个节点数的函数和树的高度相乘,就可以得出极限情况下的时间复杂度了。很容易看出,答案是最多4个,如下所示:
![](http://img.blog.csdn.net/20160605102414873?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
这是很自然的。但是,现实一定是这样的吗?一定会只有这么多个吗?会不会出现更多?下面就予以说明。我们假想一下,如果在下面的一层中出现了除这4个之外的一个节点,它也被选中了,那么这个节点应该在哪里?我们将上层的两个节点标为1、2,下层的四个节点标为3、4、5、6,那么,这个节点不可能在3左边或6的右边。因为查询是连续的,我们看到3和6中都没有完全选完,这意味着,区间的开始和结尾,都分别在3和6号节点所表示的范围内,而如果在3的左边或6的右边,这就意味着这个区间中间有一段没有被选到!这和算法的逻辑相悖。
接着,这个节点有可能是节点4和5间的一个点吗?仍然不可能。因为如果这个节点在4和5之间,那么其的某个祖先一定被选了。这样的话,既然选了一个祖先,那么以这个祖先为根的子树都相当于被选了(在节点上有附加的信息!),那么我们还要这个节点干什么?
综上所述,一层最多四个节点(被选),这是一个常数,所以各种操作的效率都是
![](http://img.blog.csdn.net/20160605102426413?watermark/2/text/aHR0cDovL2Jsb2cuY3Nkbi5uZXQv/font/5a6L5L2T/fontsize/400/fill/I0JBQkFCMA==/dissolve/70/gravity/Center)
。相当低。
【正确性证明】
正确性的证明可以说是显而易见了。由程序的逻辑,我们很容易推出来,这里就忽略了。
上一次我们讲了一些线段树的基础,地址是http://t.cn/RbQ9gVH,主要涉及的有对区间和单个点的修改、查询。这一篇则相对偏向效率与正确性的证明。
【线段树的高度】
首先我们假设线段树有n个节点,那么我们断言这棵线段树的高度不会超过
。考虑将n替换成2的整幂次方,使得
,而k最小。这很容易实现,一个简单的while循环之类就可以轻易搞定了。然后,以这个2的整幂次方为节点数建一棵线段树,显然这棵新的线段树必定不会比原来的更浅,如果比原来的更浅,那就干脆将剩余的那些节点用特殊值补掉,反正也可以通过处理令其不会影响结果。那么这颗线段树的高度,其实和二分查找的类似。想想看,点状匹配第p个元素,实际上就是在一个序列1,2,3,4,5……n中二分查找一个元素p,只不过我们将区间化作一个个的节点,这样更加强大。
那么,我们继续考虑,如果我们点状匹配一个元素,那么最后到达的必定是叶节点。让我们继续考虑,如果现在访问到了某一个节点d,那么下一步因为是点状匹配,所以说只会到某一个子节点去,而这个子节点所管理的空间比d少一半,所以我们可以很轻易地得出结论:任意一颗高度为2的正幂次方的线段树的高度不超过
。再有我们上面的另一个结论,就算n不是2的整幂次方,那么这棵线段树仍然高度不会超过
。
【各种操作的效率】
下面我们来考虑各个上一篇文章里所讲述了的操作:点状查询、点状修改、区间查询、区间修改。在这个开头,我们先归约一下。
实际上来说,点状的查询和修改都在本质上属于区间的修改和查询的子集,因此这两个操作完全可以忽略。不过,这会不会对效率有影响呢?我们下面就先简单地分析一下:点状的查询和修改,因为只是查询或修改某一个元素,那么这个元素肯定处在叶节点上,由程序我们可以知道,其的时间复杂度刚好就是线段树的高度!所以说,这是一个和二分查找的时间复杂度非常类似的复杂度。查询和修改在本质上都很相似,因此都是
级别的时间复杂度。
下面继续来分析一下区间级别的操作。
修改和查询类似,只不过查询时将相应的节点进行一定的答案合并操作(如求最值、求和)接着返回,而修改是将标记打上去。这么说,我们就只讨论查询了。
查询过程中,我们都知道,在极限的情况下,查询过程可能会到达叶节点,那么我们的问题来了:在线段树的某一层中,最多可能有多少个节点被线段树包含?如果我们能求出来,就将这个节点数的函数和树的高度相乘,就可以得出极限情况下的时间复杂度了。很容易看出,答案是最多4个,如下所示:
这是很自然的。但是,现实一定是这样的吗?一定会只有这么多个吗?会不会出现更多?下面就予以说明。我们假想一下,如果在下面的一层中出现了除这4个之外的一个节点,它也被选中了,那么这个节点应该在哪里?我们将上层的两个节点标为1、2,下层的四个节点标为3、4、5、6,那么,这个节点不可能在3左边或6的右边。因为查询是连续的,我们看到3和6中都没有完全选完,这意味着,区间的开始和结尾,都分别在3和6号节点所表示的范围内,而如果在3的左边或6的右边,这就意味着这个区间中间有一段没有被选到!这和算法的逻辑相悖。
接着,这个节点有可能是节点4和5间的一个点吗?仍然不可能。因为如果这个节点在4和5之间,那么其的某个祖先一定被选了。这样的话,既然选了一个祖先,那么以这个祖先为根的子树都相当于被选了(在节点上有附加的信息!),那么我们还要这个节点干什么?
综上所述,一层最多四个节点(被选),这是一个常数,所以各种操作的效率都是
。相当低。
【正确性证明】
正确性的证明可以说是显而易见了。由程序的逻辑,我们很容易推出来,这里就忽略了。
相关文章推荐
- 高级数据结构 - 线段树(1)
- 逆序对 - 树状数组求解 - 高级数据结构
- 数据结构学习笔记——树的概念
- 数据结构实验报告单链表的基本操作
- 基础数据结构之数组与链表(五)
- 数据结构和算法
- 数据结构及算法
- 二叉树的构建,线索化,以及线索二叉树的遍历c++语言表示(《数据结构》算法6.5,6.6,6.7)
- 数据结构之动态数组实现
- 数据结构和算法
- 学习笔记------数据结构(C语言版) 串的模式匹配
- 【数据结构学习笔记】——霍夫曼编码
- 关于编码-解码
- 折半查找法
- 【数据结构】旋转数组中的最小数字
- 数据结构概论
- 数据结构之哈希表的实现
- 数据结构--单链表
- 【数据结构】用两个栈实现队列
- 【数据结构】二维数组中的查找