常用的選擇排序方法有直接選擇排序和堆排序。
直接選擇排序(Straight Selection Sort)
1、直接選擇排序的基本思想n個記錄的文件的直接選擇排序可經過n-1趟直接選擇排序得到有序結果:
①初始狀態:無序區爲R[1..n],有序區爲空。
②第1趟排序
在無序區R[1..n]中選出關鍵字最小的記錄R[k],將它與無序區的第1個記錄R[1]交換,使R[1..1]和R[2..n]分別變爲記錄個數增加1個的新有序區和記錄個數減少1個的新無序區。
……
③第i趟排序
第i趟排序開始時,當前有序區和無序區分別爲R[1..i-1]和R[i..n](1≤i≤n-1)。該趟排序從當前無序區中選出關鍵字最小的記錄R[k],將它與無序區的第1個記錄R[i]交換,使R[1..i]和R[i+1..n]分別變爲記錄個數增加1個的新有序區和記錄個數減少1個的新無序區。
這樣,n個記錄的文件的直接選擇排序可經過n-1趟直接選擇排序得到有序結果。
2、直接選擇排序的過程【參見動畫演示】
3、算法描述
直接選擇排序的具體算法如下:
void SelectSort(SeqList R)
{
int i,j,k;
for(i=1;i<n;i++)
{//做第i趟排序(1≤i≤n-1)
k=i;
for(j=i+1;j<=n;j++) //在當前無序區R[i..n]中選key最小的記錄R[k]
if(R[j].key<R[k].key)
k=j; //k記下目前找到的最小關鍵字所在的位置
if(k!=i)
{ //交換R[i]和R[k]
R[0]=R[i];//R[0]作暫存單元
R[i]=R[k];
R[k]=R[0];
} //endif
} //endfor
} //SeleetSort
4、算法分析(1)關鍵字比較次數(如果能利用前n-1次比較所得信息,則可以減少以後各趟選擇排序中的比較次數)
無論文件初始狀態如何,在第i趟排序中選出最小關鍵字的記錄,需做n-i次比較,因此,總的比較次數爲:
n(n-1)/2=0(n2)
(2)記錄的移動次數
當初始文件爲正序時,移動次數爲0
文件初態爲反序時,每趟排序均要執行交換操作,總的移動次數取最大值3(n-1)。
直接選擇排序的平均時間複雜度爲O(n2)。
(3)直接選擇排序是一個就地排序
(4)穩定性分析
直接選擇排序是不穩定的
【例】反例[2,2,1]
堆排序
1、 堆(n個元素序列{k1,k2...ki...kn})
我們把堆看成一棵完全二叉樹(其實就是),其任何一非葉節點滿足性質:
(1)ki≤K2i且ki≤K2i+1 或(2)Ki≥K2i且ki≥K2i+1
(1≤i≤ (n/2) )
即任何一非葉節點的關鍵字不大於或者不小於其左右孩子節點的關鍵字。
【例】關鍵字序列(10,15,56,25,30,70)和(70,56,30,25,15,10)分別滿足堆性質(1)和(2),
故它們均是堆,其對應的完全二叉樹分別如小根堆示例和大根堆示例所示。
2、大根堆(序列遞增)和小根堆(遞減)
根結點(亦稱爲堆頂)的關鍵字是堆裏所有結點關鍵字中最小者的堆稱爲小根堆。
根結點(亦稱爲堆頂)的關鍵字是堆裏所有結點關鍵字中最大者,稱爲大根堆。
注意:
①堆中任一子樹亦是堆。
②以上討論的堆實際上是二叉堆(Binary Heap),類似地可定義k叉堆。
3、大根堆思想(小根堆請自行分析)
(1)假設現有一個R[1....n]的大根堆(無序區是R[1...n],沒有有序區),根據大根堆的性質,我們知道R[1].key是最大的。
所以我們做一下操作:
① 將R[1]和無序區的最後一個記錄R[n]交換,由此得到新的無序區R[1..n-1]和有序區R[n],且滿足R[1..n-1].keys≤R[n].key
② 由於交換後新的根R[1]可能違反堆性質,故應將當前無序區R[1..n-1]調整爲堆。
③ 反覆執行①② 直到無序區只有一個元素爲止。
所以,我們的第一步是,將R[1....n]構造爲堆。
最後,對於堆排序,最重要的兩個操作就是構造初始堆和調整堆,其實構造初始堆事實上也是調整堆的過程,只不過構造初始堆是對所有的非葉節點都進行調整。
注意:在任何時刻,堆排序中無序區總是在有序區之前,且有序區是在原向量的尾部由後往前逐步擴大至整個向量爲止。
下面舉例說明:
給定一個整形數組a[]={16,7,3,20,17,8},對其進行堆排序。
首先根據該數組元素構建一個完全二叉樹,得到
20和16交換後導致16不滿足堆的性質,因此需重新調整
這樣就得到了初始堆。
此時3位於堆頂不滿堆的性質,則需調整繼續調整
這樣整個區間便已經有序了。
(3)堆排序的算法:
//對R[1..n]進行堆排序,R[0]做暫存單元
void HeapSort(HeapType& R)
{
//先將R[1-n]建成初始堆
for (int i = R.length/2; i>0; i-- )
{//對每一個非子葉結點進行堆調整
HeapAdjust(R,i,R.length)
}
for(int i = R.length; i>1; i--)
{ //對當前無序區R[1..i]進行堆排序,共做n-1趟。
R[0]=R[1];//將堆頂和堆中最後一個記錄交換
R[1]=R[i];
R[i]=R[0];
HeapAdjust(R,1,i-1);//將R[1..i-1]重新調整爲堆,僅有R[1]可能違反堆性質
} //endfor
} //HeapSort
(4)堆調整算法:
//調整堆,R是數據類型,s是要調整的結點,m是無序區的最後一位
void HeapAdjust(HeapType& R,int s,int m)
{//已知R[s..m].key中除了R[s].key之外均滿足堆的定義
//所以,在初始堆的時候,要從下往上對所有非子葉結點進行堆調整
R[0]=R[s];//將R[s]先保存在R[0]上
for (int j=2*s; j<=m; j*=2)
{
if (j<m && R[j].key<R[j+1].key)
j++; //將j指向左右孩子中較大的一個
if (R[0].key > R[j].key)
break; //如果順序正確,則結束調整
R[s]=R[j]; //如果錯誤,則交換位置
s = j; //然後繼續調整交換過的結點
}
R[s] = R[0]; //最後,插入真確位置
}
5、大根堆排序實例【動畫演示】。
6、 算法分析
堆排序的時間,主要由建立初始堆和反覆重建堆這兩部分的時間開銷構成。
堆排序的最壞時間複雜度爲O(nlgn)。堆排序的平均性能較接近於最壞性能。
由於建初始堆所需的比較次數較多,所以堆排序不適宜於記錄數較少的文件。
堆排序是就地排序,輔助空間爲O(1),
它是不穩定的排序方法。
部分資源摘自http://www.cnblogs.com/dolphin0520/archive/2011/10/06/2199741.html