十大經典排序算法江山圖java
堆排序在排序複雜性的研究中有着重要的地位,由於他是咱們所知的惟一可以同時最優的利用空間和時間的方法,當空間十分緊張的時候(例如嵌入式系統或者低成本的移動設備中)他很流行,由於他只用幾行就能實現較好的性能。可是現代操做系統中不多使用他,由於他沒法利用緩存,這一點很致命
。數組元素不多和相鄰的其餘元素進行比較,所以緩存未命中的次數要遠遠高於大多數比較都在相鄰元素間的算法,如快速排序,歸併排序,甚至是希爾排序。面試
❝堆(英語:Heap)是計算機科學中的一種特別的徹底二叉樹,知足特性"給定堆中任意節點P和C,若P是C的母節點,那麼P的值會小於等於(或大於等於)C的值"。 摘自維基百科。算法
❞
首先堆是一個徹底二叉樹(除了最後一層,其餘層的節點個數都是滿的,最後一層的節點都靠左排列),這點很重要,直接決定了數組下標和左右父節點的對應關係(關係往下看)。數組
最小堆(min heap):母節點的值恆小於等於子節點的值;緩存
最大堆(max heap):母節點的值恆大於等於子節點的值;ide
堆性能
值得注意,這個地方對哦左右子節點的大小沒有要求。url
並且,堆通常用數組來存儲,而不是樹。由於樹須要爲其左右子節點分配指針空間,而數組使用數組下標來表示左右子節點的位置,能夠說堆是被樹耽誤了的數組,頂着樹的光環成功勸退了一大批想學的人,結果實際上幹着數組該乾的事兒,算法史上最大的騙局,掛羊肉賣狗肉
。spa
父節點和左右子節點在數組中的位置關係:操作系統
parent(i) = arr((i - 1)/2)
left(i) = 2i + 1
right(i) = 2i + 2
值得注意,搜索不是堆的目的,並非每個最小(大)堆都是一個有序數組!要將堆轉換成有序數組,須要使用堆排序,此處歡迎咱們的堆排序隆重登場。
正由於堆只是父子之間大小關係肯定,左右子節點直接大小不肯定,因此纔要堆排序將全部的元素有序排序。
以構建大頂堆爲例:
堆排序
根據數組構造大頂堆:
構造大頂堆
一次調整以後7成功上位,獲得數組最大的值7,與此同時數組對應的大頂堆構造完成。
由下至上的堆有序化
交換調整
這裏能夠看到,交換調整以後最大的元素到了最下面,最終會是越下面的數越大,所以構造大頂堆獲得的是降序,升序要構造小頂堆。
import com.sun.tools.javac.util.ArrayUtils;
/**
* @author by zengzhiqin
* 2020-12-10
*/
public class HeapSort {
public static int[] heapSort (int[] arr) {
if (arr == null && arr.length == 0) {
throw new RuntimeException("數組不合法");
}
// 構建堆(從最下面葉子結點層的上一層,即倒數第二層的第一個開始進行構建調整)
for (int i = arr.length / 2 -1; i >= 0; i--) {
adjustDown(arr, i, arr.length);
}
// 循環調整下沉
for (int i = arr.length -1; i >= 0; i--) {
swap(arr, 0, i);
adjustDown(arr, 0, i);
}
return arr;
}
/**
* 調整
* @param arr
* @param parent
* @param length
*/
public static void adjustDown (int[] arr, int parent, int length) {
// 臨時元素保存要下沉的元素
int temp = arr[parent];
// 左節點的位置
int leftChild = 2 * parent + 1;
// 開始往下調整
while (leftChild < length) {
// 若是右孩子節點比左孩子大,則定位到右孩子 (父節點只是比左右孩子都大,左右孩子大小不肯定)
if(leftChild + 1 < length && arr[leftChild] < arr[leftChild + 1]) {
// 此時leftChild其實是右孩子,但始終是左右裏面最大的
leftChild ++ ;
}
// 大頂堆條件被破壞了
if (arr[leftChild] <= temp) {
break;
}
// 把子節點中大的值賦值給父節點
arr[parent] = arr[leftChild];
// 大的子節點爲父節點,調整它的左右子節點
parent = leftChild;
leftChild = 2 * parent + 1;
}
arr[parent] = temp;
}
/**
* 交換數組中兩個元素
* @param arr 數組
* @param i 父元素
* @param index 元素2
*/
public static void swap (int[] arr, int i, int index) {
int temp = arr[i];
arr[i] = arr[index];
arr[index] = temp;
}
public static void main(String[] args) {
//int[] arr = {5, 7, 8, 3, 1, 2, 4, 6, 8};
int[] arr = {3, 1, 2, 4};
//int[] arr = {1, 2, 3};
arr = heapSort(arr);
for (int i=0; i<arr.length; i++) {
System.out.println(arr[i]);
}
}
}
不穩定。
nlog(n)。
大頂堆構造使用 O(N) 的時間,元素調整下濾須要 O(logN),須要下濾 N-1 次,因此總共須要 O(N+(N-1)logN) = O(NlogN)。從過程能夠看出,堆排序,無論最好,最壞時間複雜度都穩定在O(NlogN)。
原地排序,空間複雜度O(1)