(a2)基本实现

官方信息技术老师·17 页·深入(追求细节与边界)·2 次浏览·1 天前
BBST复杂度权衡

优先级队列的基本实现

看穿两种底层结构如何撑起优先级队列的操作与权衡

按 空格/→ 演示下一步

1 / 17 页

全部页面点击任意一页,跳回舞台从这页播放

BBST复杂度权衡

优先级队列的基本实现

看穿两种底层结构如何撑起优先级队列的操作与权衡

1第 1 页 · 优先级队列的基本实现

向量的结构与特性

上页选定了数组作为优先级队列的底层容器,但这只是表层决定。这一页拆开向量看:它的结构与特性到底是什么,又凭什么能撑起入队与出队的需求?

连续内存布局
元素首尾相接存放,按下标即可 O(1) 直接定位
动态扩容策略
容量满时按倍数申请新空间并整体搬迁,单次均摊 O(1)
尾部插入 O(1)
push_back 无需挪动已有元素,正好对应入队
中间删除 O(n)
取出极值后,后续元素必须前移填补空位
电影院一排连号座位对应 →向量的连续存储

凭票号能直接定位,但中间有人离场,后面整排要往前挪一格

2第 2 页 · 向量的结构与特性

向量的入队操作

入队两步走:先追加到末尾,再向上修复堆序。

1
末尾追加
push_back 把新元素写到数组尾部,均摊 O(1)
2
容量检查
容量不足时按 2 倍扩容,拷贝旧数据到新空间
3
向上比较
计算父节点下标并比较,违反堆序则交换两者
4
循环上浮
重复比较直至到达根或堆序恢复,最坏 O(log n)
3第 3 页 · 向量的入队操作

向量的出队操作

出队的核心:先线性扫一遍找最大值,再用末尾覆盖加 pop_back 完成删除。

1
线性扫描
从头遍历每个元素,逐个比较找出最大值
2
记录下标
用变量记下最大值所在的位置
3
末尾覆盖
把向量末尾元素挪到最大值位置,避免逐个前移
4
弹出末尾
将 size 减一或 pop_back,完成删除
4第 4 页 · 向量的出队操作

向量实现的性能瓶颈

看上下两条路径的对比:入队一步到位,出队却要扫描加移动两段代价,这就是瓶颈所在。

图解渲染中…
b2线性遍历所有元素比较大小,时间 O(n)b4向量删除中间位置后,后续元素整体前移填补
5第 5 页 · 向量实现的性能瓶颈

有序向量的概念

上一页我们看到,无序向量出队要遍历找最值,O(n) 是它的瓶颈。一个自然的想法是:干脆让元素一开始就按优先级排好——这就是有序向量的核心思路。

严格有序排列
元素按优先级升序或降序排列,相邻元素可比较
不变量维持
每次入队/出队后,有序性依然成立
入队 O(n)
顺序比较定位插入点,平均移动半数元素腾位
出队 O(1)
极值元素恒在端点,直接摘取即可
期末成绩排名表对应 →有序向量

第一名永远在最前/最后,对应极值元素固定在端点

Tinsert(n)=O(n),TdeleteMax(n)=O(1)T_{\text{insert}}(n) = O(n), \quad T_{\text{deleteMax}}(n) = O(1)
6第 6 页 · 有序向量的概念

有序向量的入队操作

有序向量的入队,核心难点在于既要找对位置,又要给新元素让路。

1
二分定位插入点
用二分查找在有序向量中确定新元素该放的位置
2
搬移元素腾位
插入点之后的所有元素向后平移一位,腾出空位
3
写入新元素
把新元素放入腾出来的空位,完成入队
4
总复杂度O(n)
二分查找O(log n)被搬移O(n)主导,整体为O(n)
7第 7 页 · 有序向量的入队操作

有序向量的出队操作

出队只需定位末尾、取值、弹出三步,全程 O(1)。

1
定位最高优先级
有序性保证最大元素固定在末尾,无需遍历查找
2
直接访问末尾
通过下标随机访问该元素,时间复杂度与规模无关
3
尾部弹出返回
pop_back 操作,不需前移其余元素,O(1) 完成
8第 8 页 · 有序向量的出队操作

无序向量 vs 有序向量

「无序」与「有序」看似只是排列差异,但入队与出队的复杂度恰好此消彼长——这是两种向量实现最关键的分水岭。

无序向量
  • 入队 O(1):直接追加到表尾
  • 出队 O(n):遍历全表寻找极值
  • 适合入队远多于出队的场景
有序向量
  • 入队 O(n):需移动元素腾出位置
  • 出队 O(1):直接取表首或表尾
  • 适合出队远多于入队的场景
入队为主选无序(O(1) 入队),出队为主选有序(O(1) 出队);两者的 O(n) 发生在不同操作上,无法互相抵消。
9第 9 页 · 无序向量 vs 有序向量

BBST的基本概念

有序向量查找快,但插入要搬动大量元素,O(n) 的代价让人头疼。有没有一种结构,既能像二分查找一样快定位,又不用为新元素挪位置?平衡二叉搜索树就是答案。

平衡条件
左右子树高度差不超过1(AVL标准),失衡立即调整
高度上界
n个节点时树高严格控制在O(log n),不会退化成链表
旋转修复
通过单旋或双旋在局部恢复平衡,不破坏BST的有序性
综合性能
查找、插入、删除全部O(log n),无明显短板
跷跷板两端对应 →BBST左右子树

跷跷板两端高度差要受控;对应BBST左右子树高度差不能超限

hLhR1\left|h_L - h_R\right| \leq 1
10第 10 页 · BBST的基本概念

BBST的结构与优先级队列的映射

元素入队后映射为BBST节点,中序遍历即得有序出队序列

图解渲染中…
b1每个队列元素对应BBST的一个树节点c1BST左子必小于根节点,最小值必在最左c2BST右子必大于根节点,最大值必在最右e1出队只需沿左链走到尽头并删除该节点
11第 11 页 · BBST的结构与优先级队列的映射

BBST的入队操作

BBST入队 = BST插入 + 失衡修复,每一步都为维护对数级复杂度。

1
查找插入位置
沿BST路径向下比较大小,直到找到空位
2
插入新节点
新节点作为叶节点挂入树中
3
回溯更新高度
从插入点向上逐层更新祖先节点的高度
4
检测失衡节点
检查路径上每个祖先的平衡因子是否越界
5
旋转修复
按失衡类型做单旋或双旋,恢复平衡
12第 12 页 · BBST的入队操作

BBST的出队操作

从BBST中取出最大或最小节点,需要四步配合。

1
寻找极值节点
沿最大或最小方向一路向下到叶端
2
记录极值
保存待返回的节点值
3
删除节点
物理移除该叶子节点的位置
4
重新平衡
通过旋转等操作恢复平衡性
13第 13 页 · BBST的出队操作

三种实现的性能总览

从优先级队列出发,三条分支对应三种实现的入队与出队复杂度。

图解渲染中…
v1a尾部追加,最廉价v1b需遍历全表找最值v2a插入位置后整体后移v2b最值固定在某一端
14第 14 页 · 三种实现的性能总览

实现选择的原则

前页列了三种实现在各操作上的复杂度差异,但这只能告诉你『它们各自多快』,不能告诉你『该选谁』。选择不是查表,而是看清这队列的用途。

操作模式
频繁入队还是频繁取极值,决定偏向有序结构还是无序结构
数据规模
N 较小时常数因子压过复杂度差异,几种实现都跑得快
附加操作
是否还要『按序遍历』『快速定位任意元素』等超出 PQ 的能力
权衡代价
BBST 节点内存大、平衡维护复杂;向量结构紧凑、实现最简单
选交通工具对应 →选底层结构

看距离、负重、时效综合决定,对应看操作、规模、附加需求

15第 15 页 · 实现选择的原则

基本实现要点回顾

  • 数据结构没有「谁最强」,只有「谁最贴你场景」
  • 选之前先问:插入与删除,哪个更常发生
  • BBST 看似两全,是把成本塞进了平衡维护里
  • 复杂度是底线,但代码简洁度同样值得权衡
延伸主题:堆:兼顾效率与实现简洁BBST 的平衡策略:AVL 与红黑树
16第 16 页 · 基本实现要点回顾

课后思考

三个开放性问题引导深入琢磨

课后思考
三个开放性问题引导深入琢磨
17第 17 页 · 课后思考