(a1)伸展树:逐层伸展

官方信息技术老师·10 页·深入(追求细节与边界)·0 次浏览·3 天前
伸展树逐层旋转均摊分析自调整结构

(a1)伸展树:逐层伸展

看清 zig / zig-zig / zig-zag 三态旋转,掌握均摊 O(log n) 的势能证明

按 空格/→ 演示下一步

1 / 10 页

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

伸展树逐层旋转均摊分析自调整结构

(a1)伸展树:逐层伸展

看清 zig / zig-zig / zig-zag 三态旋转,掌握均摊 O(log n) 的势能证明

1第 1 页 · (a1)伸展树:逐层伸展

宽松平衡

逐层伸展把目标节点搬到根后,树不会变成 AVL 那种严格平衡,而是处于一种「松但不散」的状态——这就是宽松平衡。它保证树高仍是 O(log n),但允许更大的形变。

核心定义
沿任一路径向下,每下一层子树规模至少按常数比例(如 2/3)缩小
与严格平衡的区别
不要求左右子树大小相近,只看沿路径的单调收缩
为何足以
沿路径每层缩常数倍 ⇒ 树高被压在 O(log n)
摊还视角
Access Lemma:连续 m 次访问总代价 O((m+n) log n)
典型应用
伸展树、LCT(链接-切割树)等动态树结构
公司组织架构对应 →宽松平衡

每下一级人数至少按常数比缩小,层级总数被锁死在 log n

size(child)23size(parent)\text{size}(\text{child}) \le \frac{2}{3} \cdot \text{size}(\text{parent})
2第 2 页 · 宽松平衡

局部性

上页说伸展树是宽松平衡——不严格控树高,只把刚访问的节点推到根。凭什么是好策略?这依赖一个现实规律:几乎所有程序都有局部性。它是伸展树摊还分析的前提,也是与严格平衡树的分水岭。

时间局部性
刚访问过的 key 在短时间内被再次访问的概率更高
空间局部性
被访问 key 附近的 key 也有较大概率被访问,如顺序扫描
摊还前提
局部性存在时均摊 O(log n);病态序列可退化为单次 O(n)
与平衡树的边界
AVL/红黑树不依赖假设、每次严格 O(log n);伸展树用最坏换均摊
书桌上的书对应 →程序的局部性

正在用的摊在桌面(根),翻过的堆在旁边(近),很久没碰的束之高阁(叶)

3第 3 页 · 局部性

自适应调整

前页讲到局部性:刚被访问的节点很可能再次被访问。伸展树如何兑现这一性质?靠的就是访问即调整——每次操作结束后,树形都会按本次访问重新组织,让热点节点上浮、冷门节点下沉。

定义
每次访问后,将被访问节点通过旋转操作移至根,依据历史访问模式动态重塑树形
三种旋转
zig、zig-zig、zig-zag 分别处理父为根、有兄弟祖父、无兄弟祖父三种结构
摊还 O(log n)
单次操作最坏 O(n),但任意 m 次连续操作摊还到每次为 O(log n)
区别于严格平衡
不维护固定平衡条件,而是用则浮起、废则下沉,树形由访问模式驱动
典型应用
LRU 缓存、网络路由表、编译器符号表等具有强访问局部性的场景
图书馆动态调架对应 →伸展树自适应调整

管理员把频繁被借的书逐步前移,下次取书更快;冷门书自然退到深处

4第 4 页 · 自适应调整

逐层伸展

访问一个节点后要把它移到根。最直觉的做法——每次往上旋一层、转完为止——听起来天经地义。但这条朴素的路,其实藏着 O(n) 的陷阱。

逐层伸展做法
对目标节点做单旋,把它和父节点交换;循环执行,直到它成为根
操作次数 = 深度
节点在第 d 层就需 d 次旋转;和普通 BST 一样按需付费
上层深度不变
单旋只交换目标节点与父节点——祖父及以上所有节点的深度完全没动
链状结构崩盘
依次访问链的最深节点,每次 O(n);总成本 Θ(n²),均摊仍是线性
楼梯一级一级爬对应 →逐层伸展每次只上一格

楼越深耗时越长;关键是上面几级在这次动作里完全没动,全靠一次次补

k=1nk=n(n+1)2=Θ(n2)\sum_{k=1}^{n} k = \frac{n(n+1)}{2} = \Theta(n^2)
5第 5 页 · 逐层伸展

实例

查到了 7 号节点,但它还远在树的下层。下次想更快命中它,就必须把它一步一步提到根。每往上爬一层,就要做一组对应旋转——这就是「逐层伸展」的核心思路。

zig 单旋
目标节点的父就是根,做一次旋转即可
zig-zig 同侧
父子同为左子或右子:祖父先转、父亲再转
zig-zag 异侧
父子方向相反(左-右或右-左):两次反向旋转
循环上提
反复执行上述三种情形,直到目标抵达根
走楼梯上楼对应 →逐层伸展

直上一级是 zig;同侧像跨两级,异侧只能走 Z 字绕弯

6第 6 页 · 实例

一步一步往上爬

访问一个节点后,伸展树要把它送到根。但树那么高,怎么送?答案是:每次只看局部两层,一步一步往上爬。

zig 单旋
父亲就是根时,一次单旋直接到根
zig-zig 同向
祖父-父-己三点同侧,先转父再转己
zig-zag 异向
祖父-父-己呈折线,先转己再转父
爬楼梯上楼对应 →逐层伸展

zig=单步,zig-zig=同向连跳两阶,zig-zag=转角跨两阶

7第 7 页 · 一步一步往上爬

最坏情况

上一页我们用逐层旋转把小节点一步步送上了根,方法直观又简单。但简洁是有代价的——某些访问顺序会让树严重失衡,单次操作最坏退化到 O(n)。

单次操作 O(n)
最坏情况下伸展路径要走过整棵树的高度
交替访问触发
两个深层叶子轮流被访问,反复把树拉变形
无均摊对数保证
不像标准 zig-zig 伸展能保证均摊 O(log n)
标准伸展的动机
zig-zig 双旋转正是为规避这一最坏情况而设计
把抽出的书只挪一格对应 →逐层伸展的失衡

借书顺序不好时,热门书会一直沉到末端,每次找它都要翻整排

8第 8 页 · 最坏情况

本节要点

  • 机制:每次一层旋转,累计 log n 步上移
  • 代价:长链最坏可退化到单次 O(n)
  • 兜底:势能均摊下仍是 O(log n)
  • 本质:用访问热度重塑树形
  • 取舍:放弃严格平衡换取自调整
延伸主题:双层伸展:一步两层避最坏势能函数证明均摊上界与其他平衡树横向对比
9第 9 页 · 本节要点

课后思考

先合上笔记自己想,再点开参考答案对思路——三个问题对应三个层次。

1为什么单次访问最坏要 O(n) 次旋转,整体均摊却是 O(log n)?

参考答案单次操作可能把整条路径翻一遍;但被访问过的节点都"付过费",下次再访就近了。均摊看的是总代价除以操作次数——"前人栽树,后人乘凉"。

2若 80% 请求落在同一批热门键上,伸展树的局部性优势如何体现?

参考答案热点键被访问后升到根部附近,再次访问路径极短;AVL/红黑树每次都得走 O(log n)。偶发冷数据会触发大量旋转,但热点更快,整体仍快。

3能否人为构造访问序列让伸展树反复"翻车",每次接近 O(n)?

参考答案可以——反复交替访问根与同一叶子,两者反复互换位置。说明均摊保证依赖"访问具有时间局部性",刻意构造的对抗序列能打破它。

10第 10 页 · 课后思考
(a1)伸展树:逐层伸展 · 知识图解