【排序】選擇排序(直接選擇排序、堆排序)

選擇排序(Selection Sort)的基本思想是:每一趟從待排序的記錄中選出關鍵字最小的記錄,順序放在已排好序的子文件的最後,直到全部記錄排序完畢。

     常用的選擇排序方法有直接選擇排序堆排序


直接選擇排序(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},對其進行堆排序。

    首先根據該數組元素構建一個完全二叉樹,得到

 
 然後需要構造初始堆,則從最後一個非葉節點開始調整(8->7->16),調整過程如下:

20和16交換後導致16不滿足堆的性質,因此需重新調整

這樣就得到了初始堆。

即每次調整都是從父節點、左孩子節點、右孩子節點三者中選擇最大者跟父節點進行交換(交換之後可能造成被交換的孩子節點不滿足堆的性質,因此每次交換之後要重新對被交換的孩子節點進行調整)。有了初始堆之後就可以進行排序了。

此時3位於堆頂不滿堆的性質,則需調整繼續調整

這樣整個區間便已經有序了。

從上述過程可知,堆排序其實也是一種選擇排序,是一種樹形選擇排序。只不過直接選擇排序中,爲了從R[1...n]中選擇最大記錄,需比較n-1次,然後從R[1...n-2]中選擇最大記錄需比較n-2次。事實上這n-2次比較中有很多已經在前面的n-1次比較中已經做過,而樹形選擇排序恰好利用樹形的特點保存了部分前面的比較結果,因此可以減少比較次數。

(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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章