很久沒寫博客了 還是抽時間寫一寫~
堆用來做優先隊列 插入與刪除平均時間複雜度都爲O(n),以鏈表實現插入是O(1),刪除是O(n),數組實現反之
JUC包就有優先隊列的實現(後面深入併發後再補充)
判斷:葉子節點爲左節點時是完全二叉樹,葉子節點爲右節點時,不連續緊密排列,非完全二叉樹
堆的定義:
堆是一顆完全二叉樹。堆的基本要求是堆中所有結點的值必須大於(或小於)其孩子結點的值。
完全二叉樹:
若設二叉樹的高度爲h,除第 h 層外,其它各層 (1~h-1) 的結點數都達到最大個數,第 h 層有葉子結點,並且葉子結點都是從左到右依次排布。
堆是數據結構(以大根堆爲例)
public class Heap { // 容量 private int capbility; // 元素大小 private int count; // 元素數組 private int[] data; Heap(int capbility) { this.data = new int[capbility+1]; this.capbility = capbility; this.count = 0; } }
注意 這裏是capbility+1; 因爲我們堆結點是從索引1開始,從1到capbility+1,data[0]是空
介紹兩個性質
獲取父結點 i/2
獲取子結點 2*i 和 2*i+1
(如果是capbility則只是計算不同,獲取父結點 (i-1)/2,子結點 2*i +1和 2*i+2 )
然後是插入操作
插入到是插入到最後的結點位置,容量++,爲了保持堆的性質,開始上升shiftUp操作,不斷與父結點比較,如果大於父結點則交換,到一個節點的時候或者滿足堆的性質
public void insert(int item) { // FIXME 可改爲擴容操作 assert(count + 1 <= capbility); count++; data[count] = item; shiftUp(count); } private void shiftUp(int k) { while (k > 1 && data[k] > data[k/2]) { // 與父結點交換 SortHelper.swap(data,k,k/2); // 繼續比較 k = k/2; } }
刪除操作
把data[1](ps:data[0]爲空)刪除,容量--,爲了保持堆的性質,把data[count]換到data[1],開始shiftDown操作,不斷與子節點比較,與子節點中大的那個交換位置,到無子節點或者已經滿足大於堆的性質
public int delete(){ assert( count > 0 ); int item = data[1]; data[1] = data[count]; count --; shiftDown(1); return item; } private void shiftDown(int k) { // 如果有左節點 while (2*k <= count){ int j = 2*k; // data[j] 是 data[2*k]和data[2*k+1]中的最大值 if (j+1 <= count && data[j] < data[j+1]) { j = j+1; } // 與子節點最大的進行比較 如果大於子節點 就直接跳出 if( data[k] >= data[j] ) break; // 交換位置和索引繼續比較 SortHelper.swap(data,k,j); k = j; } }
測試結果
堆排序
由上面得出結論 我們按照順序每次把刪除的元素就保存就是堆排序 是不是很簡單~
public static void main(String[] args) { int n = 10; Heap heap = new Heap(n); for (int i = 0; i < 10; i++) { heap.insert((i+1)*2); } System.out.println("構建的堆:"+Arrays.toString(heap.getData())); int[] arr = new int[n]; for (int i = 0; i < n ; i++) { arr[i] = heap.delete(); } System.out.println("排序後的數組:"+Arrays.toString(arr)); }
構建堆優化
其實構建堆不用每次都加一個元素就shiftUp操作
葉子節點一開始就一個 無需進行任何操作,所以我們從第一個非葉子節點進行,也就是22(索引5)和之前的元素進行shiftDown操作 ,
獲取第一個非葉子節點:獲取最後一個葉子節點(data[count])的父結點(data[count/2])
public void heapify(int[] arr, int n) { for( int i = 0 ; i < n ; i ++ ) data[i+1] = arr[i]; count = n; for (int i = n/2; i >= 1; i--) { shiftDown(i); } }
注意我們這裏是>= 1,因爲我們從索引1開始計算
原地排序(不需要藉助輔助空間)
把最大的data[0]一直往數組最後放,放完該排序的數組大小-1,再重複進行這個操作
public static void localSort(int[] arr,int n) { heapify(arr, n); for (int i = 0; i < n ; i++) { SortHelper.swap(arr,0,n-(i+1)); __shiftDown(arr, n-(i+1), 0); } }
代碼優化