Coursera - Algorithm (Stanford) - 课程笔记 - Week 7

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)
  • 最大值、最小值、前驱、后继
    • 最小键值
      • 从根节点开始
      • 遍历最左结点(沿左子树不断向下遍历)
      • 最左叶节点即最小键值
    • 最大键值
      • 从根节点开始
      • 遍历最右结点(沿右子树不断向下遍历)
      • 最右叶节点即最大键值
    • 前驱
      • 理想情形:当前节点左子树的最大值
      • 无左子树情形:向上遍历父节点,直到找到一个小于当前节点键值的节点(可以保证为前驱)
    • 后继
      • 理想情形:当前节点右子树的最小值
      • 无右子树情形:向上遍历,找到大于当前节点键值的节点
    • 最糟情形时间复杂度 θ ( 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=i1 x x x即目标所求
      • 如果 a ≥ i a \ge i ai,递归从左子树寻找第 i i i个节点
      • 如果 a < i − 1 a \lt i - 1 a<i1,递归从右子树寻找第 i − a − 1 i - a - 1 ia1个节点
    • 时间复杂度 θ ( 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) height2log(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)时间实现
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值