本文將通過動態演示+代碼的形式系統地總結十大經典排序算法。
排序算法
算法分類 —— 十種常見排序算法可以分為兩大類:
- 比較類排序:通過比較來決定元素間的相對次序,由于其時間復雜度不能突破O(nlogn),因此也稱為非線性時間比較類排序。
- 非比較類排序:不通過比較來決定元素間的相對次序,它可以突破基于比較排序的時間下界,以線性時間運行,因此也稱為線性時間非比較類排序。
算法復雜度
排序算法 | 平均時間復雜度 | 最差時間復雜度 | 空間復雜度 | 數據對象穩定性 |
---|---|---|---|---|
冒泡排序 | O(n2) | O(n2) | O(1) | 穩定 |
選擇排序 | O(n2) | O(n2) | O(1) | 數組不穩定、鏈表穩定 |
插入排序 | O(n2) | O(n2) | O(1) | 穩定 |
快速排序 | O(n*log2n) | O(n2) | O(log2n) | 不穩定 |
堆排序 | O(n*log2n) | O(n*log2n) | O(1) | 不穩定 |
歸并排序 | O(n*log2n) | O(n*log2n) | O(n) | 穩定 |
希爾排序 | O(n*log2n) | O(n2) | O(1) | 不穩定 |
計數排序 | O(n+m) | O(n+m) | O(n+m) | 穩定 |
桶排序 | O(n) | O(n) | O(m) | 穩定 |
基數排序 | O(k*n) | O(n2) | 穩定 |
1. 冒泡排序
算法思想:
- (1)比較相鄰的元素。如果第一個比第二個大,就交換它們兩個;
- (2)對每一對相鄰元素作同樣的工作,從開始第一對到結尾的最后一對,這樣在最后的元素應該會是最大的數;
- (3)針對所有的元素重復以上的步驟,除了最后一個;
- (4)重復步驟1~3,直到排序完成。
代碼:
void bubbleSort(int a[], int n)
{
for(int i =0 ; i< n-1; ++i)
{
for(int j = 0; j < n-i-1; ++j)
{
if(a[j] > a[j+1])
{
int tmp = a[j] ; //交換
a[j] = a[j+1] ;
a[j+1] = tmp;
}
}
}
}
2. 選擇排序
算法思想:
- (1)在未排序序列中找到最小(大)元素,存放到排序序列的起始位置;
- (2)從剩余未排序元素中繼續尋找最?。ù螅┰兀缓蠓诺揭雅判蛐蛄械哪?;
- (3)以此類推,直到所有元素均排序完畢;
代碼:
void selectionSort(int arr[], int n) {
int minIndex, temp;
for (int i = 0; i < n - 1; i++) {
minIndex = i;
for (var j = i + 1; j < n; j++) {
if (arr[j] < arr[minIndex]) { // 尋找最小的數
minIndex = j; // 將最小數的索引保存
}
}
temp = arr[i];
arr[i] = arr[minIndex];
arr[minIndex] = temp;
}
for (int k = 0; i < n; i++) {
printf("%d ", arr[k]);
}
}
3. 插入排序
算法思想:
- (1)從第一個元素開始,該元素可以認為已經被排序;
- (2)取出下一個元素,在已經排序的元素序列中從后向前掃描;
- (3)如果該元素(已排序)大于新元素,將該元素移到下一位置;
- (4)重復步驟3,直到找到已排序的元素小于或者等于新元素的位置;
- (5)將新元素插入到該位置后;
- (6)重復步驟2~5。
代碼:
void print(int a[], int n ,int i){
cout< ":";
for(int j= 0; j< 8; j++){
cout< " ";
}
cout<
算法分析:插入排序在實現上,通常采用in-place排序(即只需用到O(1)的額外空間的排序),因而在從后向前掃描過程中,需要反復把已排序元素逐步向后挪位,為最新元素提供插入空間。
4. 快速排序
快速排序的基本思想是通過一趟排序將待排記錄分隔成獨立的兩部分,其中一部分記錄的關鍵字均比另一部分的關鍵字小,則可分別對這兩部分記錄繼續進行排序,以達到整個序列有序。
算法思想:
- (1)選取第一個數為基準
- (2)將比基準小的數交換到前面,比基準大的數交換到后面
- (3)遞歸地(recursive)把小于基準值元素的子數列和大于基準值元素的子數列排序
代碼:
void QuickSort(vector< int >& v, int low, int high) {
if (low >= high) // 結束標志
return;
int first = low; // 低位下標
int last = high; // 高位下標
int key = v[first]; // 設第一個為基準
while (first < last)
{
// 將比第一個小的移到前面
while (first < last && v[last] >= key)
last--;
if (first < last)
v[first++] = v[last];
// 將比第一個大的移到后面
while (first < last && v[first] <= key)
first++;
if (first < last)
v[last--] = v[first];
}
//
v[first] = key;
// 前半遞歸
QuickSort(v, low, first - 1);
// 后半遞歸
QuickSort(v, first + 1, high);
}
5. 堆排序
堆排序(Heapsort)是指利用堆這種數據結構所設計的一種排序算法。堆積是一個近似完全二叉樹的結構,并同時滿足堆積的性質:即子節點的鍵值或索引總是小于(或者大于)它的父節點。
算法思想:
- (1)將初始待排序關鍵字序列(R1,R2….Rn)構建成大頂堆,此堆為初始的無序區;
- (2)將堆頂元素R[1]與最后一個元素R[n]交換,此時得到新的無序區(R1,R2,……Rn-1)和新的有序區(Rn),且滿足R[1,2…n-1]<=R[n];
- (3)由于交換后新的堆頂R[1]可能違反堆的性質,因此需要對當前無序區(R1,R2,……Rn-1)調整為新堆,然后再次將R[1]與無序區最后一個元素交換,得到新的無序區(R1,R2….Rn-2)和新的有序區(Rn-1,Rn)。不斷重復此過程直到有序區的元素個數為n-1,則整個排序過程完成。
代碼:
#include
#include
using namespace std;
// 堆排序:(最大堆,有序區)。從堆頂把根卸出來放在有序區之前,再恢復堆。
void max_heapify(int arr[], int start, int end) {
//建立父節點指標和子節點指標
int dad = start;
int son = dad * 2 + 1;
while (son <= end) { //若子節點在范圍內才做比較
if (son + 1 <= end && arr[son] < arr[son + 1]) //先比較兩個子節點指標,選擇最大的
son++;
if (arr[dad] > arr[son]) //如果父節點大于子節點代表調整完成,直接跳出函數
return;
else { //否則交換父子內容再繼續子節點與孫節點比較
swap(arr[dad], arr[son]);
dad = son;
son = dad * 2 + 1;
}
}
}
void heap_sort(int arr[], int len) {
//初始化,i從最后一個父節點開始調整
for (int i = len / 2 - 1; i >= 0; i--)
max_heapify(arr, i, len - 1);
//先將第一個元素和已經排好的元素前一位做交換,再從新調整(剛調整的元素之前的元素),直到排序完成
for (int i = len - 1; i > 0; i--) {
swap(arr[0], arr[i]);
max_heapify(arr, 0, i - 1);
}
}
int main() {
int arr[] = { 3, 5, 3, 0, 8, 6, 1, 5, 8, 6, 2, 4, 9, 4, 7, 0, 1, 8, 9, 7, 3, 1, 2, 5, 9, 7, 4, 0, 2, 6 };
int len = (int) sizeof(arr) / sizeof(*arr);
heap_sort(arr, len);
for (int i = 0; i < len; i++)
cout < < arr[i] < < ' ';
cout < < endl;
return 0;
}