堆有最大堆和最小堆之分,最大堆就是每個節點的值都>=其左右孩子(如果有的話)值的完全二叉樹。最小堆便是每個節點的值都<=其左右孩子值的完全二叉樹。
設有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 Arthur */ 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("================================================="); 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); } }
程式運行:
D:\java>java Heap
=================================================
調整後的初始堆:
20 17 16 15 9 15 11 1 10 3 2 7 8 5
刪除後
20 17 16 15 9 15 11 5 10 3 2 7 8
99 17 20 15 9 15 16 5 10 3 2 7 8 11
將堆排序後:
2 3 5 7 8 9 10 11 15 15 16 17 20 99
-------------------------
20 17 16 10 15 9 15 0 5 2 11 1 7 3 8
==============================
=================================================
調整後的初始堆:
93 72 48 53 45 30 18 36 15 35
93 80 48 53 72 30 18 36 15 35 45
93 72 48 53 45 30 18 36 15 35
93 53 48 36 45 30 18 35 15