八大排序
排序,分爲內部排序和外部排序,內部排序是指將數據記錄在內存中進行排序,而外部排序因排序的數據很大,一次不能容納全部的排序記錄,在排序過程中需要訪問外存,所以稱之爲外部排序,我們這裏講的八大排序全部屬於內部排序。
八大排序時間/空間複雜度及穩定性
堆排序
堆排序(Heapsort)是指利用堆積樹(堆)這種數據結構所設計的一種排序算法,它是選擇排序的一種。可以利用數組的特點快速定位指定索引的元素。堆分爲大根堆和小根堆,是完全二叉樹。大根堆的要求是每個節點的值都不大於其父節點的值,即A[PARENT[i]] >= A[i]。在數組的非降序排序中,需要使用的就是大根堆,因爲根據大根堆的要求可知,最大的值一定在堆頂。這裏要注意的是堆排序採用的是完全二叉樹的思想,但它的存儲結構採用的是數組。
堆排序的過程(大根堆)
- 調整堆:調整堆即是將最初無規則的堆調整爲大根堆。它的每一次調整都是將非葉子結點i與其左孩子left[i]和右孩子light[i]進行比較,若非葉子結點i不是其中最大的,那麼將其與最大的結點位置互換。堆的第一次調整是從最後一個非葉子結點開始依次向根節點調整的,這就是爲什麼它要採用完全二叉樹的原因,因爲這樣可以快速的在數組中定位最後一個非葉子結點,即a[n/2-1],其中a是待排序的數組,n是數組a的下標值。那麼就是說,下標爲n/2-1的結點就是最後一個非葉子節點。堆的第一次調整函數的調用關係大概可以總結爲循環調用+遞歸調用。其過程如下:
- 初始完全二叉樹
- 數值3爲最後一個非葉子結點,將其與它的孩子結點相比較,8>3,位置互換
- 判斷被調整後的結點3是否爲非葉子結點,3不是,函數調用進入下一層循環,將結點7與它的左右兒子結點相比較,20>17>7,將結點20與結點7位置互換
- 判斷被調整後的結點7是否爲非葉子結點,7不是,函數調用進入最後一層循環,將結點16與它的左右兒子結點相比較,20>16>7,將結點16與結點20位置互換
- 判斷被調整後的結點16是否爲非葉子結點,16是,函數進行遞歸調用,將結點16與它的左右兒子結點相比較,17>16>7,將結點17與結點16位置互換
- 判斷被調整後的結點16是否爲非葉子結點,16不是,循環結束,堆調整完畢。
- 初始完全二叉樹
- 堆排序:可以看到,上面的調整堆算法循環調用執行完畢以後,結果是一個大根堆,堆排序所要做的就是將堆頂元素20與最後一個元素3互換位置,即將a[0]與a[n-1]互換位置,換位置之後a[0]至a[n-2]元素又組成一個新的堆,這時的調整堆與第一次調整剛好相反,它是從堆頂元素開始調整,一直往下延伸,是一個純遞歸的過程,具體過程如下:
到這裏,排序就算是執行完了,從上述過程可知,堆排序其實也是一種選擇排序,是一種樹形選擇排序。只不過直接選擇排序中,爲了從a[1…n]中選擇最大記錄,需比較n-1次,然後從a[1…n-2]中選擇最大記錄需比較n-2次。事實上這n-2次比較中有很多已經在前面的n-1次比較中已經做過,而樹形選擇排序恰好利用樹形的特點保存了部分前面的比較結果,因此可以減少比較次數,這就是爲什麼第一次調整堆是從下往上,而之後都是從上往下。對於n個關鍵字序列,最壞情況下每個節點需比較log2(n)次,因此其最壞情況下時間複雜度爲nlogn。堆排序爲不穩定排序,不適合記錄較少的排序。下面是堆排序的c語言實現:
#include<stdio.h>
#include<stdlib.h>
#include<math.h>
//堆調整算法
void HeapAdjust(int num[],int i,int length)
{
//定義max保存以num[i]爲根的最小堆中最大值的下標,初始值爲i
int max=i;
//判斷num[i]是否爲根結點 (下標i<=length/2-1的結點都是根節點)
if(i<=length/2-1)
{
//判斷num[2*i+1]是否在堆內,若在,判斷其是否大於num[max],若大於,將其下標2*i+1賦予給max
if((2*i+1)<length && num[2*i+1]>num[max])
{
max=2*i+1;
}
//判斷num[2*(i+1)]是否在堆內,若在,判斷其是否大於num[max],若大於,將其下標2*(i+1)賦予給max
if((2*(i+1))<length && num[2*(i+1)]>num[max])
{
max=2*(i+1);
}
//若i不等於max,則說明以num[i]爲根的最小堆中num[max]是最大值,將num[max]與根結點num[i]交換位置
if(i!=max)
{
num[i]=num[i]^num[max];
num[max]=num[i]^num[max];
num[i]=num[i]^num[max];
//交換後,爲防止以num[max]爲根結點的最小堆結構發生變換,再次調用堆調整算法
HeapAdjust(num,max,length);
}
}
}
//堆排序算法
void HeapSort(int num[],int length)
{
int i;
//找到最後一個非葉子結點,先從最後一個非葉子結點組成的最小堆進行堆調整
for(i=length/2-1;i>=0;i--)
//調用調整堆算法將數組從下往上調整爲大根堆
HeapAdjust(num,i,length);
for(i=length-1;i>0;i--)
{
//將數組第一個數和最後一個數換位置
num[i]=num[i]^num[0];
num[0]=num[i]^num[0];
num[i]=num[i]^num[0];
//繼續調用調整堆算法,將剩餘數據組成的堆從上往下調整爲大根堆
HeapAdjust(num,0,i);
}
}
int main()
{
int i;
int num[]={8,5,7,12,48,36,4};
HeapSort(num,sizeof(num)/sizeof(int));
for(i=0;i<sizeof(num)/sizeof(int);i++)
{
printf("%d\t",num[i]);
}
printf("\n");
}