前言
1、選擇排序(Selection Sort)的基本思想
選擇排序的基本思想:每趟從待排序的記錄中選出關鍵字最小的記錄,順序放在已排好序的子文件的最后,直到全部記錄排序完畢。
常用的選擇排序方法有直接選擇排序和堆排序。
一、直接選擇排序
1、直接選擇排序的基本思想
n個記錄的文件的直接選擇排序可經過n-1趟直接選擇排序得到有序結果。
第1趟從R[0]~R[n-1]中選取最小值,與R[0]交換;
第2趟從R[1]~R[n-1]中選取最小值,與R[1]交換;
第i趟從R[i-1]~R[n-1]中選取最小值,與R[i-1]交換;
直接選擇排序和直接插入排序類似,都將數據分為有序區和無序區,所不同的是直接插入排序是將無序區的第一個元素直接插入到有序區以形成一個更大的有序區,而直接選擇排序是從無序區選擇一個最小的元素直接放到有序區的最后。
2、直接選擇排序算法的核心
- 確定待排序的數據R[0…n]
- 確定需比較的數據R[i…n]
- 每次找出最小值,進行交換
3、直接選擇排序算法的示例
給定一組數據[8 3 2 5]
第一次交換 8<->2
2 3 8 5
第二次交換 3<->3
2 3 8 5
第三次交換 8<->5
2 3 5 8
4、直接選擇排序算法實現
void SelectSort(int *arr, int len)
{
int i,j; // 為循環做准
int iMin; // 存儲每次最小值
int temp; // 作為臨時存儲值
for (i=0; i<len-1; i++) // 進行len-1趟比較即可
{
iMin = i; // 存儲每次最小值
for (j=i+1; j<len; j++) // 第i次需要與之比較的數據
{
if (arr[iMin]>arr[j])
{
iMin = j; // 記錄最小值的位置
}
}
temp = arr[i]; // 交換
arr[i] = arr[iMin];
arr[iMin] = temp;
}
}
5、直接選擇排序算法的性能分析
直接選擇排序是一個就地排序,但是直接選擇排序不是穩定的。直接選擇排序的平均時間復雜度是,而它的最壞情況時間復雜度也是
。
參考:http://blog.csdn.net/morewindows/article/details/6671824
二、堆排序
1、堆的概念
堆可以視為一棵完全的二叉樹,完全二叉樹的一個“優秀”的性質是,除了最底層之外,每一層都是滿的,這使得堆可以利用數組來表示,每一個結點對應數組中的一個元素。
數組與堆之間的關系
二叉堆一般分為兩種:最大堆和最小堆。
堆中每個父結點的元素值都大於等於其孩子結點(如果存在),這樣的堆就是一個最大堆。
結點與數組索引關系
對於給定的某個結點的下標i,可以很容易的計算出這個結點的父結點、孩子結點的下標,而且計算公式很漂亮很簡約:
2、用大根堆排序的基本思想
1)先將初始文件R[1…n]建成一個大根堆,此堆為初始的無序區
2)再將關鍵字最大的記錄R[1](即堆頂)和無序區的最后一個記錄R[n]交換,由此得到新的無序區R[1..n-1]和有序區R[n],且滿足R[1…n-1].keys<=R[n].key
3)由於交換后新的根R[1]可能違反堆性質,故應將當前無序區R[1…n-1]調整為堆。
然后再次將R[1..n-1]中關鍵字最大的記錄R[1]和該區間的最后一個記錄R[n-1]交換,由此得到新的無序區R[1..n-2]和有序區R[n-1..n],且仍滿足關系R[1…n-2].keys<=R[n-1…n].keys,同樣要將R[1..n-2]調整為堆。
………
直到無序區只有一個元素為止。
2、堆排序的核心
- 建堆
- 調整堆
- 堆排序
3、堆排序算法的示例
既然是堆排序,自然需要先建立一個堆,而建堆的核心內容是調整堆,使二叉樹滿足堆的定義(每個結點的值都不大於其父結點的值)。調整堆的過程應該從最后一個非葉子結點開始,假設有數組A={1,3,4,5,7,2,6,8,0}。那么調整堆的過程如下圖,數組下標從0開始,A[3]=5開始。分別與左孩子和右孩子比較大小,如果A[3]最大,則不用調整,否則和孩子中的值最大的那一個交換位置,在圖1中是A[7]>A[3]>A[8],所以A[3]與A[7]對換,從圖1.1轉到圖1.2。
注意:使用最小堆排序后是遞減數組,要得到遞增數組,可以使用最大堆。
1、堆化數組
如何對一個數據進行堆化操作。要一個個的從數組中取出數據來建立堆嗎?不用,先看一個數組,如下圖:
很明顯,對葉子結點來說,可以認為它已經是一個合法的堆,即20,60,65,4,49都 分別是一個合法的堆。只要從A[4]=50開始向下調整堆就可以了。然后再取A[3]=30,A[2]=17,A[1]=12,A[0]=9分別做一次向下調整操作就可以了。
由於堆也是用數組模擬的,故堆化數組后,第一次將A[0]與A[n - 1]交換,再對A[0…n-2]重新恢復堆。第二次將A[0]與A[n – 2]交換,再對A[0…n - 3]重新恢復堆,重復這樣的操作直到A[0]與A[1]交換。由於每次都是將最大的數據並入到后面的有序區間,故操作完成后整個數組就有序了。有點類似於直接選擇排序。
4、堆排序算法的實現
/**
功能:調整為最大堆
思路:
1、找到左右孩子中最大的值
2、判斷是否賦給父結點
*/
void AdjustMaxHeap(int a[], int i, int n)
{
int j, temp;
temp = a[i]; // 保存第i個結點值
j = 2 * i + 1; // 第i個結點的左孩子
while (j < n)
{
if (j + 1 < n && a[j + 1] > a[j]) // 在左右孩子中找最大的
j++; // 如果右孩子大,j就變成右孩子序號
if (a[j] <= temp) // 如果左右孩子的值都不大於父結點,就終止
break;
a[i] = a[j]; // 把較大的子結點往上移動,替換它的父結點
i = j; // 為下次循環做准備
j = 2 * i + 1;
}
a[i] = temp; // 保存父結點的值,此時i是j的值,表示孩子的值
}
/**
功能:建立初始堆(最大堆)
思路:
1、要想將初始文件R[1..n]調整為一個大根堆,就必須將它
所對應的完全二叉樹中以每一結點為根的子樹都調整為堆。
2、顯然只有一個結點的樹是堆,而在完全二叉樹中,所有序號i>[n/2]([]向下取整)的
結點都是葉子,因此以這些結點為根的子樹均已是堆。
3、我們只需依次將以序號為[n/2],[n/2]-1,...1的結點作為根的子樹都調整為堆即可。
4、由於c語言中數組下標從0開始,因此,第3點的序號改為[n/2]-1,[n/2]-2,...0。
*/
void MakeMaxHeap(int a[], int n)
{
for (int i = n / 2 - 1; i >= 0; i--) // 根結點序號
AdjustMaxHeap(a, i, n); // 為每個根結點調整,保存最大堆性質
}
/**
1、建立初始堆
2、每一趟最后一個數都與a[0]交換,並重新調整堆,
使其保持堆的特性。
*/
void MaxHeapSort(int a[], int n)
{
MakeMaxHeap(a,n); // 建立初始堆
for (int i = n - 1; i >= 1; i--) // 對當前無序區a[1..i]進行堆排序,共做n-1趟
{
int temp = a[i]; // 將堆頂和堆中最后一個記錄交換
a[i] = a[0];
a[0] = temp;
AdjustMaxHeap(a, 0, i); // 將a[0..i]重新調整為最大堆,僅有a[0]可能違反堆性質
}
}
5、堆排序算法的性能分析
堆排序是不穩定,但是它的平均時間復雜度和最差情況時間復雜度都是O(nlogn)。