排序算法:堆排序
?
相關(guān)博客:
排序算法:冒泡排序、插入排序、選擇排序、希爾排序
排序算法:歸并排序、快速排序
排序算法:桶排序、計數(shù)排序、基數(shù)排序
排序算法:堆排序
十大排序算法小結(jié)
一、堆:
1、什么是堆:
堆是一種特殊的樹,它滿足需要滿足兩個條件:
(1)堆是一種完全二叉樹,也就是除了最后一層,其他層的節(jié)點個數(shù)都是滿的,最后一個節(jié)點都靠左排列。
(2)堆中每一個節(jié)點的值都必須大于等于(或小于等于)其左右子節(jié)點的值。
對于每個節(jié)點的值都大于等于子樹中每個節(jié)點值的堆,我們叫作“大頂堆”。對于每個節(jié)點的值都小于等于子樹中每個節(jié)點值的堆,我們叫作“小頂堆”。
2、堆的實現(xiàn):
用數(shù)組來存儲完全二叉樹是非常節(jié)省內(nèi)存空間的,因為我們不需要存儲左右子節(jié)點的指針,單純通過數(shù)組的下標,就可以找到一個節(jié)點的子節(jié)點和父節(jié)點。
從圖中我們可以看到,數(shù)組中下標為 i 的節(jié)點的左子節(jié)點,就是下標為 i?2?的節(jié)點,右子節(jié)點就是下標為 i?2+1的節(jié)點,父節(jié)點就是下標為i/2的節(jié)點。
3、堆的核心操作:
(1)往堆中插入一個元素:
我們新插入一個元素之后,堆可能就不滿足堆的特性了,就需要進行調(diào)整,讓其重新滿足堆的特性,即堆化(heapify),堆化非常簡單,就是順著節(jié)點所在的路徑,向上或者向下,對比,然后交換。所以分為兩種,從下往上 和 從上往下。
①從下往上堆化:
例如:在已經(jīng)建好的堆中插入值為“22”的結(jié)點,這時候就需要重新調(diào)整堆結(jié)構(gòu),過程如下:
Java代碼實現(xiàn):
public class Heap {private int[] a;//數(shù)組,從下標1開始存儲數(shù)據(jù)private int n;//堆可以存儲的最大數(shù)據(jù)個數(shù)private int count;//堆中已經(jīng)存儲的數(shù)據(jù)個數(shù)public Heap(int capacity){a = new int[capacity+1];n=capacity;count = 0;}//1、插入數(shù)據(jù),并從下往上堆化public void insert(int data){if(count>=n) return;//堆滿了++count;a[count]=data;int i = count;while(i/2>0 && a[i]>a[i/2]){swap(a,i,i/2);i=i/2;}}private void swap(int[] array, int i, int j) {int temp=array[j];array[j]=array[i];array[i]=temp;} }(2)從堆中刪除堆頂元素:
刪除步驟:把最后一個節(jié)點放到堆頂,然后利用同樣的父子節(jié)點對比方法。對于不滿足父子節(jié)點大小關(guān)系的,互換兩個節(jié)點,并且重復(fù)進行這個過程,直到父子節(jié)點之間滿足大小關(guān)系為止。這就是從上往下的堆化方法。
例如:刪除堆頂?shù)摹?3”結(jié)點,刪除步驟如下:
Java代碼實現(xiàn):
public void removeMax(){if(count ==0) return;a[1]=a[count];--count;heapity(a,count,1);}//2、自上往下堆化private void heapity(int[] a2, int n, int i) {while(true){int MaxPos=i;if(i*2<=n && a[i]<a[i*2]) MaxPos=i*2;if(i*2+1<=n && a[MaxPos]<a[i*2+1]) MaxPos=i*2+1;if(MaxPos==i) break;swap(a, i, MaxPos);i=MaxPos;}}一個包含 n個節(jié)點的完全二叉樹,樹的高度不會超過 log2n。堆化的過程是順著節(jié)點所在路徑比較交換的,所以堆化的時間復(fù)雜度跟樹的高度成正比,也就O(logn)。插入數(shù)據(jù)和刪除堆頂元素的主要邏輯就是堆化,所以在堆中插入一個元素和刪除堆頂元素的時間復(fù)雜度都是O(logn)。
二、堆排序:
1、算法原理:
堆排序是指利用堆這種數(shù)據(jù)結(jié)構(gòu)進行排序的一種算法。
(1)排序過程中,只需要個別臨時存儲空間,所以堆排序是原地排序算法,空間復(fù)雜度為O(1)。
(2)堆排序的過程分為建堆和排序兩大步驟。建堆過程的時間復(fù)雜度為O(n),排序過程的時間復(fù)雜度為O(nlogn),所以,堆排序整體的時間復(fù)雜度為O(nlogn)。
(3)堆排序不是穩(wěn)定的算法,因為在排序的過程中,存在將堆的最后一個節(jié)點跟堆頂節(jié)點互換的操作,所以可能改變值相同數(shù)據(jù)的原始相對順序。
2、建堆:
(1)第一種是實現(xiàn)思路:借助前面的,在堆中插入元素的思路。將要排序的數(shù)組從前往后處理,依次到插入堆中,并且每次都從下往上進行堆化。
(2)第二種實現(xiàn)實現(xiàn)思路:從后往前處理數(shù)組,并且每個數(shù)據(jù)都是從上往下堆化。也就是從二叉樹中第一個非葉子節(jié)點開始開始堆化,因為葉子節(jié)點往下堆化只能自己跟自己比較。如下圖:
Java代碼實現(xiàn):
//3、第二種堆化的方法:public void buildHeap(int[] a,int n){for(int i=n/2;i>=1;--i){heapity(a,n,i);}}//2、自上往下堆化private void heapity(int[] a, int n, int i) {while(true){int MaxPos=i;if(i*2<=n && a[i]<a[i*2]) MaxPos=i*2;if(i*2+1<=n && a[MaxPos]<a[i*2+1]) MaxPos=i*2+1;if(MaxPos==i) break;swap(a, i, MaxPos);i=MaxPos;}}3、排序:
(1)建堆:建堆結(jié)束后,數(shù)組中的數(shù)據(jù)已經(jīng)是按照大頂堆的特性組織的。數(shù)組中的第一個元素就是堆頂。
(2)取出最大值(類似刪除操作):將堆頂元素a[1]與最后一個元素a[n]交換,這時,最大元素就放到了下標為n的位置。
(3)重新堆化:交換后新的堆頂可能違反堆的性質(zhì),需要重新進行堆化。
(4)重復(fù)(2)(3)操作,直到最后堆中只剩下下標為1的元素,排序就完成了。
Java代碼實現(xiàn):
//4、排序,n表示數(shù)據(jù)的個數(shù)。public void sort(int[] a,int n) {buildHeap(a,n);int k=n;while(k>1){swap(a, 1, k);--k;heapity(a,k,1);}}?
?
本篇博客主要參考《極客時間》王爭的《數(shù)據(jù)結(jié)構(gòu)與算法之美》專欄第28節(jié)。
總結(jié)
- 上一篇: 排序算法:桶排序、计数排序、基数排序
- 下一篇: 十大排序算法小结