目录
二叉搜索树
结构特征
搜索
插入
删除
单子节点删除
双子节点删除
平衡二叉搜索树
AVL树
失衡与重平衡
插入失衡
删除失衡
“3+4”平衡重构
伸展树
逐层伸展
双层伸展
插入
删除
红黑树
结构特征
插入
自底向上的染色插入
双红修正
RR-1
RR-2
自顶向下的翻转插入
删除
自底向上的染色删除
双黑修正
自顶向下的翻转删除
B树
结构特征
查找
插入
节点上溢
删除
二叉搜索树
结构特征
任一节点x的左/右子树中,所有非空节点均不大于(不小于)x
- 必须是所有的非空节点,仅左右孩子不够(左孩子的右孩子可能很大)
- 一棵二叉树是二叉搜索树当且仅当中序遍历序列是单调非降序列
两棵二叉搜索树等价当且仅当他们有相同的中序遍历序列(上下可变,左右不乱)
- 换言之,构成两棵二叉搜索树的元素相同
等价变换zig、zag
- zig:右单旋转
- zag:左单旋转
变换后仍保持二叉搜索树的性质
(《算法导论》练习13.2-2)
度为2的节点有2种转法,度为1的节点有1种转法,从而每种旋转对应一条边,共n-1条边。
(《算法导论》练习13.2-4)
对于任何含n个结点的二叉搜索树,若某节点有左孩子,就右旋,如此会消除一个左孩子-父节点关系,而最多只有n-1个上述的左孩子-父节点关系,从而经至多n-1次旋转就能将其变为一条右链,而左右旋都是可逆的,转变只需要以该右链作为中介。
搜索
中序遍历操作
内部变量_hot指向搜索的终止位置的父节点
- 如果命中,就是目标节点的父节点
- 如果未命中,就是目标节点如果存在时的父节点
API返回搜索的终止位置
- 如果命中,就是目标节点
- 如果未命中,就是_hot的子哨兵节点
时间复杂度O(h)
插入
先搜索,让_hot指向将增加孩子的节点,再添加子节点
从插入的节点开始,向上更新节点高度
时间复杂度O(h)
删除
单子节点删除
直接把删除节点换成其以子唯一节点为根的子树
删除时利用搜索接口确定节点位置的过程给出当前_hot,它是向上更新节点高度的起点
双子节点删除
用在右子树中的直接后继替换删除节点,原来直接后继是度不为2的节点,化为单子节点删除
_hot设为原来直接后继的父节点,它是向上更新节点高度的起点
/******************************************************************************************
* BST节点删除算法:初除位置x所指癿节点(全局静态模板函数,适用亍AVL、Splay、RedBlack等各种BST)
* 目标x在此前经查找定位,并确认非NULL,故必删除成功;与searchIn不同,调用之前不必将hot置空
* 返回值指向实际被删除节点的接替者,hot指向实际被删除节点的父亲——二者均有可能是NULL
******************************************************************************************/
template <typename T>
static BinNodePosi(T) removeAt (BinNodePosi(T)& x, BinNodePosi(T)& hot) {BinNodePosi(T) w = x; //实际被摘除的节点,初值同xBinNodePosi(T) succ = NULL; //实际被删除节点的接替者if (!HasLChild(*x)) { //若*x的左子树为空,则可succ = x = x->rc; //直接将*x替换为其右子树}else if (!HasRChild(*x)){ //若右子树为空,则可succ = x = x->lc; //对称地处理——注意:此时succ != NULL}else { //若左右子树均存在,则选择x的直接后继作为实际被摘除节点,为此需要w = w->succ(); //(在右子树中)找到*x的直接后继*wswap(x->data, w->data); //交换*x和*w的数据元素BinNodePosi(T) u = w->parent;succ = w->rc; //w一定无左孩子,化为单节点的仅有右孩子情形((u == x) ? u->rc : u->lc) = succ;//如果u是x,即x是w的父节点,此时w在u的右子树中//若不然,因w是x的直接后继,此时w在u的左子树中}hot = w->parent; //记录实际被删除节点的父亲if (succ) {succ->parent = hot; //并将被删除节点的接替者与hot相联}release(w->data);release(w);return succ; //释放被摘除节点,返回接替者
} //release()负责释放复杂结构,与算法无直接关系,见代码包
时间复杂度O(h)
【2014-THU-Fin】由同一组共n个词条构成的任意两棵BST,经O(logn)次zig和zag旋转之后,必可相互转换。(×)
【2016-THU-Fin】在BST中查找365,以下查找序列中不可能出现的是()
A. 912,204,911,265,344,380,365
B. 89,768,456,372,326,378,365
C. 48,260,570,302,340,380,361,365
D. 726,521,201,328,384,319,365
【2016-THU-Fin】在不改变 BST 和 BinNode 定义的前提下(BinNode 仅存储 parent, data, lc, rc), 设计算法, 使得从节点 𝑥 出发, 查找值为 𝑌 的节点 𝑦 的时间复杂度为 𝑜(𝑑), 𝑑 为节点 𝑥与 𝑦 的距离。要求利用树的局部性, 复杂度与总树高无关, 否则将不能按满分起评。
函数定义式: 参量为 BinNode 𝑥, 𝑦, 𝑇, 返回值为 BinNode 类型, 函数名 fingerSearch
(a) 说明算法思路
(b) 写出伪代码
(c) 在图中画出由值为 6 的点查找值为 17 的点的查找路径
(d) 说明算法时间复杂度为 𝑂(𝑑) (若无法达到, 说明困难在哪)
【2016,2017-THU-Fin】由5个互异节点构成的不同的BST共有()个。
A. 24
B. 30
C. 36
D. 42
E. 120
平衡二叉搜索树
理想平衡树:n个节点,树高为⌊log_2n⌋的二叉树
适度平衡:n个节点,树高为渐进O(logn)的二叉树
- 经过单次修改操作,最多只有O(logn)处不再满足适度平衡性条件
- 可在O(logn)时间内,使这些不适度平衡处重新适度平衡
【2013-THU-Fin】两棵key值顺序一样的BBST经过O(logn)次zig、zag就能互相转化。()
AVL树
节点v的平衡因子balFac(v) = height(lc(v)) - height(rc(v))
AVL条件:AVL树中所有节点满足|balFac(v)| <= 1
高度为h的AVL树至少含fib(h+3)-1个节点,进而n个节点的AVL树树高是O(logn)的。
失衡与重平衡
记UT(x)是因对节点x的操作而不满足AVL条件的节点集,下假设调整前UT(x)非空
插入失衡
UT(x)中的元素都是x的祖先,其不低于x的祖父节点,且可能一直失衡到根节点
重平衡自下而上逐个修正
右旋转
左旋转
左-右旋转
右-左旋转
- 如果节点g的X孩子的Y子树插入导致的失衡
- X=Y,在g做X旋转
- X!=Y,先在X孩子做X旋转,再在g做Y旋转
- 如果插入导致了旋转调整,那么本次插入不改变树高
时间复杂度O(logn)
- 每种旋转都是就地O(1)时间复杂度算法,每次将消除一个节点的失衡
- AVL树树高是O(logn)的,即最多O(logn)次旋转
删除失衡
UT(x)只有1个节点,但可能出现节点的替换(自下而上的失衡传播);任何进入UT(x)的节点失衡前后高度不变(要是失衡了,删除部分来自更低的部分,但高度取决于更高的子树)
删除导致的旋转调整不保证不改变树高,树高可能降低
时间复杂度O(logn)
“3+4”平衡重构
单次重构为就地O(1)时间复杂度算法(不计更新高度)
【2012-THU-Fin】将[1481,1992]区间内的整数逐一插入到空AVL树中,最后该AVL树的高度是(CD)
A. 7
B. 8
C. 9
D. 10
E. 以上都不对
共512=2^9个元素,至少为9。fib(13)-1=232,也可能是10。
【2013-THU-Fin】(依次)将 0, ..., 2^d−1插入(到初始为空的)AVL一定高度为d(括号部分由新威考研补全)()
【2014-THU-Fin】设在某新节点插入AVL树后(尚待平衡化时),最低失衡节点为g。若此时g的左、右孩子的平衡因子分别为-1和0,则应通过(C)旋转使之重新恢复平衡。
A. zig
B. zig+zag
C. zag+zig
D. zag
E. 不确定
【2016-THU-Fin】若AVL树插入元素的过程中发生了旋转操作,则树高必不变。(√)
【2016-THU-Fin】如果元素理想随机,那么对二叉搜索树做平衡化处理,对改进其渐进时间复杂度并没有什么实质的作用。(×)
伸展树
利用程序局部性加速
逐层伸展
每当访问一个节点,反复使用左旋右旋将其提升至根节点
分摊复杂度\omega(n)
双层伸展
每当访问一个节点,以两层为单位提升目标节点
- 两次相反旋转:与逐层伸展相同
- 两次相同旋转:该路径深度折半(核心)
最后一次可能只有单旋转,不影响整体性能
分摊复杂度O(logn)(习题解析8-2)
- 最坏情况不会持续发生,这使得分摊的复杂度优于逐层伸展树
- 一旦发生最坏情况,该条路径立刻折半
- 不能杜绝最坏情况
- 无论采用单层伸展还是双层伸展,连续按顺序访问节点,都能将伸展树变为一条单链
- 此时访问最低节点即为最坏情形,但这种情形最多发生一次
- 如果k个访问位置高度集中,分摊复杂度为O(logk)
插入
- 调用BST::search(),确定插入位置,以_hot指示父节点
- 将_hot所指节点提升为根
- 将两棵子树分裂,与x拼接为新树
删除
- 调用BST::search(),确定删除节点
- 将删除节点x提升至根并删除,两棵子树分裂
- 选择x的前驱或后继作为新的根节点
- 选前驱,则在左子树中BST::search(x)
- 选后驱,则在右子树中BST::search(x)
【2013-THU-Fin】将2014个数插入splay,第一次访问经过2013次旋转,则是单调插入的。()
【2014-THU-Fin】即便访问序列不满足局部性(比如完全理想的随机),伸展树依然能保证分摊O(logn)的性能。(√)
【2016-THU-Fin】在任何情况下,伸展树总能保持每次操作𝑂(log 𝑛)的平均复杂度。()
红黑树
结构特征
视哨兵节点为外部节点与叶节点,所有带关键字的节点都是内部节点
- 每个节点要么是红色的,要么是黑色的
- 根节点是黑色的
- 每个叶节点都是黑色的
- 任何红节点的两个孩子都是黑节点(进而红节点的父亲也是红结点)
- 任一节点到其每个后代叶节点的路径上有相同数量的黑节点
黑高bh(x):从节点x出发到其叶节点路径上经过的黑节点数(不含叶节点)
- 任何叶节点的黑高都为0
- 红黑树的黑高等于根节点的黑高
对于有n个内部节点的红黑树,其树高h满足
(《算法导论》练习13.1-4)
(《算法导论》练习13.1-5)
(《算法导论》练习13.1-6)
(《算法导论》练习13.1-7)
最大时红黑节点在任意一条路径上交替出现,亦即从黑色根节点开始颜色交替向下染色,如果该树是单链且n是偶数,此时比值最大,为1
最小时红黑树中没有红色节点,比值为0
插入
假设插入节点x,将x染为红色
可能出现一个红节点有红色子节点问题,违反性质4
自底向上的染色插入
执行BST::insert()
- 其父节点是黑色,红黑树性质保持,结束
- 其父节点是红色
- 其祖父节点必为黑色(否则插入前不是合法红黑树)
- 考虑其父节点的兄弟节点颜色
双红修正
RR-1
如果x叔节点为黑,执行3+4重构,将局部根节点变黑
- 不改变任何节点的黑高
RR-2
如果x叔节点为红,将x祖父节点染为红
- x的祖父节点的黑高+1,其他所有节点均不变
- 如果x的祖父节点的父节点也是红色的,选择RR-1或RR-2继续修正,此时需修正节点上移2层
- 因为黑节点可以出现在任意位置,最终将可能被染红的根节点再染黑即可
- 此时树黑高+1
时间复杂度O(logn)
- 最好情况:一次RR-1完成修正,O(1)
- 最差情况:一直执行RR-2直到根节点,O(logn)
自顶向下的翻转插入
BST::insert()中search过程内嵌动作,对红黑树重染色
- 如果某黑节点有两个红孩子,就做一次翻转
- 如果X父节点是黑色,无事发生
- 如果X父节点是红色,那么其叔节点必为黑
- 如果叔节点是红色,那之前就会做一次翻转,使叔节点和父节点都会变成黑色
- 做一次RR-1修正
- 找到插入位置
- 如果父节点是黑色,直接插入
- 如果父节点是红色,那么其叔节点必为黑
- 做一次RR-1修正
排除了RR-2的情形,只需要执行RR-1即可
(《算法导论》练习13.3-1)
将出现一条有更多黑色节点的路径,z以上的祖先节点的性质5都会被破坏
(《算法导论》练习13.3-6)
在search时将x的诸祖先节点压栈,在自底向上染色中逐步出栈,或采用自顶向下的插入
删除
假设删除节点x
可能出现局部子树黑高下降问题,局部子树的各祖先节点不满足性质5
BST::succ(x)获取x的直接后继r
- x与r颜色不同
- 直接交换数据删除物理r节点,与普通二叉树无区别
- x与r颜色相同
自底向上的染色删除
双黑修正
自顶向下的翻转删除
【2012-THU-Fin】对红黑树进行插入操作时,进行双红修正,黑高度增加,则()发生重染色,()发生结构调整。
A. 必然,必然
B. 必然,可能
C. 必然,必然不
D. 可能,必然
E. 可能,可能
F. 可能,必然不
【2013-THU-Fin】(在初始为空的红黑树中)依次插入(关键码)[0, N)(括号部分由新威考研添加)
(1)写出N = 9的红黑树
(2)写出树高 H 和 N 的通项公式
【2016-THU-Fin】若红黑树插入一个元素后,黑高度增加,则双红修正过程中没有拓扑结构变换,只有重染色操作。()
B树
m阶B树:m路平衡搜索树
针对I/O的数据结构
- 活跃的B树根节点常驻内存中
- 只有一个节点能够留在内存中
- 出去一个进来一个的替换检索
结构特征
- 上界限制:每个内部节点存有n-1个关键码
- 下界限制:每个内部节点有n个分支
n不大于m,不小于⌈m/2⌉,即至少是半满的
- m阶B树=(⌈m/2⌉,m)-树=⌈m/2⌉-⌈m/2⌉+1-...-m树
- 每个内部节点关键码数应介于⌊m/2⌋和m-1之间
- 每个内部节点引用数应介于⌈m/2⌉和m之间
对于根节点,没有下界限制
如果记其最小度数(最小分支数)为t,t=⌈m/2⌉,那么m=2t-1
其树高需要加上外部哨兵节点高度,即外部节点高度为0
树高为h的m阶B树存储N个关键词,那么,即h=\Theta(\log_mN)
(《算法导论》练习18.1-1)
除根节点以外,每个内部节点至少有t-1个关键词,t=1时不是良定义的
(《算法导论》练习18.1-4)
查找
与BST::search()类似
节点内关键词查找采用顺序查找(无需二分)
- 查找成功,返回目标关键码所在内部节点,_hot指向其父节点
- 查找失败,返回终点处外部节点,_hot指向其父节点
时间复杂度O(\log_mN)
插入
调用BTree::search(),_hot指向即为插入位置所在内部节点
在节点内再Vector::search()一次即得确切位置
节点上溢
因插入关键词导致的内部节点关键词数量为m
取该节点内第⌈m/2⌉个关键词x提升至父节点
- 从而父节点内关键词数量+1,对应引用数要+1
- 父节点也可能关键词数量为m,上溢向上传播
- 这个传播最多传播到根节点
- 如果根节点也发生了上溢,由于根节点关键词数量没有下界限制,直接上溢出一个新的根节点即可
- 此时B树高+1
- B树增高的唯一可能
- 以x为界的原内部节点分裂为两个内部节点
- 现在两个内部节点最少有⌊(m-1)/2⌋个关键词,是合法的m阶B树节点
- x两侧的引用分别划分给两个子节点
时间复杂度O(\log_mN)
删除
调用BTree::search(),得到节点位置x后获取其直接前驱BinTree::pred(x)或直接后继BinTree::succ(x),记为y
- 如果获取直接前驱,那么y所在内部节点是外部节点的父节点,且y在内部节点的最高Rank位置
- 如果获取直接后继,那么y所在内部节点是外部节点的父节点,且y在内部节点的最低Rank位置
- 交换数据后删除y
节点下溢
删除y的动作可能导致原y所在内部节点T关键码数量小于⌊m/2⌋
T一定有左兄弟或右兄弟(因最小度数t不能为1)
- 如果T的左兄弟L关键码富余(多于⌊m/2⌋个),从其父节点P那要一个,P再找L要一个
- 如果T的右兄弟R关键码富余(多于⌊m/2⌋个),从其父节点P那要一个,P再找R要一个
- 如果T的左右兄弟都离下溢不远了(刚好⌊m/2⌋个)
- 选择一个存在的兄弟
- 把他们之间的父节点关键词p拉下来和他们一辈
- 保持顺序性
- 如果找的左兄弟,就按L-p-T顺序组成一个新的内部节点
- 如果找的右兄弟,就按T-p-R顺序组成一个新的内部节点
- 这样P的关键词-1,引用也-1
- 进而P可能继续下溢,从而可能会传播至根节点
- 如果根节点关键词数量大于1,直接分一个给内部子节点
- 如果根节点关键词数量为1,把该关键词分出去
- 此时只有两个分支,用这个关键词将两个内部节点连接
- 原根节点里空了,删去空壳
- 此时B树高-1
- B树降低的唯一可能
- 进而P可能继续下溢,从而可能会传播至根节点
时间复杂度O(\log_mN)
【2012-THU-Fin】将[23,1481)区间内的整数组成一个2-3-B树,且根节点只有一个关键码,则最终该B树的高度至少是()
A. 7
B. 8
C. 9
D. 以上都不对
【2014-THU-Fin】Btree::solveOverflow()和 Btree::solveUnderflow()在最坏情况下均需Ω(logn)时间。然而在 B-树任一足够长的生命期内,就分摊意义而言二者都仅需O(1)时间。()
【2016-THU-Fin】将𝑁个关键码按随机次序插入B树,则期望的分裂次数为𝑂 (log^2𝑁)。()
【2016-THU-Fin】以下数据结构,在插入元素后可能导致𝑂(log 𝑛)次局部结构调整的是()
A. AVL
B. B-树
C. 红黑树
D. 伸展树
E. 以上皆非
【2016-THU-Fin】人类拥有的数字化数据数量,在2010年已达到ZB (2^70 = 10^21) 量级。若每个字节自成一个关键码,用一棵16阶B-树存放,则可能的高度为()
A. 10
B. 20
C. 40
D. 80
E. 大于80
【2017-THU-Fin】B树非叶节点的元素都有后继元素,且都在叶子节点中。()
【2012,2017-THU-Fin】对以下各种搜索树进行删除操作,哪些树可能会经过Ω(logn)次局部调整,其中n为关键码的数量。()
A. AVL
B. 伸展树
C. 红黑树
D. B-树
E. 都不会
【2017-THU-Fin】一个初始时只有一个内部节点的10阶B树,经过9900次分裂和7888次合并,问可能的节点数在()附近
A. 2013
B. 2015
C. 2017
D. 2018