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較小時較好!

代碼:

  1. void select_sort(int *a, int n)  
  2. {  
  3.     for(inti = 1; i <= n; i++) {  
  4.         intmin_pos = i;  
  5.         for(intj = i+1; j <= n; j++)  
  6.             if(a[j] < a[min_pos])  
  7.                 min_pos = j;  
  8.                                                                                                                                                                                                                     
  9.         if(min_pos != i) {  
  10.             inttemp = a[i];  
  11.             a[i] = a[min_pos];  
  12.             a[min_pos] = temp;  
  13.         }     
  14.     }     
  15. }  

2. 冒泡排序

基本思想:顧名思義,每一趟都通過相鄰元素兩兩比較,通過交換將較小的元素往前移動,一趟下來就可以將最小的元素(氣泡)移動到最前面。一般會加一個標誌flag,若一趟掃描沒有任何元素交換,則說明序列已經有序,flag爲false,直接退出。

特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),穩定排序(因爲比較和交換都是兩相鄰元素,相等時不交換),n較小時較好!

代碼:

  1. void bubble_sort(int *a, int n)  
  2. {  
  3.     boolflag =false;  
  4.     for(inti = 1; i <= n; i++) {  
  5.         for(intj = n; j > i; j--)  
  6.             if(a[j] < a[j-1]) {  
  7.                 inttemp = a[j];  
  8.                 a[j] = a[j-1];  
  9.                 a[j-1] = temp;  
  10.                 flag =true;  
  11.             }  
  12.         if(!flag)  
  13.             return;  
  14.     }  
  15. }  

3. 插入排序

基本思想:假定一個已排好序的序列和一個元素,只需將該元素從序列末尾向前比較,找到第一個小於它的序列元素,排在其之後即可。思想類似於玩撲克牌時整理牌面。

特點:平均時間複雜度O(n^2),最壞時間複雜度O(n^2),額外空間O(1),穩定排序(比較元素和序列時,找到序列中相等元素的話,排在其之後),序列大部分已排好序時(時間複雜度可提升至O(n))較好!

代碼:

  1. void insert_sort(int *a, int n)  
  2. {  
  3.     inttemp;  
  4.     for(inti =2; i <= n; i++) {  
  5.         intj = i - 1;  
  6.         temp = a[i];  
  7.         while(j >= 1) {  
  8.             if(a[j] > temp) {  
  9.                 a[j+1] = a[j];  
  10.                 j--;  
  11.             }else  
  12.                 break;  
  13.         }  
  14.         a[j+1] = temp;  
  15.     }  
  16. }  

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),不穩定排序(相等元素在不同組裏,交換後相對順序可能改變)!

代碼:

  1. void shell_sort(int *a, int n)  
  2. {   //我這裏步長爲5、3、1,僅爲舉例  
  3.     for(intgap = 5; gap > 0; gap -= 2)  
  4.         for(inti = gap + 1; i <=n; i++) {  
  5.             intj = i - gap;  
  6.             inttemp = a[i];  
  7.             while(j >= 1) {  
  8.                 if(a[j] > temp) {  
  9.                     a[j + gap] = a[j];  
  10.                     j -= gap;  
  11.                 }else  
  12.                     break;  
  13.             }  
  14.             a[j+gap] = temp;  
  15.         }  
  16. }  

5. 歸併排序

基本思想:分治的思想,就是用遞歸先將序列分解成只剩一個元素的子序列,然後逐漸向上進行合併,每次合併過程就是將兩個內部已排序的子序列進行合併排序,只需O(n)時間。

特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n*logn),額外空間O(n)(另外需要一個數組),穩定排序,當n較大時較好(當也不能太大,用了遞歸就要考慮棧溢出)!

代碼:

  1. int b[MAX] = {0};  
  2.                                                                                                                                  
  3. void merge(int *a,intlow, int mid, inthigh)  
  4. {  
  5.     inti = low, j = mid + 1;//左邊和右邊的初始位置  
  6.     intk = i;  
  7.     while(i <= mid && j <= high) {  
  8.         if(a[i] <= a[j]) {  
  9.             b[k++] = a[i];  
  10.             i++;  
  11.         }else{  
  12.             b[k++] = a[j];  
  13.             j++;  
  14.         }  
  15.     }  
  16.     while(i <= mid){  
  17.         b[k++] = a[i++];  
  18.     }  
  19.     while(j <= high){  
  20.         b[k++] = a[j++];  
  21.     }  
  22.                                                                                                                                  
  23.     for(intx = 1, i = low; x <= high-low+1; x++, i++)  
  24.         a[i] = b[i];  
  25. }  
  26.                                                                                                                                  
  27. voidmerge_sort(int*a,int low, int high)  
  28. {  
  29.     intmid;  
  30.     if(low < high) {  
  31.         mid = (low + high) / 2;  
  32.         merge_sort(a, low, mid);  
  33.         merge_sort(a, mid+1, high);  
  34.         merge(a, low, mid, high);  
  35.     }  
  36. }  

6. 堆排序

基本思想:利用最大堆的性質——父節點擁有最大值,所以不斷的將堆的根節點與最後節點交換,減小堆長度,然後再恢復堆性質,堆排序主要就是建立最大堆和不斷恢復堆性質兩個過程。堆排序不需要用到遞歸,所以適合海量數據處理,同時堆還可以用於優先級隊列。

特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n*logn),額外空間O(1),不穩定排序(涉及根節點與最後節點的交換,可能會破壞兩相等元素的相對位置!),當n較大時較好(海量數據)!

代碼:

  1. void max_heapify(int *a, int p, int n)  
  2. {  
  3.     intleft = 2 * p;  
  4.     intright = 2 * p + 1;  
  5.     intlarge = p;   
  6.     if(left <= n && a[left] > a[p])  
  7.         large = left;  
  8.     if(right <= n && a[right] > a[large])  
  9.         large = right;  
  10.                                                                                                                   
  11.     if(large != p) {  
  12.         inttemp = a[p];  
  13.         a[p] = a[large];  
  14.         a[large] = temp;  
  15.         max_heapify(a, large, n);  
  16.     }  
  17. }  
  18.                                                                                                       
  19. voidheap_sort(int*a,int n)  
  20. {  
  21.     //build_max_heap  
  22.     for(inti = n/2; i > 0; i--)  
  23.         max_heapify(a, i, n);  
  24.                                                                                                       
  25.     inttemp;  
  26.     while(n > 1){  
  27.         temp = a[n];  
  28.         a[n] = a[1];  
  29.         a[1] = temp;  
  30.                                                                                                               
  31.         --n;  
  32.         max_heapify(a, 1, n);  
  33.     }  
  34. }  

7. 快速排序

基本思想:快排是目前使用最多的排序算法,每次都是先選擇一個位置的元素(可以爲序列的最左或最右位置)作爲中間值,將比其小的元素放在其左邊,比其大的元素放在右邊,然後遞歸對其左邊和右邊的子序列進行相同操作,直到子序列爲單個元素。

特點:平均時間複雜度O(n*logn),最壞時間複雜度O(n^2)(序列基本有序時,退化爲冒泡排序),額外空間O(logn),不穩定排序(舉例:序列爲 5 3 3 4 3 8 9 10 11, 現在中樞元素5和3(第5個元素,下標從1開始計)交換就會把元素3的穩定性打亂),當n較大時較好(當也不能太大,用了遞歸就要考慮棧溢出)!

代碼:

  1. void quick_sort(int *a, int p, int r)  
  2. {  
  3.     if(p < r) {  
  4.         inttemp;  
  5.         intx = a[r];  
  6.         inti = p - 1;  
  7.         for(intj = p; j < r; j++)  
  8.             if(a[j] < x) {  
  9.                 i++;  
  10.                 temp = a[j];  
  11.                 a[j] = a[i];  
  12.                 a[i] = temp;  
  13.             }  
  14.                                                                                   
  15.         temp = a[i+1];  
  16.         a[i+1] = a[r];  
  17.         a[r] = temp;  
  18.         quick_sort(a, p, i);  
  19.         quick_sort(a, i+2, r);  
  20.     }  
  21. }  

8. 計數排序

基本思想:假定輸入是有一個小範圍內的整數構成的(比如年齡等),利用額外的數組去記錄元素應該排列的位置,思想比較簡單,看代碼即可瞭解。

特點:在一定限制下時間複雜度爲O(n),額外空間O(n)(需要兩個數組),穩定排序!

代碼:

  1. int b[MAX] = {0};  
  2. int c[MAX] = {0};  
  3.                                                              
  4. void counting_sort(int *a, int n)  
  5. {  
  6.                                                              
  7.     for(inti=1; i <= n; i++)  
  8.         c[a[i]]++;   //c[i]包含等於i的元素個數  
  9.                                                              
  10.     for(inti=1; i < MAX; i++)  
  11.         c[i] += c[i-1];//c[i]包含小於等於i的元素個數  
  12.                                                              
  13.     for(inti = n; i>0; i--){  
  14.         b[c[a[i]]] = a[i];  
  15.         c[a[i]]--;  
  16.     }  
  17.     for(inti = 1; i <=n; i++)  
  18.         a[i] = b[i];  
  19. }  

9. 基數排序

基本思想:只適用於整數排序,確定序列中元素的最大位數d,只要進行d次循環,從低位開始根據相應位置的數進行排序。(我的代碼中具體排序是參考了計數排序,數據結構中還可以用鏈式相關的方法)。

特點:在一定限制下時間複雜度爲O(n),額外空間O(n)(需要兩個數組),穩定排序!

代碼:

  1. int b[MAX] = {0};  
  2. int counter[10] = {0};  
  3. int get_value(int v, int d) //獲取第d位上的值  
  4. {  
  5.     for(inti = 1; i < d; i++)   
  6.         v = v/10;  
  7.     returnv%10;  
  8.                                         
  9. }  
  10. //只能排序d位的十進制數  
  11. voidradix_sort(int*a,int n, int d)  
  12. {  
  13.     intx;  
  14.     for(intk = 1; k <= d; k++) {  
  15.         for(inti = 0; i < 10; i++)  
  16.             counter[i] = 0;//注意,一定要清零  
  17.         for(inti = 1; i <= n; i++) {  
  18.             x = get_value(a[i], k);  
  19.             counter[x]++;  
  20.         }  
  21.                                                 
  22.         for(inti = 1; i < 10; i++)  
  23.             counter[i] += counter[i-1];  
  24.         for(inti = n; i > 0; i--) {  
  25.             x = get_value(a[i], k);  
  26.             b[counter[x]] = a[i];  
  27.             counter[x]--;  
  28.         }  
  29.         for(inti = 1; i <= n; i++)  
  30.             a[i] = b[i];  
  31.     }  
  32. }  

排序總結

穩定性:選擇排序、快速排序、希爾排序、堆排序不是穩定的排序算法,而冒泡排序、插入排序、歸併排序和基數排序是穩定的排序算法。

快速排序算法使用最廣泛,大數據量時適合使用快速排序、歸併排序和堆排序,需要O(n)時間複雜度時(當然要考慮數值範圍的限制),可以考慮使用計數排序、基數排序、桶排序(上面未介紹,思想很簡單,假設數據分佈均勻!)等。

最後是我用來測試排序算法的main函數,非常簡單!

  1. #include <iostream>  
  2. usingnamespacestd;  
  3.                               
  4. constintMAX = 255;  
  5.                           
  6. int main ()  
  7. {  
  8.     intn;  
  9.     inta[MAX];  
  10.     cin >> n;  
  11.     for(inti = 1; i <= n; i++)  
  12.         cin >> a[i];  
  13.                           
  14.     cout <<"Before sort:";  
  15.     for(inti = 1; i <= n; i++)  
  16.         cout << a[i] <<" ";  
  17.     cout << endl;  
  18.     //radix_sort(a, n, 2);  
  19.     //select_sort(a, n);  
  20.     //insert_sort(a, n);  
  21.     //bubble_sort(a, n);  
  22.     //quick_sort(a, 1, n);  
  23.     //heap_sort(a, n);  
  24.     //merge_sort(a, 1, n);  
  25.     //counting_sort(a, n);  
  26.     //shell_sort(a, n);  
  27.     cout <<"Sort:";  
  28.     for(inti = 1; i <= n; i++)  
  29.         cout << a[i] <<" ";  
  30.     cout << endl;  
  31.     return0;  
  32. }  


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