数据结构与算法篇-二叉堆插入-追踪示例

跟踪插入操作

跟踪完整的插入序列,建立对上浮如何维护堆属性的直观理解。

示例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⌋。

参考资料