最小最大堆積 (Min-Max Heap)
My vault 資料結構筆記:最小最大堆積 (Min-Max Heap)。
定義
-
一個 Min-Max Heap 是一棵 完全二元樹 (complete binary tree),其第 $i$ 層為 min-level 或 max-level,並互相交錯 (alternating levels)
-
Root 必然是全樹最小值
-
若節點 $x$ 在 min-level,則 $x$ 是其子樹的 最小值
-
若節點 $x$ 在 max-level,則 $x$ 是其子樹的 最大值
-
一個在 min-level 的節點稱為 min node,在 max-level 的節點稱為 max node
-
相對地,max-min heap 是 root 是最大值,子節點中有最小值
性質
-
因為是完全二元樹,可以用陣列形式 $A[1..N]$ 來表示
-
第 $i$ 個節點的父節點為 $\left\lfloor \frac{i}{2} \right\rfloor$,子節點為 $2i$ 和 $2i + 1$
Min/Max 層級
-
使用 0-based 層級 index
-
偶數層: $0, 2, 4, \dots$ 是 min-level
-
奇數層: $1, 3, 5, \dots$ 是 max-level
-
Min / Max 驗證
-
假設節點 $P$ 在 min-level,則
$P \le \min(\text{所有子孫節點的值})$ -
假設節點 $P$ 在 max-level,則
$P \ge \max(\text{所有子孫節點的值})$
插入操作(Insertion)
步驟
-
將 $x$ 插入在最後一個位置(即 $n+1$)
-
設 $p = \left\lfloor \frac{n}{2} \right\rfloor$ 為 $x$ 的父節點
-
根據 $p$ 所在層級分為兩種情況處理:
Case 1:父節點 $p$ 在 min-level
-
若 $x.key > H[p].key$,代表 $x$ 比父親大,不應留在 min-level:
- 執行
VerifyMax(H, n, x),檢查 $x$ 能否上升為 max-level 中的最大值
- 執行
-
否則 $x$ 比父親小,有機會挑戰 min-level:
- 令 $H[n] = H[p]$(父值下移),再執行
VerifyMin(H, p, x)
- 令 $H[n] = H[p]$(父值下移),再執行
Case 2:父節點 $p$ 在 max-level
-
若 $x.key < H[p].key$,代表 $x$ 比父親小,不應留在 max-level:
- 執行
VerifyMin(H, n, x),檢查 $x$ 能否成為 min-level 中的最小值
- 執行
-
否則 $x$ 比父親大,有機會挑戰 max-level:
- 令 $H[n] = H[p]$,再執行
VerifyMax(H, p, x)
- 令 $H[n] = H[p]$,再執行
✅ 插入後,會根據父節點決定自己應該屬於 max-level 還是 min-level,然後只會在「自己的層級」中挑戰上去,保持那層的 heap 性質。
時間複雜度
因為調整 min-level 或是 max-level 那麼他的複雜度為樹高 $O(h), \ h=\log(n)$ 所以爲 $O(\log n)$
程式碼片段
void VerifyMin(std::vector<int>& H, int index, int x) {
while (index > 3) {
int grandparent = index / 4;
if (x < H[grandparent]) {
H[index] = H[grandparent];
index = grandparent;
} else {
break;
}
}
H[index] = x;
}
void VerifyMax(std::vector<int>& H, int index, int x) {
while (index > 3) {
int grandparent = index / 4;
if (x > H[grandparent]) {
H[index] = H[grandparent];
index = grandparent;
} else {
break;
}
}
H[index] = x;
}
void insert(std::vector<int>& H, int x) {
H.push_back(x); // 插入到最後一個位置
int n = H.size() - 1;
if (n == 1) return; // root 無需調整
int p = n / 2;
if (isMinLevel(n)) {
if (x > H[p]) {
VerifyMax(H, n, x);
} else {
H[n] = H[p];
VerifyMin(H, p, x);
}
} else { // max level
if (x < H[p]) {
VerifyMin(H, n, x);
} else {
H[n] = H[p];
VerifyMax(H, p, x);
}
}
}
bool isMinLevel(int index) {
int level = 0;
while (index > 1) {
index /= 2;
level++;
}
return level % 2 == 0;
}
Delete-Min on Min-Max Heap
操作目標:
✅ 刪除最小值時,會將最後一個節點的值 $x$ 補到 root,然後從 root 開始向下調整位置,直到找到 $x$ 合法的落點,確保整棵 Min-Max Heap 結構正確。
從 Min-Max Heap 中刪除最小值(即 root),並保持 heap 結構與 min-max 層級規則正確。
步驟說明:
-
移走 root 的資料值(即最小值)。
-
將最後一個節點的值(記作 $x$)暫時補到 root 上,並將最後節點從陣列中刪除。
- $x$ 是被移上來等待調整的新值,接下來的任務就是讓 $x$ 在 heap 中重新落位
-
將
x插入到 root 為起點的子樹中,視子節點情況分成 3 類:
Case 1:Root 無子節點
- 表示 heap 只剩一個節點,直接移除即可。
Case 2:Root 有子但無孫子節點(即 root 的 children 是葉節點)
-
找出 root 兩個子節點中較小者
k -
若
x.key > k.key,則x與k交換位置,x成為子節點 -
否則:
x留在 root
Case 3:Root 有孫子存在(完整三層以上)
-
找出整個子孫中 最小值所在的節點
k -
設
p為k的父節點
接下來判斷:
-
如果
x.key <= k.key:x就留在 root 上(已經比全部子孫都小)
-
否則:
-
將
k放上 root -
接著:如果
x > p.key,表示x不適合當 max-level 子 ⇒ 交換x與p再繼續操作 -
否則:將
x放到k位置結束
-
💡 重點:因為 min-max heap 層級交錯,若
x被推到孫子層,可能會違反該層級的 min/max 性質,所以要再與其父p檢查一次
-
整體類似於 min-heap 的 delete-min,但需額外考慮 min-max 層級正確性
-
若
x被放到 max-level,要保證它不是比其父還大(因為那是 min 層)
時間複雜度
如果已 case 3 討論,那麼從孫子節點中找到最大或是最小,所以基本就是找到樹的每層 $h/2$ 所以基本是樹高的 $O(\log n)$
C++ 程式碼片段:Delete-Min 操作
void deleteMin(std::vector<int>& H) {
int n = H.size() - 1;
if (n == 0) return; // 空 heap
int minValue = H[1];
int x = H[n]; // 取最後一個值
H.pop_back();
if (n == 1) return; // 只剩一個元素被刪除完就結束
int index = 1;
while (true) {
int m = -1; // 最小孫子或子的位置
int minKey = x;
// 找出所有子與孫子中的最小值位置
for (int i = 2 * index; i <= std::min(2 * index + 1, n); ++i) {
if (i <= n && H[i] < minKey) {
minKey = H[i];
m = i;
}
}
for (int i = 4 * index; i <= std::min(4 * index + 3, n); ++i) {
if (i <= n && H[i] < minKey) {
minKey = H[i];
m = i;
}
}
if (m == -1 || x <= H[m]) {
H[index] = x;
break;
}
H[index] = H[m];
int parent = m / 2;
if (x > H[parent]) {
std::swap(H[m], H[parent]);
index = parent;
} else {
index = m;
}
}
}
範例樹(插入後驗證)
8 ← Min level (root)
/ \
31 16 ← Max level
/ \ / \
46 51 13 71 ← Min level
-
Root (8) 是全樹最小值 ✅
-
31, 16 在 max-level:
-
31 > 46, 51 ✅
-
16 < 71 ❌(無法成為子樹最大值)
-
-
46, 51, 13, 71 為 min-level 節點
最大值位置
- 由於 root 是 min-value,max-value 會出現在 root 的子節點中,即 $A[2]$ 與 $A[3]$ $$\text{max_value} = \max(A[2], A[3])$$