一、堆排序時間複雜度
堆排序的時間複雜度是
O(N*lgN)。
假設被排序的數列中有N個數。遍歷一趟的時間複雜度是O(N),須要遍歷多少次呢?
堆排序是採用的二叉堆進行排序的,二叉堆就是一棵二叉樹,它須要遍歷的次數就是二叉樹的深度,而根據徹底二叉樹的定義,
它的深度至少是lg(N+1)。最可能是多少呢?因爲二叉堆是徹底二叉樹,所以,
它的深度最多也不會超過lg(2N)。所以,遍歷一趟的時間複雜度是O(N),而遍歷次數介於lg(N+1)和lg(2N)之間;所以得出它的時間複雜度是O(N*lgN)。
二、堆排序穩定性
堆排序是不穩定的算法,它不知足穩定算法的定義。它在交換數據的時候,是比較父結點和子節點之間的數據,因此,即使是存在兩個數值相等的兄弟節點,它們的相對順序在排序也可能發生變化。
算法穩定性 -- 假設在數列中存在a[i]=a[j],若在排序以前,a[i]在a[j]前面;而且排序以後,a[i]仍然在a[j]前面。則這個排序算法是穩定的!
三、示例
// golang //堆排序 //s[0]不用,實際元素從角標1開始 //父節點元素大於子節點元素 //左子節點角標爲2*k //右子節點角標爲2*k+1 //父節點角標爲k/2 func HeapSort(s []int) { N := len(s) - 1 //s[0]不用,實際元素數量和最後一個元素的角標都爲N //構造堆 //若是給兩個已構造好的堆添加一個共同父節點, //將新添加的節點做一次下沉將構造一個新堆, //因爲葉子節點均可看做一個構造好的堆,因此 //能夠從最後一個非葉子節點開始下沉,直至 //根節點,最後一個非葉子節點是最後一個葉子 //節點的父節點,角標爲N/2 for k := N / 2; k >= 1; k-- { sink(s, k, N) } //下沉排序 for N > 1 { swap(s, 1, N) //將大的放在數組後面,升序排序 N-- sink(s, 1, N) } } //下沉(由上至下的堆有序化) func sink(s []int, k, N int) { for { i := 2 * k if i > N { //保證該節點是非葉子節點 break } if i < N && s[i+1] > s[i] { //選擇較大的子節點 i++ } if s[k] >= s[i] { //沒下沉到底就構造好堆了 break } swap(s, k, i) k = i } } func swap(s []int, i int, j int) { s[i], s[j] = s[j], s[i] }
四、算法流程分析
堆排序(Heap Sort)是指
利用堆這種數據結構所設計的一種排序算法。
咱們知道,堆分爲"最大堆"和"最小堆"。最大堆一般被用來進行"升序"排序,而最小堆一般被用來進行"降序"排序。
鑑於最大堆和最小堆是對稱關係,理解其中一種便可。本文將對最大堆實現的升序排序進行詳細說明。
最大堆進行升序排序的基本思想:
① 初始化堆:將數列a[1...n]構形成最大堆。
② 交換數據:將a[1]和a[n]交換,使a[n]是a[1...n]中的最大值;而後將a[1...n-1]從新調整爲最大堆。 接着,將a[1]和a[n-1]交換,使a[n-1]是a[1...n-1]中的最大值;而後將a[1...n-2]從新調整爲最大值。 依次類推,直到整個數列都是有序的。
下面,經過圖文來解析堆排序的實現過程。注意實現中用到了"數組實現的二叉堆的性質"。
在第一個元素的索引爲 0 的情形中:
性質一:索引爲i的左孩子的索引是 (2*i+1);
性質二:索引爲i的左孩子的索引是 (2*i+2);
性質三:索引爲i的父結點的索引是 floor((i-1)/2);
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
例如,對於最大堆{110,100,90,40,80,20,60,10,30,50,70}而言:索引爲0的左孩子的全部是1;索引爲0的右孩子是2;索引爲8的父節點是3。
下面演示heap_sort_asc(a, n)對a={20,30,90,40,70,110,60,10,100,50,80}, n=11進行堆排序過程。下面是數組a對應的初始化結構:
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
1 初始化堆
在堆排序算法中,首先要將待排序的數組轉化成二叉堆。
下面演示將數組{20,30,90,40,70,110,60,10,100,50,80}轉換爲最大堆{110,100,90,40,80,20,60,10,30,50,70}的步驟。
1.1 i=11/2-1,即i=4
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
上面是maxheap_down(a, 4, 9)調整過程。maxheap_down(a, 4, 9)的做用是將a[4...9]進行下調;a[4]的左孩子是a[9],右孩子是a[10]。調整時,選擇左右孩子中較大的一個(即a[10])和a[4]交換。
1.2 i=3
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
上面是maxheap_down(a, 3, 9)調整過程。maxheap_down(a, 3, 9)的做用是將a[3...9]進行下調;a[3]的左孩子是a[7],右孩子是a[8]。調整時,選擇左右孩子中較大的一個(即a[8])和a[4]交換。
1.3 i=2
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
上面是maxheap_down(a, 2, 9)調整過程。maxheap_down(a, 2, 9)的做用是將a[2...9]進行下調;a[2]的左孩子是a[5],右孩子是a[6]。調整時,選擇左右孩子中較大的一個(即a[5])和a[2]交換。
1.4 i=1
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
上面是maxheap_down(a, 1, 9)調整過程。maxheap_down(a, 1, 9)的做用是將a[1...9]進行下調;a[1]的左孩子是a[3],右孩子是a[4]。調整時,選擇左右孩子中較大的一個(即a[3])和a[1]交換。交換以後,a[3]爲30,它比它的右孩子a[8]要大,接着,再將它們交換。
1.5 i=0
![](http://static.javashuo.com/static/loading.gif)
![](http://static.javashuo.com/static/loading.gif)
上面是maxheap_down(a, 0, 9)調整過程。maxheap_down(a, 0, 9)的做用是將a[0...9]進行下調;a[0]的左孩子是a[1],右孩子是a[2]。調整時,選擇左右孩子中較大的一個(即a[2])和a[0]交換。交換以後,a[2]爲20,它比它的左右孩子要大,選擇較大的孩子(即左孩子)和a[2]交換。
調整完畢,就獲得了最大堆。此時,數組{20,30,90,40,70,110,60,10,100,50,80}也就變成了{110,100,90,40,80,20,60,10,30,50,70}。
第2部分 交換數據
在將數組轉換成最大堆以後,接着要進行交換數據,從而使數組成爲一個真正的有序數組。
交換數據部分相對比較簡單,下面僅僅給出將最大值放在數組末尾的示意圖。
![](http://static.javashuo.com/static/loading.gif)
上面是當n=10時,交換數據的示意圖。
![](http://static.javashuo.com/static/loading.gif)
當n=10時,首先交換a[0]和a[10],使得a[10]是a[0...10]之間的最大值;而後,調整a[0...9]使它稱爲最大堆。交換以後:a[10]是有序的!
當n=9時, 首先交換a[0]和a[9],使得a[9]是a[0...9]之間的最大值;而後,調整a[0...8]使它稱爲最大堆。交換以後:a[9...10]是有序的!
...
依此類推,直到a[0...10]是有序的。