堆是一個數組,它能夠被當作一個近似的徹底二叉樹,樹上的每個結點對應數組中的一個元素。除去最底層外,該樹是徹底充滿的,並且從左到右填充。算法
用數組A表示堆,從數組第1個元素開始,數組中第i(1<=i <=n)個元素,其父結點,左孩子,右孩子的下標以下api
// 父結點 public int parent( int i){ return i/2; } // 左孩子 public int left(int i){ return 2*i; } // 右孩子 public int right(int i){ return 2*i+1; }
當數組起始下標是0的時候,其父結點,左右孩子結點以下數組
// 父結點 public int parent( int i){ return (i-1)/2; } // 左孩子 public int left(int i){ return 2*i+1; } // 右孩子 public int right(int i){ return 2*i+2; }
堆能夠分爲大頂堆和小頂堆ide
大頂堆:結點 i 的值 都大於其左右孩子結點的值ui
小頂堆:結點 i 的值 都小於其左右孩子結點的值spa
二叉樹的形式與數組形式表達堆之間元素的關係code
練習1,高度爲h的堆,元素最少和最可能是多少?blog
最多:這個徹底二叉樹第h層元素填滿:2^h - 1排序
最少:第h-1層填滿,第h層只有一個元素:2^(h-1) -1 + 1 = 2^(h-1)遞歸
說明:h=1 表示第一層
練習2,含有n個元素的堆的高度是 [lgn]
練習3,當用數組存放堆的時候,堆的元素時候n,求葉子結點下標
max-heapify 用於維護一個大頂堆。它的輸入是一個數組A和下標i。在調用max-heapify的時候,假定根結點left(i) 和right(i) 的二叉樹都是大頂堆,但這時A[i]有可能小於其中的一個孩子,這樣就違背大頂堆的性質。咱們須要進行調整,選取left(i) right(i) 對應結點較大的一個和 i 位置處的結點進行互換。
建堆代碼以下
/** * 調整堆元素 * @param A * @param n * @param i */ public void max_heapify(int[] A ,int n,int i){ // 左孩子結點 int l = left(i); // 右孩子結點 int r = right(i); // 最大結點下標 int largest = -1; // 與左孩子判斷 if(l<= n && A[l] > A[i]) largest = l; else largest = i; // 與右孩子判斷 if(r <=n && A[r] > A[largest]) largest = r; // i 結點不是最大值maxId 和i 結點進行交換 if(largest != i ){ swap(A,largest,i); max_heapify(A,n,largest); } } /** * 交換 * @param A * @param l * @param r */ public void swap(int [] A,int l,int r){ int tmp = A[l]; A[l] = A[r]; A[r] = tmp; }
時間複雜度:O(lg(n))
說明:
在 i left(i) right(i) 三個結點中,選取其對應的值最大的結點和 i 結點的值進行交換。在交換後,下標爲largest的結點的值是原來的A[i] 的值,largest所在的結點的子樹可能違反大頂堆的性質,須要對該子樹遞歸調用max-heapify
這裏的假設是 以left(i) right(i) 爲結點的堆已是大頂堆,因此這個時候,咱們只須要上面的三個元素就行了
算法導論例子
改爲循環代碼
/** * 調整堆元素 * @param A 數組存放堆 * @param n 數組的數量 從 1 - n 開始 * @param i 須要調整的堆 元素 位置 */ public void max_heapify1(int[] A ,int n,int i){ int l = -1; int r = -1; int largest = -1; while(true){ l = left(i); r = right(i); if(l<=n && A[l] > A[i]) largest = l; else largest = i; if(r <=n && A[r] > A[largest]) largest = r; if(largest!=i){ swap(A,largest,i); //更新i 的值至關的遞歸調用 i = largest; // 相等 對左右子樹不影響 }else{ break; } } }
自底向上的方法,利用max-heapify 把一個大小爲n 的數組A[1,...,n] 轉換成大頂堆。子數組A[[n/2] +1,...,n] 中的元素是樹的葉子結點。每一個葉子結點能夠當作一個大頂堆,全部在建堆的時候從 n/2 的元素開始 一直到 第 1 的元素。
爲何這樣作?由於對當前結點進行操做時可以保證以當前結點爲根的樹的子樹都已是最大頂堆。這是從後像前的過程,先把底層的大頂堆構建好,再逐步的構建上層的大頂堆,在必定程度上減小了不比較的操做。若是是從1 到 n/2 的時候,對於新加入的元素,前面已經構建好的大頂堆可能都須要進行更新。好比,咱們已經在第100層, 新插入的元素比以前堆內的元素都大,如100000,前面已經構建好的99層都要進行調整。
/** * 創建大頂堆 * @param A * @param n */ public void build_max_heap(int[] A,int n){ for(int i = n/2;i>=1;i--){ max_heapify(A,n,i); } }
時間複雜度:O(nlog(n))
算法導論建堆例子
初始時候,利用build_max_heap將輸入數組A[1,..,n] 建成大頂堆,這裏A[1]必定是最大的元素,將A[1]與A[n]互換,再對A[1,...,n-1] 調整爲大頂堆,而後A[1]與A[n-1]元素互換,再對A[1,...,n-2] 調整爲大頂堆,如此循環下去
/** * 堆排序 升序 * @param A * @param n */ public void heap_sort(int[] A,int n){ build_max_heap(A,n); for(int i = n;i>=2;i--){ swap(A,i,1); max_heapify(A,i-1,1); } }
堆排序的時間複雜度:O(nlogn)
算法導論例子
全部程序
package heapSort; class heap{ /** * 堆排序 升序 * @param A * @param n */ public void heap_sort(int[] A,int n){ build_max_heap(A,n); for(int i = n;i>=2;i--){ swap(A,i,1); max_heapify(A,i-1,1); } } /** * 創建大頂堆 * @param A * @param n */ public void build_max_heap(int[] A,int n){ for(int i = n/2;i>=1;i--){ max_heapify(A,n,i); } } /** * 調整堆元素 * @param A 數組存放堆 * @param n 數組的數量 從 1 - n 開始 * @param i 須要調整的堆 元素 位置 */ public void max_heapify1(int[] A ,int n,int i){ int l = -1; int r = -1; int largest = -1; while(true){ l = left(i); r = right(i); if(l<=n && A[l] > A[i]) largest = l; else largest = i; if(r <=n && A[r] > A[largest]) largest = r; if(largest!=i){ swap(A,largest,i); //更新i 的值至關的遞歸調用 i = largest; // 相等 對左右子樹不影響 }else{ break; } } } /** * 調整堆元素 * @param A * @param n * @param i */ public void max_heapify(int[] A ,int n,int i){ // 左孩子結點 int l = left(i); // 右孩子結點 int r = right(i); // 最大結點下標 int largest = -1; // 與左孩子判斷 if(l<= n && A[l] > A[i]) largest = l; else largest = i; // 與右孩子判斷 if(r <=n && A[r] > A[largest]) largest = r; // i 結點不是最大值maxId 和i 結點進行交換 if(largest != i ){ swap(A,largest,i); max_heapify(A,n,largest); } } /** * 交換 * @param A * @param l * @param r */ public void swap(int [] A,int l,int r){ int tmp = A[l]; A[l] = A[r]; A[r] = tmp; } // 父結點 public int parent( int i){ return i/2; } // 左孩子 public int left(int i){ return 2*i; } // 右孩子 public int right(int i){ return 2*i+1; } } public class heapSort { public static void main(String[] args) { heap h = new heap(); // 第一個元素不考慮 int[] A = new int[]{-1,4,1,3,2,16,9,10,14,8,7}; int n = A.length-1; //h.build_max_heap(A, n); //printA(A); //16 14 10 8 7 9 3 2 4 1 //16 14 10 8 7 9 3 2 4 1 h.heap_sort(A, n); System.out.println(); printA(A); } public static void printA(int[] A){ for(int i:A){ System.out.print(i+" "); } } }