算法導論讀書筆記(5)
堆
(二叉) 堆 數據結構是一種數據結構,它能夠被視爲一棵徹底二叉樹。樹中每一個結點與數組中存放該結點值的那個元素對應。樹的每一層都是填滿的,最後一層從左子樹開始填。表示堆的數組 A 是一個具備兩個屬性的對象: A.length 是數組中元素的個數, A.heap-size 是存放在 A 中的堆元素個數。此處有 A.heap-size <= A.length 。樹的根爲 A [ 1 ],給定了某個結點的下標 i ,其父結點 PARENT(i)
,左兒子 LEFT(i)
和右兒子 RIGHT(i)
的下標能夠簡單的計算出來:java
PARENT(i) 1 return FLOOR(i / 2)
LEFT(i) 1 return 2i
RIGHT(i) 1 return 2i + 1
二叉堆有兩種: 最大堆 和 最小堆 。這兩種堆都要知足各自的堆特性。在最大堆中,最大堆特性是指除了根之外的每一個結點 i ,有 A [ PARENT(i)
] >= A [ i ]。即某個結點的值至多和其父結點同樣大。這樣,堆中的最大元素就存放在根結點中。最小堆的組織方式恰好相反,最小堆特性是指除了根之外的每一個結點 i ,有 A [ PARENT(i)
] <= A [ i ],最小堆的最小元素是在根部。算法
堆能夠被當作是一棵樹,結點在堆中的高度定義爲從本結點到葉子的最長簡單降低路徑上邊的數目;定義堆的高度爲樹根的高度,於是樹的高度爲 Θ (lg n )。而堆結構上的一些基本操做的運行時間至多與樹的高度成正比,爲 O (lg n )。下面將介紹幾個基本過程,並說明它們的用法。sql
MAX-HEAPOFY
過程,運行時間爲 O (lg n ),是保持最大堆性質的關鍵BUILD-MAX-HEAP
過程,以線性時間運行,能夠在無序的輸入數組上構造出最大堆HEAP-SORT
過程,運行時間爲 O ( n lg n ),對一個數組進行原地排序MAX-HEAP-INSERT
,HEAP-EXTRACT-MAX
,HEAP-INCREASE-KEY
,HEAP-MAXIMUM
過程的運行時間爲 O (lg n ),可讓堆結構做爲優先級隊列使用。
保持堆的性質
MAX-HEAPIFY
過程的輸入爲一個數組 A 和下標 i 。當 MAX-HEAPIFY
被調用時,咱們假定以 LEFT(i)
和 RIGHT(i)
爲根的兩棵二叉樹都是最大堆,但這時 A [ i ]可能小於其子女,這樣就違反了最大堆特性。 MAX-HEAPIFY
讓 A [ i ]在最大堆中「降低」,使以 i 爲根的子樹成爲最大堆。api
MAX-HEAPIFY(A, i) 1 l = LEFT(i) 2 r = RIGHT(i) 3 if l <= A.heap-size and A[l] > A[i] 4 largest = l 5 else 6 largest = i 7 if r <= A.heap-size and A[r] > A[largest] 8 largest = r 9 if largest != i 10 exchange A[i] with A[largest] 11 MAX-HEAPIFY(A, largest)
下圖描述了 MAX-HEAPIFY
的過程。在算法的每一步裏,從元素 A [ i ], A [ LEFT(i)
]和 A [ RIGHT(i)
]中找出最大的,並將其下標保存在 largest 中。若是 A [ i ]是最大的,則以 i 爲根的子樹已經是最大堆,程序結束。不然,交換 A [ i ]和 A [ largest ],從而使 i 及其子女知足堆性質。下標爲 largest 的結點在交換後的值爲 A [ i ],以該結點爲根的子樹可能又違反了最大堆性質。於是要對該子樹遞歸調用 MAX-HEAPIFY
。數組
當 MAX-HEAPIFY
做用在一棵以結點 i 爲根的,大小爲 n 的子樹上時,其運行時間爲調整元素 A [ i ], A [ LEFT(i)
]和 A [ RIGHT(i)
]的關係時所用的時間 Θ (1),加上對以 i 爲結點的某個子結點爲根的子樹遞歸調用 MAX-HEAPIFY
所需的時間。 i 結點的子樹大小至多爲2 n / 3(最壞狀況發生在最底層剛好半滿的時候),那麼 MAX-HEAPIFY
的運行時間可表示爲: T ( n ) <= T (2 n / 3) + Θ (1)。該遞歸式的解爲 T ( n ) = O (lg n )。或者說, MAX-HEAPIFY
做用於一個高度爲 h 的結點所需的運行時間爲 O ( h )。bash
建堆
如今能夠自底向上地用 MAX-HEAPIFY
來將一個數組 A [ 1 .. n ](此處 n = A.length )變成一個最大堆。又可知子數組 A [ FLOOR(n / 2)
+ 1 .. n ]中的元素都是樹中的葉子結點,它們均可以看作是隻含一個元素的堆。過程 BUILD-MAX-HEAP
對樹中的每個非葉子結點都調用了一次 MAX-HEAPIFY
。數據結構
BUILD-MAX-HEAP(A) 1 A.heap-size = A.length 2 for i = FLOOR(A.length / 2) downto 1 3 MAX-HEAPIFY(A, i)
下圖給出了 BUILD-MAX-HEAP
過程的一個例子。ide
咱們能夠計算出 BUILD-MAX-HEAP
運行時間的一個簡單上界:每次調用 MAX-HEAPIFY
的時間爲 O (lg n ),共有 O ( n )次調用,故運行時間是 O ( n lg n )。儘管這個界是對的,但從漸近意義上來講不夠緊確。post
實際上,咱們能夠獲得一個更加緊確的界。由於,在樹中不一樣高度的結點處運行 MAX-HEAPIFY
的時間不一樣,而大部分結點的高度都比較小。關於更緊確界的分析依賴於這樣的性質:一個 n 元素堆的高度爲FLOOR(lg n ),而且,在任意高度 h 上,至多有CEIL( n / 2( h + 1))個結點。ui
MAX-HEAPIFY
做用在高度爲 h 的結點上的時間爲 O ( h ),能夠將 BUILD-MAX-HEAP
的代價表示爲:
最終能夠得出 BUILD-MAX-HEAP
過程運行時間的界爲
這說明能夠在線性時間內,將一個無序數組建成一個最大堆。
堆排序算法
堆排序算法先用 BUILD-MAX-HEAP
將輸入數組 A [ 1 .. n ]建成一個最大堆。此時數組中最大元素在根 A [ 1 ],能夠經過將它與 A [ n ]互換來達到最終正確的位置。而後經過縮小 A.heap-seize ,能夠很容易地將 A [ 1 .. n - 1 ]建成最大堆。不斷的重複這一過程,堆的大小由 n - 1一直降到2。
HEAP-SORT(A) 1 BUILD-MAX-HEAP(A) 2 for i = A.length downto 2 3 exchange A[1] with A[i] 4 A.heap-size = A.heap-size - 1 5 MAX-HEAPIFY(A, 1)
HEAP-SORT
過程的時間代價爲 O ( n lg n )。其中調用 BUILD-MAX-HEAP
的時間爲 O ( n ), n - 1次 MAX-HEAPIFY
調用中每次的時間代價爲 O (lg n )。
堆結構和堆排序算法的簡單Java實現
/** * 堆支持的公共方法 */ public interface Heap { public int[] toArray(); public int[] toHeapArray(); /** * 優先級隊列支持的四個操做 */ public int head(); public int extractHead(); public void changeKey(int i, int key); public void add(int k); }
/** * 堆結構的公共部分實現 */ public abstract class AbstractHeap implements Heap { private static final int DEFAULT_CAPACITY = 10; protected int size; // 堆中元素的個數 protected int length; // 數組中元素的個數 protected int[] elements; protected AbstractHeap() { this(DEFAULT_CAPACITY); } protected AbstractHeap(int len) { this.elements = new int[len]; this.length = len; this.size = 0; } /** * 接受數組類型的參數後直接建堆 */ protected AbstractHeap(int[] array) { this.elements = Arrays.copyOf(array, array.length); this.length = array.length; buildHeap(); } protected int parent(int i) { return (i - 1) >> 1; } protected int left(int i) { return (i << 1) + 1; } protected int right(int i) { return (i + 1) << 1; } /** * 維持堆特性:最大堆與最小堆除了比較部分,其他代碼都是相同的,故將比較部分抽取出來留給具體的實現 */ protected void heapify(int i) { int l = left(i); int r = right(i); int index; if (l < size && heapifyLogic(l, i)) index = l; else index = i; if (r < size && heapifyLogic(r, index)) index = r; if (index != i) { swap(index, i); heapify(index); } } protected void buildHeap() { size = length; for (int i = (size >> 1) - 1; i >= 0; i--) heapify(i); } protected abstract boolean heapifyLogic(int a, int b); @Override public int head() { if (size == 0) throw new IndexOutOfBoundsException("heap underflow"); return elements[0]; } @Override public int extractHead() { if (size == 0) throw new IndexOutOfBoundsException("heap underflow"); int head = head(); elements[0] = elements[size - 1]; size--; heapify(0); return head; } @Override public int[] toArray() { return Arrays.copyOf(elements, length); } @Override public int[] toHeapArray() { return Arrays.copyOf(elements, size); } protected void swap(int i, int j) { AlgorithmUtil.swap(elements, i, j); } }
/** * 最大堆 */ public class MaxHeap extends AbstractHeap { public MaxHeap() { super(); } public MaxHeap(int len) { super(len); } public MaxHeap(int[] array) { super(array); } /** * 最大堆特性:要求子結點的值不大於其父結點 */ @Override protected boolean heapifyLogic(int a, int b) { return elements[a] > elements[b]; } @Override public void changeKey(int i, int key) { if (key < elements[i]) throw new IllegalArgumentException("new key is smaller than current key"); elements[i] = key; while (i > 0 && elements[parent(i)] < elements[i]) { swap(i, parent(i)); i = parent(i); } } @Override public void add(int k) { if (size == length) throw new IndexOutOfBoundsException("heap overflow"); size++; elements[size - 1] = Integer.MIN_VALUE; changeKey(size - 1, k); } /** * 堆排序 */ public void heapSort() { for (int i = length - 1; i > 0; i--) { swap(0, i); size--; heapify(0); } } }
/** * 最小堆 */ public class MinHeap extends AbstractHeap { public MinHeap() { super(); } public MinHeap(int len) { super(len); } public MinHeap(int[] array) { super(array); } /** * 最小堆特性:要求子結點的值不小於其父結點 */ @Override protected boolean heapifyLogic(int a, int b) { return elements[a] < elements[b]; } @Override public void changeKey(int i, int key) { if (key > elements[i]) throw new IllegalArgumentException("new key is larger than current key"); elements[i] = key; while (i > 0 && elements[parent(i)] > elements[i]) { swap(i, parent(i)); i = parent(i); } } @Override public void add(int k) { if (size == length) throw new IndexOutOfBoundsException("heap overflow"); size++; elements[size - 1] = Integer.MAX_VALUE; changeKey(size - 1, k); } }
練習
6.2-2
最小堆僞碼
MIN-HEAPIFY(A, i) 1 l = LEFT(i) 2 r = RIGHT(i) 3 if l <= A.heap-size and A[l] < A[i] 4 smallest = l 5 else 6 smallest = i 7 if r <= A.heap-size and A[r] < A[smallest] 8 smallest = r 9 if smallest != i 10 exchange A[i] with A[smallest] 11 MIN-HEAPIFY(A, smallest)
6.2-5
使用迭代結構改寫 MAX-HEAPIFY
過程
MAX-HEAPIFY-ITERATOR(A, i) 1 largest = -1 2 while largest != i 3 l = left(i) 4 r = right(i) 5 if l <= A.heap-size and A[l] > A[i] 6 largest = l 7 else 8 largest = i 9 if r <= A.heap-size and A[r] > A[largest] 10 largest = r 11 if largest != i 12 exchange A[largest] with A[i] 13 i = largest 14 largest = -1