跟踪插入操作
跟踪完整的插入序列,建立对上浮如何维护堆属性的直观理解。
示例1:从头开始构建堆
任务:插入序列:50, 30, 70, 20, 60, 80, 10
从空堆开始:
txt
Insert 50:
────────
Array: [50]
0
Tree: [50]
No parent, no bubble-up needed.
Insert 30:
────────
Step 1: Add at end (index 1)
Array: [50, 30]
0 1
Tree: [50]
/
[30]
Step 2: Check 30 vs parent 50
30 < 50 ✓ Already satisfies heap property
No swaps needed
Final: [50]
/
[30]
Insert 70:
────────
Step 1: Add at end (index 2)
Array: [50, 30, 70]
0 1 2
Tree: [50]
/ \
[30] [70]
Step 2: Check 70 vs parent 50
70 > 50 ✗ Violation! Swap them
After swap:
Array: [70, 30, 50]
0 1 2
Tree: [70]
/ \
[30] [50]
Step 3: Check 70 vs its new parent (none - at root)
At root, stop
Final: [70]
/ \
[30] [50]
Insert 20:
────────
Step 1: Add at end (index 3)
Array: [70, 30, 50, 20]
0 1 2 3
Tree: [70]
/ \
[30] [50]
/
[20]
Step 2: Check 20 vs parent 30
20 < 30 ✓ Already satisfies heap property
No swaps needed
Final: [70]
/ \
[30] [50]
/
[20]
Insert 60:
────────
Step 1: Add at end (index 4)
Array: [70, 30, 50, 20, 60]
0 1 2 3 4
Tree: [70]
/ \
[30] [50]
/ \
[20][60]
Step 2: Check 60 vs parent 30
60 > 30 ✗ Violation! Swap them
After swap:
Array: [70, 60, 50, 20, 30]
0 1 2 3 4
Tree: [70]
/ \
[60] [50]
/ \
[20][30]
Step 3: Check 60 vs its new parent 70
60 < 70 ✓ Heap property satisfied
Stop
Final: [70]
/ \
[60] [50]
/ \
[20][30]
Insert 80:
────────
Step 1: Add at end (index 5)
Array: [70, 60, 50, 20, 30, 80]
0 1 2 3 4 5
Tree: [70]
/ \
[60] [50]
/ \ /
[20][30] [80]
Step 2: Check 80 vs parent 50
80 > 50 ✗ Violation! Swap them
After first swap:
Array: [70, 60, 80, 20, 30, 50]
0 1 2 3 4 5
Tree: [70]
/ \
[60] [80]
/ \ /
[20][30] [50]
Step 3: Check 80 vs its new parent 70
80 > 70 ✗ Still violates! Swap them
After second swap:
Array: [80, 60, 70, 20, 30, 50]
0 1 2 3 4 5
Tree: [80]
/ \
[60] [70]
/ \ /
[20][30] [50]
Step 4: Check 80 vs its new parent (none - at root)
At root, stop
Final: [80]
/ \
[60] [70]
/ \ /
[20][30] [50]
Insert 10:
────────
Step 1: Add at end (index 6)
Array: [80, 60, 70, 20, 30, 50, 10]
0 1 2 3 4 5 6
Tree: [80]
/ \
[60] [70]
/ \ / \
[20][30] [50][10]
Step 2: Check 10 vs parent 70
10 < 70 ✓ Already satisfies heap property
No swaps needed
Final: [80]
/ \
[60] [70]
/ \ / \
[20][30] [50][10]
序列分析
每次插入的交换次数:
| 值 | 交换次数 | 原因 |
|---|---|---|
| 50 | 0 | 第一个元素(根节点) |
| 30 | 0 | 比父节点小 |
| 70 | 1 | 比父节点大,上浮到根节点 |
| 20 | 0 | 比父节点小 |
| 60 | 1 | 比父节点大,在祖父节点处停止 |
| 80 | 2 | 迄今为止最大,一直上浮到根节点 |
| 10 | 0 | 比父节点小 |
观察到的规律:交换次数取决于:
- 插入值相对于其祖先的大小
- 插入位置离根节点的距离(该位置的树高度)
示例2:最坏情况插入场景
单次插入的最坏情况是什么?
答:插入一个比所有现有元素都大的值,会导致它一直上浮到根节点。
txt
Start with heap: [50, 40, 30, 20, 10]
Tree: [50]
/ \
[40] [30]
/ \
[20][10]
Height = 2
Insert 60:
────────
Position: index 5 (right child of 30)
Parent distance to root: 2 levels
Swap 1: 60 with 30
Swap 2: 60 with 50
Total swaps: 2 = height of tree
The element travels the entire height!
最坏情况规律:向空堆中按递增顺序插入元素,每个新元素都会成为新的最大值,需上浮至根节点。
示例 :插入 10, 20, 30, 40, 50
txt
Insert 10: [10] → 0 swaps
Insert 20: [20, 10] → 1 swap
Insert 30: [30, 10, 20] → 1 swap
Insert 40: [40, 30, 20, 10] → 2 swaps
Insert 50: [50, 40, 20, 10, 30] → 2 swaps
Each new element becomes the new maximum!
示例3:最佳情况插入场景
最佳情况是什么?
答:插入一个比其父节点小的值,需要零次交换。
txt
Insert 75 into this heap:
Tree: [90]
/ \
[70] [80]
/ \
[50][60]
Array: [90, 70, 80, 50, 60]
New position: index 5 (left child of 80)
Parent: 80
Check: 75 < 80 ✓
Result: 0 swaps! Heap property already satisfied
Final: [90]
/ \
[70] [80]
/ \ /
[50][60] [75]
最佳情况规律:插入最小值时,无论树的大小,均无需交换。
数组与树操作的可视化
重要概念:数组中的每次交换,对应于树中元素向上移动一层。
txt
Insert 85 into heap:
Initial state:
Array: [90, 70, 80, 50, 60]
0 1 2 3 4
Tree: [90]
/ \
[70] [80]
/ \
[50][60]
Step 1: Add 85 at index 5
Array: [90, 70, 80, 50, 60, 85]
0 1 2 3 4 5
↑
position
Tree: [90]
/ \
[70] [80]
/ \ /
[50][60] [85] ← level 2
Parent of index 5 = (5-1)/2 = 2
Parent value: heap[2] = 80
Step 2: Compare and swap (85 > 80)
Array: [90, 70, 85, 50, 60, 80]
0 1 2 3 4 5
↑
position moved
Tree: [90]
/ \
[70] [85] ← level 1
/ \ /
[50][60] [80]
Parent of index 2 = (2-1)/2 = 0
Parent value: heap[0] = 90
Step 3: Compare (85 < 90) → Stop
Array index movement: 5 → 2 → stop
Tree level movement: 2 → 1 → stop
Number of swaps: 1
总结
从上述插入跟踪中得出的关键观察:
- 交换次数 ≤ 树的高度 - 最坏情况是上浮到根节点
- 零次交换很常见 - 当插入值小于父节点时无需交换
- 每次交换恰好向上移动一层 - 对应于树中的一层
- 完全二叉树属性 - 确保高度为 O ( log n ) O(\log n) O(logn)
这些跟踪演示了:
- 为什么插入在最坏情况下是 O ( l o g n ) O(log n) O(logn)?
- 最大交换次数等于具有
n个节点的完全二叉树的高度,即 ⌊ log 2 n ⌋ \lfloor\log_2 n\rfloor ⌊log2n⌋。