conclude segment tree.
This commit is contained in:
@@ -9,7 +9,13 @@
|
||||
|
||||
优先级队列在外部排序中也有相应的应用。例如在归并排序中,为了减少I/O的访问次数,往往采用`多路归并`的方法,此时每一次归并都需要从`m`路的数据中选择出最值。在实际应用中,往往是使用`锦标赛树`来实现,顾名思义,`锦标赛树`是采用类似于锦标赛的模式,对数据进行两两比较,层层筛选最终得到全局的最值(即冠军)。在这种应用场合下,锦标赛树的性能是优于堆的,尽管只是在常系数的层面上,这是因为堆的`下滤`每次需要两次比较,而锦标赛树的每一步`重赛`(replay)只需要一次,同理在[从n个元素中选取最小的k个](k << n)问题中,锦标赛树也具有更优的性能。
|
||||
|
||||
锦标赛树还具有一些问题,例如每次`重赛`都需要沿着路径迂回访问邻居节点,为了避免这些不必要的迂回,可以把每次比较的[败者]记录在父亲节点当中,这样每次`重赛`就只需要和父亲节点进行比较,这就是`败者树`,在`多路归并选择排序`当中,实际上用的就是败者树。
|
||||
锦标赛树还具有一些问题,例如每次`重赛`都需要沿着路径迂回访问邻居节点,为了避免这些不必要的迂回,可以把每次比较的[败者]记录在父亲节点当中,这样每次`重赛`就只需要和父亲节点进行比较,这就是`败者树`,在`多路归并选择排序`当中,实际上用的就是`败者树`。
|
||||
|
||||
`线段树`(segment tree)并不是这里的内容,但是昨晚也学习了一下,简单做一个总结。`线段树`主要是用于一维区间的统计问题,感觉很类似`kd树`,应该是`kd树`的退化情况。它的基本思想是将一个区间做平均划分,划分的两个子区间分别作为当前节点的左右子树,再将上述过程递归地进行,直至每个区间都是单位区间大小。线段树要解决的问题需要满足`可加性`,即问题的解可以通过若干个子区间加和而得到,比如说每个区间都有一个权重,给定一个查询区间,求其中的权重之和——平凡的方法是针对每一个单位区间分别统计求和,时间复杂度为`O(r)`,`r`为查询区间包含的单位区间个数,但是对于区间权重相对固定,查询区间动态变化的场合(即`在线`问题),该算法的时间性能未免太差。而通过`线段树`,则可以不用访问每个单位区间,而是直接查询作为内部节点的某一些较大区间,即可完成统计。
|
||||
|
||||
`线段树`主要有三种操作,一是`建树`,然后是`区间修改`和`区间查询`。可以简明地使用向量来作为底层存储结构,尽管它本身并非是一棵完全二叉树。设区间长度为`n`,则线段树至多需要`4n`的存储空间,这是因为`线段树`的平衡性很好,其左右子树至多相差两个节点,因此若`线段树`的高度为`h`,则它的高度为`h - 1`的子树一定是满二叉树。这样,对于一个叶节点数量为`n`的线段树,总结点数量为`2n - 1`,因此`4n`的空间足以容纳高度为`h`的满二叉树的全部节点。实际上,`2n - 1`向上取整到最近的`2^k`的空间就足够了。
|
||||
|
||||
为了建立`线段树`,可以采用递归的策略,自上而下地对区间进行二分,直至区间成为单位区间;`线段树`的`区间修改`采用`懒惰修改`的策略,即并不对所有叶节点进行修改,而是将要修改的量保存在它们的祖先结点当中,称为`lazy_tag`,由于对这些祖先结点的值进行了修改,因此不会影响到查询的正确性。`区间查询`沿用二叉树的区间查询的方法,即自顶向下不断判断区间的关系,从而定位要查询的区间,如果在向下深入的过程中,发现了`lazy_tag`,则需要将之向下传递,并且修改路径上区间的权重。
|
||||
|
||||
## 优先级队列
|
||||
|
||||
|
||||
Reference in New Issue
Block a user