1、堆排序算法的基本特性
時間複雜度:O(n*lgn)
最壞:O(n*lgn)
空間複雜度:O(1)
不穩定。html
堆排序是一種選擇排序算法,與關鍵字的初始排列次序無關,即就是在最好,最壞,通常的狀況下排序時間複雜度不變。對包含n個數的輸入數組,平均時間爲O(nlgn),最壞狀況(已經排好序)也是是O(nlgn),最好狀況(徹底無序)也是O(nlgn)。因爲不但時間複雜度少,並且空間複雜度也是最少的,因此是用於排序的最佳選擇。由於,基於比較的排序,最快也只能達到O(nlgn),雖然快排也能夠達到這個這個水平,可是快排的時間複雜度是跟關鍵字的初始排序有關,最壞的狀況退化成O(n^2),並且快排的空間複雜度是O(n*lgn)。java
2、堆的定義算法
n個元素的序列{k1,k2,…,kn}當且僅當知足下列關係之一時,稱之爲堆。數組
情形1:ki <= k2i 且ki <= k2i+1 (最小化堆或小頂堆:左、右子孩子的值比父結點的值都大)函數
情形2:ki >= k2i 且ki >= k2i+1 (最大化堆或大頂堆:左、右子孩子的值比父結點的值都小)測試
其中i=1,2,…,n/2向下取整;ui
若將和此序列對應的一維數組(即以一維數組做此序列的存儲結構)當作是一個徹底二叉樹,則堆的含義代表,徹底二叉樹中全部非葉子結點的值均不大於(或不小於)其左、右孩子結點的值。spa
由此,若序列{k1,k2,…,kn}是堆,則堆頂元素(或徹底二叉樹的根)必爲序列中n個元素的最小值(或最大值)。3d
例如,下列兩個序列爲堆,對應的徹底二叉樹如圖:code
若在輸出堆頂的最小值以後,使得剩餘n-1個元素的序列重又建成一個堆,則獲得n個元素的次小值。如此反覆執行,便能獲得一個有序序列,這個過程稱之爲堆排序。
堆排序(Heap Sort)只須要一個記錄元素大小的輔助空間(供交換用),每一個待排序的記錄僅佔有一個存儲空間。
3、堆的存儲
通常用數組來表示堆,若根結點存在序號0處, i結點的父結點下標就爲(i-1)/2。i結點的左右子結點下標分別爲2*i+1和2*i+2。
(注:若是根結點是從1開始,則左右孩子結點分別是2i和2i+1。)
如第0個結點左右子結點下標分別爲1和2。
如大頂堆以下:
左圖爲其存儲結構,右圖爲其邏輯結構。
4、堆排序的實現
實現堆排序須要解決兩個問題:
1.如何由一個無序序列建成一個堆?
2.如何在輸出堆頂元素以後,調整剩餘元素成爲一個新的堆?
先考慮第二個問題,通常在輸出堆頂元素以後,視爲將這個元素排除,而後用表中最後一個元素填補它的位置,自上向下進行調整:首先將堆頂元素和它的左右子樹的根結點進行比較,把最小的元素交換到堆頂;而後順着被破壞的路徑一路調整下去,直至葉子結點,就獲得新的堆。
咱們稱這個自堆頂至葉子的調整過程爲「篩選」。
從無序序列創建堆的過程就是一個反覆「篩選」的過程。
初始化堆的時候是對全部的非葉子結點進行篩選。
假設有n個元素的堆,那麼最後一個非葉子元素的下標是[n/2]-1(向下取整),因此篩選只須要從第[n/2]-1個元素開始,從後往前進行調整。
好比,給定一個數組,首先根據該數組元素構造一個徹底二叉樹。
而後從最後一個非葉子結點開始,每次都是從父結點、左孩子、右孩子中進行比較交換,交換可能會引發孩子結點不知足堆的性質,因此每次交換以後須要從新對被交換的孩子結點進行調整。
有了初始堆以後就能夠進行排序了。
堆排序是一種選擇排序。創建的初始堆爲初始的無序區。
排序開始,首先輸出堆頂元素(由於它是最值),將堆頂元素和最後一個元素交換,這樣,第n-1個位置(即最後一個位置)做爲有序區,前n-2個位置還是無序區,對無序區進行調整,獲得堆以後,再交換堆頂和最後一個元素,這樣有序區長度變爲2。。。
不斷進行此操做,將剩下的元素從新調整爲堆,而後輸出堆頂元素到有序區。每次交換都致使無序區-1,有序區+1。不斷重複此過程直到有序區長度增加爲n-1,排序完成。
首先,創建初始的堆結構如圖:
而後,交換堆頂的元素和最後一個元素,此時最後一個位置做爲有序區(有序區顯示爲黃色),而後進行其餘無序區的堆調整,從新獲得大頂堆後,交換堆頂和倒數第二個元素的位置……
重複此過程:
最後,有序區拓展完成,即排序完成:
由排序過程可見,若想獲得升序,則創建大頂堆,若想獲得降序,則創建小頂堆。
5、代碼
假設排列的元素爲整型,且元素的關鍵字爲其自己。
由於要進行升序排列,因此用大頂堆。
根結點從0開始,因此i結點的左右孩子結點的下標爲2i+1和2i+2。
import java.util.Scanner; /** * 堆排序 * 時間複雜度:O(n*lgn) * 空間複雜度:O(1) * @author breeze * */ public class HeapSort { /** * 堆排序的主方法 * * @param array * @return */ public static int[] sortHeap(int[] array) { buildHeap(array);// 構建堆 int n = array.length;//數組中元素的個數 for (int i = n - 1; i >= 1; i--) { swap(array, 0, i);//交換首尾元素 adjustHeap(array, 0, i);//調整堆 } return array; } /** * 構建大頂堆堆 * 從最後一個非葉子結點開始調整堆。每次都是從父結點,左節點,右結點三者中選最大值與父結點交換 * @param array */ private static void buildHeap(int[] array) { int n = array.length;// 數組中元素的個數 for (int i = n / 2 - 1; i >= 0; i--)//i= n/2-1 表示最後一個非葉子結點的索引值 adjustHeap(array, i, n); } /** * 調整堆 * @param A * @param idx * @param max 堆的大小 */ private static void adjustHeap(int[] A, int idx, int max) { int left = 2 * idx + 1;// 左孩子的下標(若是存在的話) int right = left + 1;// 左孩子的下標(若是存在的話) int largest = 0;// 尋找3個節點中最大值節點的下標 if (left < max && A[left] > A[idx])//左孩子的值比父結點的值大 largest = left; else largest = idx; if (right < max && A[right] > A[largest]) largest = right; if (largest != idx) { swap(A, largest, idx);//從左節點,右結點中選最大值與父結點交換 adjustHeap(A, largest, max); } } private static void swap(int[] array, int i, int i2) { int temp = array[i]; array[i] = array[i2]; array[i2] = temp; } public static void main(String[] args) { Scanner sc = new Scanner(System.in); while(sc.hasNextLine()){ String str = sc.nextLine(); //獲取一行 String[] words = str.trim().split(" ");//把一行的數據,分割爲單個的數據的數組 int length = words.length; //字符串還須要轉爲int,或者其餘的類型 int[] params = new int[length]; for (int i = 0;i<length;i++){ if ("".equals(words[i]) || "\n".equals(words[i])) continue; params[i] = Integer.parseInt(words[i]); } //調用須要測試的函數 int[] array = sortHeap(params); //打印結果 for(int i : array){ System.out.print(i+" "); } } } }
圖片和內容參考:http://www.cnblogs.com/mengdd/archive/2012/11/30/2796845.html