9種排序算法總結
排序算法可以說是計算機專業學生要學習的最基礎的算法,但其實也是最重要的,現在大部分互聯網公司筆試麵試也都會涉及到排序算法的知識。除了了解思想之外,還應該動手寫一寫,分析一些具體思路、時間複雜度、空間複雜度和穩定性等。
我們麵試討論小分隊也簡單討論了一下排序算法,為了加深記憶,我自己也動手寫了一些代碼(Linux平台寫的,自己測試是通過了),並做一些分析(由於水平較水,代碼可能有誤!)。
9種排序算法分別為:選擇排序、冒泡排序、插入排序、希爾排序、歸並排序、堆排序、快速排序、計數排序、基數排序!
1. 選擇排序
基本思想:從第一個位置開始依次選擇該位置的元素,第i次掃描就可以選出第i小的元素,思想很簡單,現在用的較少。
特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),不穩定排序(舉例:序列5 8 5 2 9, 第一遍選擇第1個元素5會和2交換,原序列中2個5的相對前後順序就被破壞了),n較小時較好!
代碼:
void select_sort(int *a, int n) { for(inti = 1; i <= n; i++) { intmin_pos = i; for(intj = i+1; j <= n; j++) if(a[j] < a[min_pos]) min_pos = j; if(min_pos != i) { inttemp = a[i]; a[i] = a[min_pos]; a[min_pos] = temp; } } }
2. 冒泡排序
基本思想:顧名思義,每一趟都通過相鄰元素兩兩比較,通過交換將較小的元素往前移動,一趟下來就可以將最小的元素(氣泡)移動到最前麵。一般會加一個標誌flag,若一趟掃描沒有任何元素交換,則說明序列已經有序,flag為false,直接退出。
特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),穩定排序(因為比較和交換都是兩相鄰元素,相等時不交換),n較小時較好!
代碼:
void bubble_sort(int *a, int n) { boolflag =false; for(inti = 1; i <= n; i++) { for(intj = n; j > i; j--) if(a[j] < a[j-1]) { inttemp = a[j]; a[j] = a[j-1]; a[j-1] = temp; flag =true; } if(!flag) return; } }
3. 插入排序
基本思想:假定一個已排好序的序列和一個元素,隻需將該元素從序列末尾向前比較,找到第一個小於它的序列元素,排在其之後即可。思想類似於玩撲克牌時整理牌麵。
特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),穩定排序(比較元素和序列時,找到序列中相等元素的話,排在其之後),序列大部分已排好序時(時間複雜度可提升至O(n))較好!
代碼:
void insert_sort(int *a, int n) { inttemp; for(inti =2; i <= n; i++) { intj = i - 1; temp = a[i]; while(j >= 1) { if(a[j] > temp) { a[j+1] = a[j]; j--; }else break; } a[j+1] = temp; } }
4. 希爾排序
基本思想:插入排序的升級版(根據其特點:序列大部分已排好序時效率很高),將數據分為不同的組,先對每一組進行排序,然後對所有元素進行一次排序(即最後步長必須為1),步長的選擇是遞減的,比如5、3、1,現在一般使用D.E.Knuth分組方法(n很大是,用h(n+1)=3h(n)+1來分組,即1、4、13......)。
特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n^s)(1<s<2),額外空間O(1),不穩定排序(相等元素在不同組裏,交換後相對順序可能改變)!
代碼:
void shell_sort(int *a, int n) { //我這裏步長為5、3、1,僅為舉例 for(intgap = 5; gap > 0; gap -= 2) for(inti = gap + 1; i <=n; i++) { intj = i - gap; inttemp = a[i]; while(j >= 1) { if(a[j] > temp) { a[j + gap] = a[j]; j -= gap; }else break; } a[j+gap] = temp; } }
5. 歸並排序
基本思想:分治的思想,就是用遞歸先將序列分解成隻剩一個元素的子序列,然後逐漸向上進行合並,每次合並過程就是將兩個內部已排序的子序列進行合並排序,隻需O(n)時間。
特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n*logn),額外空間O(n)(另外需要一個數組),穩定排序,當n較大時較好(當也不能太大,用了遞歸就要考慮棧溢出)!
代碼:
int b[MAX] = {0}; void merge(int *a,intlow, int mid, inthigh) { inti = low, j = mid + 1;//左邊和右邊的初始位置 intk = i; while(i <= mid && j <= high) { if(a[i] <= a[j]) { b[k++] = a[i]; i++; }else{ b[k++] = a[j]; j++; } } while(i <= mid){ b[k++] = a[i++]; } while(j <= high){ b[k++] = a[j++]; } for(intx = 1, i = low; x <= high-low+1; x++, i++) a[i] = b[i]; } voidmerge_sort(int*a,int low, int high) { intmid; if(low < high) { mid = (low + high) / 2; merge_sort(a, low, mid); merge_sort(a, mid+1, high); merge(a, low, mid, high); } }
6. 堆排序
基本思想:利用最大堆的性質——父節點擁有最大值,所以不斷的將堆的根節點與最後節點交換,減小堆長度,然後再恢複堆性質,堆排序主要就是建立最大堆和不斷恢複堆性質兩個過程。堆排序不需要用到遞歸,所以適合海量數據處理,同時堆還可以用於優先級隊列。
特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n*logn),額外空間O(1),不穩定排序(涉及根節點與最後節點的交換,可能會破壞兩相等元素的相對位置!),當n較大時較好(海量數據)!
代碼:
void max_heapify(int *a, int p, int n) { intleft = 2 * p; intright = 2 * p + 1; intlarge = p; if(left <= n && a[left] > a[p]) large = left; if(right <= n && a[right] > a[large]) large = right; if(large != p) { inttemp = a[p]; a[p] = a[large]; a[large] = temp; max_heapify(a, large, n); } } voidheap_sort(int*a,int n) { //build_max_heap for(inti = n/2; i > 0; i--) max_heapify(a, i, n); inttemp; while(n > 1){ temp = a[n]; a[n] = a[1]; a[1] = temp; --n; max_heapify(a, 1, n); } }
7. 快速排序
基本思想:快排是目前使用最多的排序算法,每次都是先選擇一個位置的元素(可以為序列的最左或最右位置)作為中間值,將比其小的元素放在其左邊,比其大的元素放在右邊,然後遞歸對其左邊和右邊的子序列進行相同操作,直到子序列為單個元素。
特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n^2)(序列基本有序時,退化為冒泡排序),額外空間O(logn),不穩定排序(舉例:序列為 5 3 3 4 3 8 9 10 11, 現在中樞元素5和3(第5個元素,下標從1開始計)交換就會把元素3的穩定性打亂),當n較大時較好(當也不能太大,用了遞歸就要考慮棧溢出)!
代碼:
void quick_sort(int *a, int p, int r) { if(p < r) { inttemp; intx = a[r]; inti = p - 1; for(intj = p; j < r; j++) if(a[j] < x) { i++; temp = a[j]; a[j] = a[i]; a[i] = temp; } temp = a[i+1]; a[i+1] = a[r]; a[r] = temp; quick_sort(a, p, i); quick_sort(a, i+2, r); } }
8. 計數排序
基本思想:假定輸入是有一個小範圍內的整數構成的(比如年齡等),利用額外的數組去記錄元素應該排列的位置,思想比較簡單,看代碼即可了解。
特點:在一定限製下時間複雜度為O(n),額外空間O(n)(需要兩個數組),穩定排序!
代碼:
int b[MAX] = {0}; int c[MAX] = {0}; void counting_sort(int *a, int n) { for(inti=1; i <= n; i++) c[a[i]]++; //c[i]包含等於i的元素個數 for(inti=1; i < MAX; i++) c[i] += c[i-1];//c[i]包含小於等於i的元素個數 for(inti = n; i>0; i--){ b[c[a[i]]] = a[i]; c[a[i]]--; } for(inti = 1; i <=n; i++) a[i] = b[i]; }
9. 基數排序
基本思想:隻適用於整數排序,確定序列中元素的最大位數d,隻要進行d次循環,從低位開始根據相應位置的數進行排序。(我的代碼中具體排序是參考了計數排序,數據結構中還可以用鏈式相關的方法)。
特點:在一定限製下時間複雜度為O(n),額外空間O(n)(需要兩個數組),穩定排序!
代碼:
int b[MAX] = {0}; int counter[10] = {0}; int get_value(int v, int d) //獲取第d位上的值 { for(inti = 1; i < d; i++) v = v/10; returnv%10; } //隻能排序d位的十進製數 voidradix_sort(int*a,int n, int d) { intx; for(intk = 1; k <= d; k++) { for(inti = 0; i < 10; i++) counter[i] = 0;//注意,一定要清零 for(inti = 1; i <= n; i++) { x = get_value(a[i], k); counter[x]++; } for(inti = 1; i < 10; i++) counter[i] += counter[i-1]; for(inti = n; i > 0; i--) { x = get_value(a[i], k); b[counter[x]] = a[i]; counter[x]--; } for(inti = 1; i <= n; i++) a[i] = b[i]; } }
排序總結
穩定性:選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法,而冒泡排序、插入排序、歸並排序和基數排序是穩定的排序算法。
快速排序算法使用最廣泛,大數據量時適合使用快速排序、歸並排序和堆排序,需要O(n)時間複雜度時(當然要考慮數值範圍的限製),可以考慮使用計數排序、基數排序、桶排序(上麵未介紹,思想很簡單,假設數據分布均勻!)等。
最後是我用來測試排序算法的main函數,非常簡單!
#include <iostream> usingnamespacestd; constintMAX = 255; int main () { intn; inta[MAX]; cin >> n; for(inti = 1; i <= n; i++) cin >> a[i]; cout <<"Before sort:"; for(inti = 1; i <= n; i++) cout << a[i] <<" "; cout << endl; //radix_sort(a, n, 2); //select_sort(a, n); //insert_sort(a, n); //bubble_sort(a, n); //quick_sort(a, 1, n); //heap_sort(a, n); //merge_sort(a, 1, n); //counting_sort(a, n); //shell_sort(a, n); cout <<"Sort:"; for(inti = 1; i <= n; i++) cout << a[i] <<" "; cout << endl; return0; }
排序算法基本上就總結如上,告誡自己,不能死記硬背!要理解思想,同時要注意實現上的一些技巧!
最後更新:2017-04-03 20:51:30