Heap & Binary Search Tree
Heap
- 支持的操作
- 堆是一个存放具有键的对象的容器,其键值可供比较
- 插入
insert
:向堆中加入一个对象- 时间复杂度: O ( log n ) O(\log n) O(logn)
- 获取最小值
extract_min
:将队中具有最小键值的对象移出- 对于相等情形则随机选择一个
- 可以类比实现一个获取最大值方法(比较时调整,插入时调整)
- 时间复杂度 O ( log n ) O(\log n) O(logn)
- 初始化
heapify
:线性时间内初始化一组对象- 时间复杂度: O ( n ) O(n) O(n)
- 删除
delete
:删除任意元素- 时间复杂度: O ( log n ) O(\log n) O(logn)
- 应用场景:排序
- 典型应用:非常开始的重复寻找最小值的数据结构
- 选择排序
- 每次选择最小值,可改进为堆排序
- 堆排序
- 插入 n n n个元素到一个堆中,每次取最小值,可以获得有序序列
- 运行时间:两组堆操作, O ( n log n ) O(n \log n) O(nlogn)
- 至少是基于比较的算法,时间复杂度不会低于 O ( n log n ) O(n \log n) O(nlogn)
- 应用场景:时间管理器
- “优先队列”
- 存放对象:事件记录——在未来的给定事件被激活发生
- 键:事件发生的规划时间
- 应用场景:中值维护
- 逐个输入一个序列,维护序列的中值
- 两个堆
- 大顶堆:存放较小的一半元素
- 小顶堆:存放较大的一半元素
- 中位数的计算只取决于大顶堆的最大值以及小顶堆的最小值
- 插入新元素时,同样也跟这两个元素比较,判断新元素处于哪一半
- 当插入元素使得其中一个堆的元素个数大于另外一个堆两个,需要调整,只需要从多的堆拿出一个元素送入少的堆即可
- 应用场景:加速Dijkstra算法
- 从 O ( m n ) O(mn) O(mn)加速到 O ( m log n ) O(m \log n) O(mlogn)
- 堆的性质
- 尽可能完全的二叉树
- 对于任意结点,其键值必不超过(小顶堆)其子节点的键值
- (小顶堆)根节点必然时整组数据的最小值
- 实现:数组(相比于树更加高效)
- 按树的层级逐层将数据放入到数组中
- 由于树时尽可能完全的,确定子节点和父节点变得很容易(不再需要指针)
- 对结点 i i i,其父节点为 ⌊ i / 2 ⌋ \lfloor i / 2 \rfloor ⌊i/2⌋,其子节点为 2 i 2i 2i和 2 i + 1 2i + 1 2i+1
- 不需要遍历,也不再需要指针
- 插入新对象
- 数组中第一个非空位置插入
- 向上检查合法性(向上冒泡)
- 时间复杂度 O ( log n ) O(\log n) O(logn)
- 提取最小值
- 删去根节点(第一位)对象,该对象即为当前最小值(小顶堆)
- 将最后一个结点移到根节点位置
- 向下检查合法性(向下冒泡,选择满足要求的较小键值的结点进行交换)
- 时间复杂度 O ( log n ) O(\log n) O(logn)
BST
- 有序数组支持的操作
- 搜索
search
:二分搜索,时间复杂度 O ( log n ) O(\log n) O(logn) - 选择
get(i)
:选择给定的第 i i i个元素,时间复杂度 O ( 1 ) O(1) O(1) - 最大
max
/ 最小min
:选择最大值或者最小值,时间复杂度 O ( 1 ) O(1) O(1) - 前驱查询
pred
/ 后继查询succ
:选择当前元素的前驱或者后继,时间复杂度 O ( 1 ) O(1) O(1) - 秩
rank
:不大于给定值的元素个数,实际就是确定给定元素的位置,时间复杂度 O ( log n ) O(\log n) O(logn) - 按序输出
output
:时间复杂度 O ( n ) O(n) O(n) - 插入和删除:会很慢!
- 搜索
- BST:类似有序数组(有妥协),同时保证快速的插入和删除
- 搜索
search
:二分搜索,时间复杂度 O ( log n ) O(\log n) O(logn) - 选择
get(i)
:选择给定的第 i i i个元素,时间复杂度 O ( log n ) O(\log n) O(logn) - 最大
max
/ 最小min
:选择最大值或者最小值,时间复杂度 O ( log n ) O(\log n) O(logn) - 前驱查询
pred
/ 后继查询succ
:选择当前元素的前驱或者后继,时间复杂度 O ( log n ) O(\log n) O(logn) - 秩
rank
:不大于给定值的元素个数,实际就是确定给定元素的位置,时间复杂度 O ( log n ) O(\log n) O(logn) - 按序输出
output
:时间复杂度 O ( n ) O(n) O(n) - 插入
insert
:时间复杂度 O ( log n ) O(\log n) O(logn) - 删除
delete
:时间复杂度 O ( log n ) O(\log n) O(logn)
- 搜索
- 二叉搜索树
- 每一个键值对应树中的一个结点
- 每个结点包含三个指针
- 左子节点指针
- 右子节点指针
- 父节点指针
- 搜索树性质:对任一结点,其左子树所有结点的键值都小于该节点键值,右子树所有结点键值都大于该节点键值
- BST的高度:同一组数据可以构造很多不同的BST,其高度(根节点到最深的叶子节点的路径长度)可以从 O ( log n ) O(\log n) O(logn)到 n n n,前者为完全平衡情形
- 搜索和插入
- 与
T
T
T中寻找键值
k
k
k
- 从根节点开始
- 按需向当前结点的左子树( k k k更小)或右子树( k k k更大)遍历
- 返回对应结点(找到)或空指针(不存在)
- 插入键值
k
k
k(考虑无重复元素情形)
- 在树种搜索键值 k k k
- 将返回的空指针重写为装在当前键值的结点
- 如果存在相同元素,可以保持一个惯例,将相等元素统一置于一侧子树中
- 最糟情形时间复杂度 θ ( h e i g h t ) \theta(height) θ(height)
- 与
T
T
T中寻找键值
k
k
k
- 最大值、最小值、前驱、后继
- 最小键值
- 从根节点开始
- 遍历最左结点(沿左子树不断向下遍历)
- 最左叶节点即最小键值
- 最大键值
- 从根节点开始
- 遍历最右结点(沿右子树不断向下遍历)
- 最右叶节点即最大键值
- 前驱
- 理想情形:当前节点左子树的最大值
- 无左子树情形:向上遍历父节点,直到找到一个小于当前节点键值的节点(可以保证为前驱)
- 后继
- 理想情形:当前节点右子树的最小值
- 无右子树情形:向上遍历,找到大于当前节点键值的节点
- 最糟情形时间复杂度 θ ( h e i g h t ) \theta(height) θ(height)
- 最小键值
- 先序遍历:输出升序数列
- 先向左子树遍历
- 遍历当前节点
- 再向右子树遍历
- 时间复杂度 O ( log n ) O(\log n) O(logn)
- 删除
- 搜索需要删除的节点 k k k
- 无子树情形:直接删除即可
- 一个子树情形:删除节点后,直接用子树的连接节点(子树中与当前节点直接相连的节点)代替之
- 两个子树情形
- 计算 k k k的前驱 l l l(存在左子树情形,因此往左子树考察)
- 交换 k k k和 l l l
- k k k的新位置必然没有右子树,使用上述两种情形删除 k k k
- 时间复杂度 θ ( h e i g h t ) \theta(height) θ(height)
- 选择和秩
- 解决方案:每个节点额外存储一个有关树结构的信息
- 额外存储信息:以当前节点为根节点的子树的节点个数
- size ( x ) = size ( l x ) + size ( r x ) + 1 \operatorname{size}(x) = \operatorname{size}(l_x) + \operatorname{size}(r_x) + 1 size(x)=size(lx)+size(rx)+1
- 在调整树结构时(插入、删除),需要同时维护该信息
- 选择:返回第
i
i
i个节点
- 从根节点 x x x开始,子树分别为 y y y和 z z z
- 令 a = size ( y ) a = \operatorname{size}(y) a=size(y)
- 如果 a = i − 1 a = i - 1 a=i−1, x x x即目标所求
- 如果 a ≥ i a \ge i a≥i,递归从左子树寻找第 i i i个节点
- 如果 a < i − 1 a \lt i - 1 a<i−1,递归从右子树寻找第 i − a − 1 i - a - 1 i−a−1个节点
- 时间复杂度 θ ( h e i g h t ) \theta(height) θ(height)
- 秩寻找过程类似
- 平衡二叉搜索树
- 确保树的高度始终保持 O ( log n ) O(\log n) O(logn)
- 红黑树是一类著名的平衡BST
- 任一平衡二叉树(不是所有二叉树)都可以构造一个红黑树
- 红黑树
- 不变量
- 额外信息存储:节点是红还是黑
- 根节点是黑节点
- 不存在两个红节点相连
- 每一条从根节点到空节点的路径上都有相同数量的黑节点
- 高度保证:任一拥有 n n n个节点的红黑树的高度 h e i g h t ≤ 2 log ( n + 1 ) height \le 2 \log (n + 1) height≤2log(n+1)
- 高度保证确保了所有操作在对数时间内完成
- 旋转过程
- 目标:局部调整以重新平衡整棵树
- 左旋转:
- 父节点 x x x拥有一个右子节点 y y y,父节点有左子树 A A A,子节点有两个子树 B B B和 C C C
- y y y成为父节点(调整 x x x父节点的链接), x x x成为其左节点, A A A和 C C C的链接不变, B B B调整为 x x x的右子树
- 右旋转:对应左旋转
- 优势:常数时间实现、保证树的平衡性质
- 不变量
- 红黑树的插入
- 按照BST的思路插入,然后重新着色并进行旋转以确保树的平衡性
- 首先按照BST的思路将 x x x插入(应该是一个叶节点)
- 尝试将 x x x作为红节点
- 如果其父节点 y y y为黑节点,插入完成
- 否则, y y y的父节点 w w w必然是黑节点
- 情形1:
w
w
w的另外一个子节点
z
z
z存在时红节点
- y y y和 z z z变为黑节点, w w w变为红节点
- 向上检查是否违反双红节点的情形
- 如果根节点因向上传播被变为红节点,直接变为黑节点即可(所有路径上黑节点个数加1)
- 时间复杂度 O ( log n ) O(\log n) O(logn)
- 情形2:
z
z
z为黑节点或者不存在
- 注意,情形2不仅在插入时直接触发,可能在向上检查时某个位置出发
- O ( 1 ) O(1) O(1)时间实现