前言
教科书上所学的所有数据结构都是最常规、最精简的数据结构。 我们可以对现有的数据结构进行些许改造进而解决一些特殊的问题。本章介绍两种通过扩张红黑树构造出的数据结构,一种是动态顺序统计树;另一种是区间树。
动态顺序统计
第九章 顺序统计 。在一个无序的集合中,任意的顺序统计量都可以在 O ( n )时间内找到。而这里我们将介绍如何在 O ( lg n )时间内确定任意的顺序统计量。我们可以用 顺序统计树 的方式去解决。
一棵 顺序统计树 T 通过在红黑树的每个结点中存入附加信息而成。在一个结点 x 内,增加域 x.size 。该域包含以结点 x 为根的子树的(内部)结点数(包括 x 本身),即子树的大小。设 T.nil.size 为0。
x.size = x.left.size + x.right.size + 1
检索具有给定排序的元素
过程 OS-SELECT(x, i) 返回一个指向以 x 为根的子树中包含第 i 小关键字的结点的指针.运行时间为 O ( lg n )。
OS-SELECT(x, i)
1 r = x.left.size + 1
2 if i == r
3 return x
4 elseif i < r
5 return OS-SELECT(x.left, i)
6 else
7 return OS-SELECT(x.right, i)
确定一个元素的秩
给定指向一顺序统计树 T 中结点 x 的指针,过程 OS-RANK 返回在对 T 进行中序遍历后得到的线性序中 x 的位置。x 的秩可以视为在对树的中序遍历中,排在 x 之前的结点个数再加1。在最坏情况下,对含 n 个结点的顺序统计树, OS-RANK 的运行时间为 O(lgn)。
OS-RANK(T, x)
1 r = x.left.size + 1
2 y = x
3 while y != T.root
4 if y == y.p.right
5 r = r + y.p.left.size + 1
6 y = y.p
7 return r
对子树规模的维护
红黑树上基本的修改操作对这些size域加以有效的维护。
- 插入结点:在寻找插入位置的过程中,所经路径上的结点size域都需加1
- 旋转:如果插入结点后,需要旋转某些结点,可以验证,可以在常数时间内更新相关结点的size域。
练习
1-1对于图14-1中的红黑树T,说明执行OS_SELECT(T.root,10)的过程。
- 26:r=13,i=10, go left.
- 17: r = 8, i = 1017:r=8,i=10, go right.
- 21: r = 3, i = 221:r=3,i=2, go left.
- 19: r = 1, i = 219:r=1,i=2, go right.
- 20: r = 1, i = 120:r=1,i=1, choose 20.
1-2对于图14-1中的红黑树T和关键字x.key为35的结点x,说明执行OS_RNAK(T,x)的过程
- 35:r=1.
- 38: r = 138:r=1.
- 30: r = r + 2 = 330:r=r+2=3.
- 41: r = 341:r=3.
- 26: r = r + 13 = 1626:r=r+13=16.
1-3写出OS_SELECT的非递归版本。
OS-SELECT(x, i)
r = x.left.size + 1
while r != i
if i < r
x = x.left
else x = x.right
i = i - r
r = x.left.size + 1
return x
1-4 写出一个递归过程OS_KEY_RANK(T,k),以一棵顺序统计树T和一个关键字k作为输入,要求返回k在由T表示的动态集合中的秩。假设T的所有关键字都不相同。
OS-KEY-RANK(T, k)
if k == T.root.key
return T.root.left.size + 1
else if T.root.key > k
return OS-KEY-RANK(T.left, k)
else return T.root.left.size + 1 + OS-KEY-RANK(T.right, k)
1-5给定n个元素的顺序统计树中的一个元素x和一个自然数i,如何在O(lgn)时间内确定x在该树线性序中的第i个后继?
先调用OS_RANK确定元素x的秩r,然后调用OS_SELECT确定秩r+i所对应的元素
1-6
(略)
1-7 说明如何在O(nlgn)时间内,利用顺序统计树对大小为n的数组中的逆序对进行计数。
- 如果有i < j,且A[ i ]>A[ j ],则称(A[ i] ,A[ j] )为数组A中的一个逆序对
- 插入过程中,假设第i个元素插入到树中,那么i的秩可以求出,也就计算出所有小于等于秩为r的元素个数,那么总共插入了i个数,i-rank(i)=所有在i之前插入但又大于i的逆序对。
- 对其进行累加,最后求逆序对和
1-8现有一个圆的n条弦,每条弦都由其端点来定义。请给出一个能在O(nlgn)时间内确定圆内相交弦对数的算法。(例如,如果n条弦都为直径,它们相交于圆心,则正确的答案为C(n,2))假设任意两条弦都不会共享端点。
思路:以圆心为坐标系。然后查看端点坐标顺序,是来查看是否相交。
如何扩张数据结构
在算法设计过程中,常常需要对基本数据扩张,以便支持一些新功能。扩张的步骤:
- 选择一种基础数据结构
- 确定基础数据结构中需要维护的附加信息。
- 检验基础数据结构上的基本修改操作能否维护附加信息。
- 设计一些新操作
以顺序统计树为例:
步骤1:
选择红黑树为基础数据结构
步骤2:
提供了size附加信息
步骤3:
保持插入和删除维护size域
步骤4:
设计了新的OS-RANK,OS-SELECT操作
练习
2-1
- MINIMUM: 一指针指向 minimum node, 如果结点被删除,则指向后继successor.
- MAXIMUM: 类似MINIMUM
- SUCCESSOR: 每个结点的后继形成链表,如果插入和删除 和链表操作类似
- PREDECESSOR: 类似SUCCESSOR
2-[2,3]
- 黑高仅取决于其子节点的黑高和颜色。定理14.1暗示我们可以在不影响其他红黑树操作的渐近性能的情况下保持该属性。
- 对于保持节点的深度,情况并非如此。如果我们删除树的根,我们可能必须更新 O(n)个节点的深度,从而使delete操作比以前渐进地慢。
2-4
和size类似, x.f=x.left.f⊗x.a⊗x.right.f.
2-5
- lgn:先调用查找函数找到a,若树中不存在a,那么找到刚好比a小的元素x
- m:然后连续调用m次查找后继函数
区间树
区间树则是对红黑树扩张以便支持由区间构成的动态集合操作,红黑树用到的关键字值是区间树的区间左端点值。以下是一个区间树及其所表示的区间:
区间树的节点还扩展了一个域max,就是以该节点为根的子树的所有区间元素的右端点的最大值。该域很容易在O(1)的时间内维护,那就是左子结点的max、右子结点的max和自身区间的右端点三者的最大值。
区间树是一种对动态集合进行维护的红黑树,该集合中的每个元素 x 都包含一个区间 x.int 。区间树支持下列操作:
- INTERVAL-INSERT(T, X) :将包含区间域 int 的元素 x 插入到区间树 T 中。
- INTERVAL-DELETE(T, x) :从区间树 T 中删除元素 x 。
- INTERVAL-SEARCH(T, i) :返回一个指向区间树 T 中元素 x 的指针,使 x.int 与 i 重叠;否则返回 T.nil 。
步骤1:基础数据结构
基础数据结构为红黑树,其中每个结点 x 包含一个区间域 x.int , x 的关键字为区间的低端点 x.int.low 。
步骤2:附加信息
每个结点还要包含一个值 x.max ,即以 x 为根的子树中所有区间的端点的最大值。
步骤3:维护信息
必须验证对含 n 个结点的区间树的插入和删除能在 O ( lg n )时间内完成。给定区间 x.int 和 x 的子结点的 max 值,可以确定 x.max = max ( x.int.high , x.left.max , x.right.max )。
步骤4:设计新操作
唯一需要的新操作是 INTERVAL-SEARCH 。
INTERVAL-SEARCH(T, i)
1 x = T.root
2 while x != T.nil and i does not overlap x.int
3 if x.left != T.nil and x.left.max >= i.low
4 x = x.left
5 else
6 x = x.right
7 return x
练习
3-1写出作用于区间树的结点且在O(1)时间内更新max属性的过程LEFT_ROTATE的伪代码。(最后两行是维护MAX属性过程)
Add 2 lines in LEFT-ROTATE in 13.2
y.max = x.max
x.max = max(x.high, x.left.max, x.right.max)
3-2 重写INTERVAL-SEARCH代码,使得当所有的区间都是开区间时,它也能正确地工作。
INTERVAL-SEARCH(T, i)
x = T.root
while x != T.nil and i does not overlap x.int
if x.left != T.nil and x.left.max > i.low
x = x.left
else x = x.right
return x
3-3
(略)
3-4给定一个区间树T和一个区间i,请描述如何能在O(min(n,klgn))时间内,列出T中所有与i重叠的区间,此处k为输出区间数。(可选:找出一种不修改树的方法。)
INTERVALS-SEARCH(T, x, i)
let list be an empty array
if i overlaps x.int
list.APPEND(x)
if x.left != T.nil and x.left.max > i.low
list = list.APPEND(INTERVALS-SEARCH(T, x.left, i))
if x.right != T.nil and x.int.low ≤ i.high and x.right.max ≥ i.low
list = list.APPEND(INTERVALS-SEARCH(T, x.right, i))
return list
3-5请说明要对前面介绍的有关区间树的过程作哪些修改,才能支持操作ITERVAL-SEARCH-EXACTLY(T,i),它返回一个指向区间树T中结点x的指针,使x.int.low=i.low, x.int.high=i.high,或当T不包含这样的结点时返回T.nil。所有操作对于n结点树(包括INTERVAL-SEARCH-EXACTLY)的运行时间应为O(lgn).
INTERVAL-SEARCH-EXACTLY(T, i)
x = T.root
while x != T.nil and i not exactly overlap x
if i.high > x.max
x = T.nil
else if i.low < x.low
x = x.left
else if i.low > x.low
x = x.right
else x = T.nil
return x
3-6 请说明如何来维护一个支持操作MIN-GAP的动态数集Q,使该操作能给出Q中最近的两个数之间的差幅。例如,如Q={1,5,9,15,18,22},则MIN-GAP(Q)返回18015=3,因为15和18为Q中最近的两数。使操作INSERT,DELETE,SEARCH和MIN-GAP尽可能高效,并分析它们的运行时间。
红黑树+后继链表的方式来存储数据,附加属性是和前驱的差值
3-7
(略)
主要参考
《算法导论读书笔记(16)》
《算法导论第十四章 数据结构的扩张》
《算法导论第十四章数据结构的扩张》
《Augmenting Data Structures》