Java資料結構與演算法解析(十七)——斜堆
斜堆概述
斜堆(Skew heap)也叫自適應堆(self-adjusting heap),它是左斜堆的一個變種。和左傾堆一樣,它通常也用於實現優先隊列;作為一種自適應的左斜堆,它的合併作業的時間複雜度也是O(lg n)。
它與左斜堆的差別是:
(1) 斜堆的節點沒有”零距離”這個屬性,而左斜堆則有。
(2) 斜堆的合併作業和左傾堆的合併作業演算法不同。
斜堆的合併作業
(1) 如果一個空斜堆與一個非空斜堆合并,返回非空斜堆。
(2) 如果兩個斜堆都非空,那麼比較兩個根節點,取較小堆的根節點為新的根節點。將”較小堆的根節點的右孩子”和”較大堆”進行合并。
(3) 合并後,交換新堆根節點的左孩子和右孩子。
第(3)步是斜堆和左傾堆的合併作業差別的關鍵所在,如果是左傾堆,則合并後要比較左右孩子的零距離大小,若右孩子的零距離 > 左孩子的零距離,則交換左右孩子;最後,在設定根的零距離。
由於合并都是沿著最右路徑進行的,經過合并之後,新斜堆的最右路徑長度必然增加,這會影響下一次合并的效率。所以合并後,通過交換左右子樹,使整棵樹的最右路徑長度非常小(這是啟發規則)。然而斜堆不記錄節點的距離,在操作時,從下往上,沿著合并的路徑,在每個節點處都交換左右子樹。通過不斷交換左右子樹,斜堆把最右路徑甩向左邊了。
遞迴實現合并
1.比較兩個堆; 設p是具有更小的root的索引值的堆,q是另一個堆,r是合併後的結果堆。
2.令r的root是p(具有最小root索引值),r的右子樹為p的左子樹。
3.令r的左子樹為p的右子樹與q合併的結果。
合并前:
合并後:
非遞迴合并實現
1.把每個堆的每棵(遞迴意義下)最右子樹切下來。這使得得到的每棵樹的右子樹均為空白。
2.按root的索引值的升序排列這些樹。
3.迭代合併具有最大root索引值的兩棵樹:
1)具有次大root索引值的樹的右子樹必定為空白。把其左子樹與右子樹
2)交換。現在該樹的左子樹為空白。
具有最大root索引值的樹作為具有次大root索引值樹的左子樹。
效能比較
斜堆的代碼實現 1. 基本定義
public class SkewHeap<T extends Comparable<T>> { private SkewNode<T> mRoot; // 根結點 private class SkewNode<T extends Comparable<T>> { T key; // 關鍵字(索引值) SkewNode<T> left; // 左孩子 SkewNode<T> right; // 右孩子 public SkewNode(T key, SkewNode<T> left, SkewNode<T> right) { this.key = key; this.left = left; this.right = right; } public String toString() { return "key:"+key; } }} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
SkewNode是斜堆對應的節點類。
SkewHeap是斜堆類,它包含了斜堆的根節點,以及斜堆的操作。 2. 合并
/* * 合并"斜堆x"和"斜堆y" */private SkewNode<T> merge(SkewNode<T> x, SkewNode<T> y) { if(x == null) return y; if(y == null) return x; // 合并x和y時,將x作為合并後的樹的根; // 這裡的操作是保證: x的key < y的key if(x.key.compareTo(y.key) > 0) { SkewNode<T> tmp = x; x = y; y = tmp; } // 將x的右孩子和y合并, // 合并後直接交換x的左右孩子,而不需要像左傾堆一樣考慮它們的npl。 SkewNode<T> tmp = merge(x.right, y); x.right = x.left; x.left = tmp; return x;}public void merge(SkewHeap<T> other) { this.mRoot = merge(this.mRoot, other.mRoot);} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
merge(x, y)是內部介面,作用是合并x和y這兩個斜堆,並返回得到的新堆的根節點。
merge(other)是外部介面,作用是將other合并到當前堆中。 3. 添加
/* * 建立結點(key),並將其插入到斜堆中 * * 參數說明: * key 插入結點的索引值 */public void insert(T key) { SkewNode<T> node = new SkewNode<T>(key,null,null); // 如果建立結點失敗,則返回。 if (node != null) this.mRoot = merge(this.mRoot, node);} 1 2 3 4 5 6 7 8 9 10 11 12 13
insert(key)的作用是建立索引值為key的節點,並將其加入到當前斜堆中。 4. 刪除
/* * 刪除根結點 * * 傳回值: * 返回被刪除的節點的索引值 */public T remove() { if (this.mRoot == null) return null; T key = this.mRoot.key; SkewNode<T> l = this.mRoot.left; SkewNode<T> r = this.mRoot.right; this.mRoot = null; // 刪除根節點 this.mRoot = merge(l, r); // 合并左右子樹 return key;} 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
remove()的作用是刪除斜堆的最小節點。 完整代碼
public class SkewHeap<T extends Comparable<T>> { private SkewNode<T> mRoot; // 根結點 private class SkewNode<T extends Comparable<T>> { T key; // 關鍵字(索引值) SkewNode<T> left; // 左孩子 SkewNode<T> right; // 右孩子 public SkewNode(T key, SkewNode<T> left, SkewNode<T> right) { this.key = key; this.left = left; this.right = right; } public String toString() { return "key:"+key; } } public SkewHeap() { mRoot = null; } /* * 前序走訪"斜堆" */ private void preOrder(SkewNode<T> heap) { if(heap != null) { System.out.print(heap.key+" "); preOrder(heap.left); preOrder(heap.right); } } public void preOrder() { preOrder(mRoot); } /* * 中序遍曆"斜堆" */ private void inOrder(SkewNode<T> heap) { if(heap != null) { inOrder(heap.left); System.out.print(heap.key+" "); inOrder(heap.right); } } public void inOrder() { inOrder(mRoot); } /* * 後序遍曆"斜堆" */ private void postOrder(SkewNode<T> heap) { if(heap != null) { postOrder(heap.left); postOrder(heap.right); System.out.print(heap.key+" "); } } public void postOrder() { postOrder(mRoot); } /* * 合并"斜堆x"和"斜堆y" */ private SkewNode<T> merge(SkewNode<T&