堆有最大堆和最小堆之分,最大堆就是每個節點的值都>=其左右孩子(如果有的話)值的完全二叉樹。最小堆便是每個節點的值都<=其左右孩子值的完全二叉樹。
設有n個元素的序列{k1,k2,...,kn},當且僅當滿足下列關系時,稱之為堆。
堆的三種基本操作(以下以最大堆為例):
⑴最大堆的插入
由於需要維持完全二叉樹的形態,需要先將要插入的結點x放在最底層的最右邊,插入后滿 足完全二叉樹的特點;
然后把x依次向上調整到合適位置滿足堆的性質,例如下圖中插入80,先將80放在最后,然后兩次上浮到合適位置.
時間:O(logn)。 “結點上浮”
程序實現:

//向最大堆中插入元素, heap:存放堆元素的數組 public static void insert(List<Integer> heap, int value) { //在數組的尾部添加 if(heap.size()==0) heap.add(0);//數組下標為0的位置不放元素 heap.add(value); //開始上升操作 // heapUp2(heap, heap.size() - 1); heapUp(heap, heap.size() - 1); } //上升,讓插入的數和父節點的數值比較,當大於父節點的時候就和父節點的值相交換 public static void heapUp(List<Integer> heap, int index) { //注意由於數值是從下標為1開始,當index = 1的時候,已經是根節點了 if (index > 1) { //求出父親的節點 int parent = index / 2; //獲取相應位置的數值 int parentValue = (Integer) heap.get(parent); int indexValue = (Integer) heap.get(index); //如果父親節點比index的數值小,就交換二者的數值 if (parentValue < indexValue) { //交換數值 swap(heap, parent, index); //遞歸調用 heapUp(heap, parent); } } }
⑵刪除
操作原理是:當刪除節點的數值時,原來的位置就會出現一個孔,填充這個孔的方法就是,
把最后的葉子的值賦給該孔並下調到合適位置,最后把該葉子刪除。
如圖中要刪除72,先用堆中最后一個元素來35替換72,再將35下沉到合適位置,最后將葉子節點刪除。
“結點下沉”
程序:

/** * 刪除堆中位置是index處的節點 * 操作原理是:當刪除節點的數值時,原來的位置就會出現一個孔 * 填充這個孔的方法就是,把最后的葉子的值賦給該孔,最后把該葉子刪除 * @param heap */ public static void delete(List<Integer> heap,int index) { //把最后的一個葉子的數值賦值給index位置 heap.set(index, heap.get(heap.size() - 1)); //下沉操作 //heapDown2(heap, index); heapDown(heap, index); //把最后一個位置的數字刪除 heap.remove(heap.size() - 1); } /** * 遞歸實現 * 刪除堆中一個數據的時候,根據堆的性質,應該把相應的位置下移,才能保持住堆性質不變 * @param heap 保持堆元素的數組 * @param index 被刪除的那個節點的位置 */ public static void heapDown(List<Integer> heap, int index) { //因為第一個位置存儲的是空值,不在考慮之內 int n = heap.size() - 2; //記錄最大的那個兒子節點的位置 int child = -1; //2*index>n說明該節點沒有左右兒子節點了,那么就返回 if (2 * index > n) { return; } //如果左右兒子都存在 else if (2 * index < n) { //定義左兒子節點 child = 2 * index; //如果左兒子小於右兒子的數值,取右兒子的下標 if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) { child++; } }//如果只有一個兒子(左兒子節點) else if (2 * index == n) { child = 2 * index; } if ((Integer) heap.get(child) > (Integer) heap.get(index)) { //交換堆中的child,和index位置的值 swap(heap, child, index); //完成交換后遞歸調用,繼續下降 heapDown(heap, child); } }
⑶初始化
方法1:插入法:
從空堆開始,依次插入每一個結點,直到所有的結點全部插入到堆為止。
時間:O(n*log(n))
方法2:調整法:
序列對應一個完全二叉樹;從最后一個分支結點(n div 2)開始,到根(1)為止,依次對每個分支結點進行調整(下沉),
以便形成以每個分支結點為根的堆,當最后對樹根結點進行調整后,整個樹就變成了一個堆。
時間:O(n)
對如圖的序列,要使其成為堆,我們從最后一個分支結點(10/2),其值為72開始,依次對每個分支節點53,18,36 45進行調整(下沉).
程序:

/*根據樹的性質建堆,樹節點前一半一定是分支節點,即有孩子的,所以我們從這里開始調整出初始堆*/ public static void adjust(List<Integer> heap){ for (int i = heap.size() / 2; i > 0; i--) adjust(heap,i, heap.size()-1); System.out.println("================================================="); System.out.println("調整后的初始堆:"); print(heap); } /** * 調整堆,使其滿足堆得定義 * @param i * @param n */ public static void adjust(List<Integer> heap,int i, int n) { int child; for (; i <= n / 2; ) { child = i * 2; if(child+1<=n&&heap.get(child)<heap.get(child+1)) child+=1;/*使child指向值較大的孩子*/ if(heap.get(i)< heap.get(child)){ swap(heap,i, child); /*交換后,以child為根的子樹不一定滿足堆定義,所以從child處開始調整*/ i = child; } else break; } }
(4)最大堆排序

//對一個最大堆heap排序 public static void heapSort(List<Integer> heap) { for (int i = heap.size()-1; i > 0; i--) { /*把根節點跟最后一個元素交換位置,調整剩下的n-1個節點,即可排好序*/ swap(heap,1, i); adjust(heap,1, i - 1); } }
(5)完整的代碼

import java.util.*; /** * 實現的最大堆的插入和刪除操作 * * @author You */ public class Heap { /** * 刪除堆中位置是index處的值 操作原理是:當刪除節點的數值時,原來的位置就會出現一個孔 * 填充這個孔的方法就是,把最后的葉子的值賦給該孔,最后把該葉子刪除 * * @param heap * 大頂堆 */ public static void delete(List<Integer> heap, int index) { // 把最后的一個葉子的數值賦值給index位置 heap.set(index, heap.get(heap.size() - 1)); // 下沉操作 // heapDown2(heap, index); heapDown(heap, index); // 節點下沉 // 把最后一個位置的數字刪除 heap.remove(heap.size() - 1); } /** * 節點下沉遞歸實現 刪除一個堆中一個數據的時候,根據堆的性質,應該把相應的位置下移,才能保持住堆性質不變 * * @param heap * 保持最大堆元素的數組 * @param index * 被刪除的那個節點的位置 */ public static void heapDown(List<Integer> heap, int index) { // 因為第一個位置存儲的是空值,不在考慮之內 int n = heap.size() - 2; // 記錄最大的那個兒子節點的位置 int child = -1; // 2*index>n說明該節點沒有左右兒子節點了,那么就返回 if (2 * index > n) { return; } // 如果左右兒子都存在 else if (2 * index < n) { // 定義左兒子節點 child = 2 * index; // 如果左兒子小於右兒子的數值,取右兒子的下標 if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) { child++; } }// 如果只有一個兒子(左兒子節點) else if (2 * index == n) { child = 2 * index; } if ((Integer) heap.get(child) > (Integer) heap.get(index)) { // 交換堆中的child,和index位置的值 swap(heap, child, index); // 完成交換后遞歸調用,繼續下降 heapDown(heap, child); } } // 非遞歸實現 public static void heapDown2(List<Integer> heap, int index) { int child = 0;// 存儲左兒子的位置 int temp = (Integer) heap.get(index); int n = heap.size() - 2; // 如果有兒子的話 for (; 2 * index <= n; index = child) { // 獲取左兒子的位置 child = 2 * index; // 如果只有左兒子 if (child == n) { child = 2 * index; } // 如果右兒子比左兒子的數值大 else if ((Integer) heap.get(child) < (Integer) heap.get(child + 1)) { child++; } // 如果數值最大的兒子比temp的值大 if ((Integer) heap.get(child) > temp) { // 交換堆中的child,和index位置的值 swap(heap, child, index); } else { break; } } } // 打印鏈表 public static void print(List<Integer> list) { for (int i = 1; i < list.size(); i++) { System.out.print(list.get(i) + " "); } System.out.println(); } // 把堆中的a,b位置的值互換 public static void swap(List<Integer> heap, int a, int b) { // 臨時存儲child位置的值 int temp = (Integer) heap.get(a); // 把index的值賦給child的位置 heap.set(a, heap.get(b)); // 把原來的child位置的數值賦值給index位置 heap.set(b, temp); } // 向最大堆中插入元素 public static void insert(List<Integer> heap, int value) { // 在數組的尾部添加要插入的元素 if (heap.size() == 0) heap.add(0);// 數組下標為0的位置不放元素 heap.add(value); // 開始上升操作 // heapUp2(heap, heap.size() - 1); heapUp(heap, heap.size() - 1); } // 節點上浮,讓插入的數和父節點的數值比較,當大於父節點的時候就和節點的值相交換 public static void heapUp(List<Integer> heap, int index) { // 注意由於數值是從小標為一開始,當index = 1的時候,已經是根節點了 if (index > 1) { // 保存父親的節點 int parent = index / 2; // 獲取相應位置的數值 int parentValue = (Integer) heap.get(parent); int indexValue = (Integer) heap.get(index); // 如果父親節點比index的數值小,就交換二者的數值 if (parentValue < indexValue) { // 交換數值 swap(heap, parent, index); // 遞歸調用 heapUp(heap, parent); } } } // 非遞歸實現 public static void heapUp2(List<Integer> heap, int index) { int parent = 0; for (; index > 1; index /= 2) { // 獲取index的父節點的下標 parent = index / 2; // 獲得父節點的值 int parentValue = (Integer) heap.get(parent); // 獲得index位置的值 int indexValue = (Integer) heap.get(index); // 如果小於就交換 if (parentValue < indexValue) { swap(heap, parent, index); } } } /* 根據樹的性質建堆,樹節點前一半一定是分支節點,即有孩子的,所以我們從這里開始調整出初始堆 */ public static void adjust(List<Integer> heap) { for (int i = heap.size() / 2; i > 0; i--) adjust(heap, i, heap.size() - 1); System.out.println("調整后的初始堆:"); print(heap); } /** * 調整堆,使其滿足堆得定義 * * @param i * @param n */ public static void adjust(List<Integer> heap, int i, int n) { int child; for (; i <= n / 2;) { child = i * 2; if (child + 1 <= n && heap.get(child) < heap.get(child + 1)) child += 1;/* 使child指向值較大的孩子 */ if (heap.get(i) < heap.get(child)) { swap(heap, i, child); /* 交換后,以child為根的子樹不一定滿足堆定義,所以從child處開始調整 */ i = child; } else break; } } // 對一個最大堆heap排序 public static void heapSort(List<Integer> heap) { for (int i = heap.size() - 1; i > 0; i--) { /* 把根節點跟最后一個元素交換位置,調整剩下的n-1個節點,即可排好序 */ swap(heap, 1, i); adjust(heap, 1, i - 1); } } public static void main(String args[]) { List<Integer> array = new ArrayList<Integer>(Arrays.asList(null, 1, 2, 5, 10, 3, 7, 11, 15, 17, 20, 9, 15, 8, 16)); adjust(array);// 調整使array成為最大堆 delete(array, 8);// 堆中刪除下標是8的元素 System.out.println("刪除后"); print(array); insert(array, 99);// 堆中插入 print(array); heapSort(array);// 排序 System.out.println("將堆排序后:"); print(array); System.out.println("--------------------------------------------------"); List<Integer> array1 = new ArrayList<Integer>(); insert(array1, 0); insert(array1, 1); insert(array1, 2); insert(array1, 5); insert(array1, 10); insert(array1, 3); insert(array1, 7); insert(array1, 11); insert(array1, 15); insert(array1, 17); insert(array1, 20); insert(array1, 9); insert(array1, 15); insert(array1, 8); insert(array1, 16); print(array1); System.out.println("--------------------------------------------------"); array = new ArrayList<Integer>(Arrays.asList(null, 45, 36, 18, 53, 72, 30, 48, 93, 15, 35)); adjust(array); insert(array, 80);// 堆中插入 print(array); delete(array, 2);// 堆中刪除80的元素 print(array); delete(array, 2);// 堆中刪除72的元素 print(array); } }
轉自:http://www.java3z.com/cwbwebhome/article/article1/1362.html?id=4745