經典的排序算法

經典的排序算法

這些天覆習了排序這個模塊,排序算法在程序員的日常工作中是必不可少的,有時候我們不知不覺就用到了排序,這是因爲高級語言系統已經比較完美的封裝和優化了排序算法,並且在筆試,面試等方面我們都能見到它的身影。下面結合那本大三的教材:嚴版的《數據結構》,來說一說這幾個經典的排序算法,如果有不對的歡迎指正!

首先我們還是先說基礎概念(按書上說的),萬變離不開概念,沒有概念沒有規矩,那可不行。

一、冒泡排序

二、直接選擇排序

三、直接插入排序

四、希爾排序

五、堆排序

六、歸併排序

七、快速排序

八、各種算法效率比拼

1:內部排序和外部排序(我們重點說內部排序(因爲我們最常用到))

內排序:在排序的時間數據對象全部存放在內存的排序。
外排序:在排序的時間對象個數太多,不能同時存放在內存,根據排序的要求,不斷在內、外存之間移動。

2:排序的方法

按所排列(移動交換)的方式不同,可歸納爲五類:插入排序、選擇排序、交換排序、歸併排序和分配排序(有的書上直接說基數)。
插入排序主要包括直接插入排序和希爾排序兩種;
選擇排序主要包括直接選擇排序和堆排序;
交換排序主要包括冒泡排序和快速排序;
歸併排序主要包括二路歸併(常用的歸併排序)。
分配排序主要包括箱排序和基數排序。

3:穩定性

穩定排序:假設在待排序的文件中,存在兩個或兩個以上的記錄具有相同的關鍵字,在用某種排序法排序後,若這些相同關鍵字的元素的相對次序仍然不變,則這種排序方法是穩定的。
冒泡,插入,基數,歸併屬於穩定排序;
選擇,快速,希爾,堆屬於不穩定排序。

4:圖表幫助大家記憶:

 各種排序算法
類別 排序方法 時間複雜度 穩定性
平均情況 最好情況 最壞情況
插入排序  直接插入
 O(n2)
 O(n)  O(n2)  穩定
 希爾排序
 O(n1.3)  O(n)  O(n2)  不穩定
 選擇排序
 直接選擇
 O(n2)  O(n2)  O(n2)  不穩定
 堆排序
 O(nlog2n)  O(nlog2n)  O(nlog2n)  不穩定
 交換排序
 冒泡排序
 O(n2)  O(n)  O(n2)  穩定
 快速排序
 O(nlog2n)  O(nlog2n)  O(n2)  不穩定
  歸併排序
O(nlog2n)  O(nlog2n)  O(nlog2n)  穩定 
  基數排序
O(d(r+n)) 
O(d(n+rd))   O(d(r+n))   穩定 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

一、冒泡排序

冒泡排序也叫起泡排序,大家都知道,學C語言那會的循環結構就那這個做的例子,冒泡排序過程很簡單,但是效率太低。
1:簡要過程:
首先將一個記錄的關鍵字和第二個記錄的關鍵字比較若爲逆序將兩個記錄交換,然後比較第二個記錄和第三個記錄的關鍵字....,直到n-1個記錄呢第n個記錄的關鍵字進行比較,上述過稱作一趟冒泡排序,然後進行第二趟,對n-1個記錄進行操作...(可以不安這種方法來,實際應用中可以逆序也可以正序)
2:排序效率:
如果開始的時候需要排序的元素是正序,則只要一趟,需要n-1次的關鍵字比較,如果爲逆序要進行n(n-1)/2次的比較,所以時間複雜度是O(n2),所以效率超級的低,一般不建議使用
3:排序過程圖:
4:具體算法實現(可以有多種形式):
複製代碼
 1 //冒泡排序 
 2 void bubbling(int arr[], int n){
 3     int i, j, temp;
 4     for (j = 0; j < n - 1; j++){ 
 5         for (i = 0; i < n - 1 - j; i++){
 6             if(arr[i] > arr[i + 1]){
 7                 temp = arr[i];
 8                 arr[i] = arr[i + 1];
 9                 arr[i + 1] = temp;
10             }
11         }
12     } 
13 }
複製代碼

 

二、直接選擇排序

是一種選擇排序,總的來說交換的少,比較的多,如果排序的數據是字符串的話比建議使用這種方法。

1:簡要過程:

首先找出最大元素,與最後一個元素交換(arr[n-1]),然後再剩下的元素中找最大元素,與倒數第二個元素交換....直到排序完成。

2:排序效率:

一趟排序通過n-1次的關鍵字比較,從n-i-1個記錄中選擇最大或者最小的元素並和第i個元素交換,所以平均爲n(n-1)/2,時間複雜度爲O(n2),也不是個省油的燈,效率還是比較低。

3:排序過程圖:(選擇小元素,圖片來源百度搜索)

4:具體算法實現:

複製代碼
 1 //直接選擇排序 
 2 void directDialing(int arr[], int n){  3     int i,j,k,num;  4     for(i = 0; i < n; i++){  5         num = 0;  6         for(j = 0; j < n-i; j++){  7             if(arr[j] > arr[num]){  8                 num = j;  9  } 10  } 11         k = arr[n-i-1]; 12         arr[n-i-1] = arr[num]; 13         arr[num] = k; 14  } 15 }
複製代碼

 

三、直接插入排序

是一種簡單的排序方法,算法簡潔,容易實現

1:簡要過程:

每步將一個待排序元素,插入到前面已經排序好的元素中,從而得到一個新的有序序列,直到最後一個元素加入進去即可。

2:排序效率:

空間上直接插入排序只需要一個輔助空間,時間上:花在比較兩個關鍵字的大小和移動記錄,逆序時比較次數最大:(n+2)(n-1)/2,平均:n2/4,所以直接插入排序時間複雜度也是O(n2)

3:排序過程圖:

4:具體算法實現:

複製代碼
 1 //直接插入排序
 2 void  insert(int arr[], int n){  3     int m,i,k;  4     for(i = 1; i < n; i++){  5         m = arr[i];  6         for(k = i-1; k >= 0; k--){  7             if(arr[k] > m){  8                 arr[k+1] = arr[k];  9             }else{ 10                 break; 11  } 12  } 13         arr[k+1] = m; 14  } 15 }
複製代碼
 
四、希爾排序
希爾排序,也叫縮小增量排序,是一種插入排序,和前三種不一樣它在時間效率上有很大的改進,所以這種排序方法纔是我們需要重點學習和掌握的,但是凡事都有利弊,容易的排序算法算法簡單,但是效率低下,反過來複雜的排序算法算法複雜,但是效率及其的出色。

 1:簡要過程:

選擇一個增量序列a1,a2,…,ak,其中ai>aj,ak=1;按增量序列個數k,對序列進行k 趟排序;每趟排序,根據對應的增量ai,將待排序列分割成若干長度爲m 的子序列,分別對各子表進行直接插入排序。僅增量因子爲1 時,整個序列作爲一個表來處理,表長度即爲整個序列的長度。
說白了其實就是:先取一個正整數d1<n,把所有序號相隔d1的數組元素放一組,組內進行直接插入排序;然後取d2<d1,重複上述分組和排序操作;直至di=1,即所有記錄放進一個組中排序爲止。

2:排序效率:

希爾排序的效率的確不低,大量的研究說時間複雜度爲O(n3/2)(書上說的在大量的試驗基礎上推出的)感覺有點玄乎,但是希爾排序效率確實很高,在後面我寫的有一個效率比拼大家可以看看。

3:排序過程圖:

 
 4:具體算法實現:
複製代碼
 1 //希爾排序 
 2 void Heer(int arr[], int n){  3     int i,j,k,num;  4     k = n/2;  5     while(k > 0){  6         for(i = k; i < n; i++){  7             num = arr[i];  8             for(j = i - k;j >= 0; j -= k){  9                 if(arr[j] > num){ 10                     arr[j+k] = arr[j]; 11                 }else{ 12                     break; 13  } 14  } 15             arr[j+k] = num; 16  } 17         k = k/2; 18  } 19 } 
複製代碼

 

五、堆排序

先說說堆的定義:n個元素的序列{k1,k2,. . . kn}當且僅當滿足以下條件時稱之爲堆:

ki <= k2i              ki >= k2i

k<= k2i+1          k>= k2i+1

若將這樣的序列對用的一維數組看成一個完全二叉樹則堆的定義看出:完全二叉樹非終節點的值均不大於(不小於)其左右兩個孩子的節點值。

大頂堆和小頂堆:堆頂元素取最小值的是小頂堆,取最大值的是大頂堆。

1:簡要過程:

(1) 建堆:對初始序列建堆的過程,就是一個反覆進行篩選的過程。n 個結點的完全二叉樹,則最後一個結點是第(既是程序中的n * 2 +1)個結點的子樹。篩選從第個結點爲根的子樹開始,該子樹成爲堆。之後向前依次對各結點爲根的子樹進行篩選,使之成爲堆,直到根結點。

(2) 調整堆:k個元素的堆,輸出堆頂元素後,剩下k-1 個元素。將堆底元素送入堆頂(最後一個元素與堆頂進行交換),堆被破壞,其原因僅是根結點不滿足堆的性質。將根結點與左、右子樹中較小元素的進行交換。若與左子樹交換:如果左子樹堆被破壞,即左子樹的根結點不滿足堆的性質,則重複調整堆,.若與右子樹交換,如果右子樹堆被破壞,即右子樹的根結點不滿足堆的性質。則重複調整堆。對不滿足堆性質的子樹進行上述交換操作,直到葉子結點,堆被建成。

2:排序效率:

堆排序的方法對記錄較少的文件並不值得提倡,但對於數據較大的文件還是很有效的,時間主要花在初始建堆和交換元素後調整堆上。對深度爲k的堆比較算法最多爲2(k-1)次,含有n個元素深度爲h的堆時總共進行的關鍵字比較不超過4n,由此可見最壞的情況下,時間複雜度爲O(nlogn)並且僅需要一個輔助控間,速度已經是相當快了。

3:排序過程圖(圖片來源百度,幫助理解):

4:具體算法實現:

複製代碼
 1 //堆調整 
 2 void adjust(int arr[], int n, int length){  3     int max,b;  4     while(n * 2 +1 <= length){//說明存在左節點 
 5         max = n * 2 + 1;  6         if(n * 2 + 2 <= length){//說明存在右節點 
 7             if(arr[max] < arr[n * 2 + 2]){  8                 max = n * 2 + 2; //跟新最小的值 
 9  } 10  } 11         if(arr[n] > arr[max]){ 12             break;//順序正確,不需要再調整 
13         }else{ 14             b = arr[n]; 15             arr[n] = arr[max]; 16             arr[max] = b; 17             n = max; 18  } 19  } 20 } 21 
22 //堆排序 
23 void stack(int arr[], int length){ 24     int i,k,m = 0; 25     for(i = length/2-1; i >=0; i--){ 26         adjust(arr,i,length-1); 27  } 28     //調整堆
29     for(i = length-1 ;i >= 0; i--){ 30         //調整後把最後一個和第一個交換,每次調整少一個元素,依次向前 
31         k = arr[i]; 32         arr[i] = arr[0]; 33         arr[0] = k; 34         adjust(arr,0,i-1); 35  } 36 }
複製代碼

 

六、歸併排序

將兩個和兩個以上的排序表合成一個新的有序表,歸併排序是分治法思想運用的一個典範。

1:簡要過程:

 將有 n個對象的原始序 列看作 n個有序子列,每個序列的長度爲1,從第一個子序列開始,把相鄰的子序列兩兩合併得到[n/2]個長度爲2或者是1的歸併項,(如果n爲奇數,則最後一個有序子序列的長度爲1),稱這一個過程爲一趟歸併排序。

然後重複上述過程指導得到一個長度爲n的序列爲止。

2:排序效率:

 一趟歸併排序的操作是:調用[n/2b]次算法,整個過程需要[log2n]趟,課件歸併排序時間複雜度是O(logn)

3:排序過程圖:

 

4:具體算法實現:

複製代碼
 1 //歸併排序合併函數 
 2 void mergeSoft(int a[], int first, int mid, int last, int temp[]){  3     int i = first, j = mid + 1;  4     int m = mid,   n = last;  5     int k = 0;  6     while (i <= m && j <= n){  7         if (a[i] <= a[j]){  8             temp[k++] = a[i++];  9         }else{ 10             temp[k++] = a[j++]; 11  } 12  } 13     while (i <= m){ 14         temp[k++] = a[i++]; 15  } 16     while (j <= n){ 17         temp[k++] = a[j++]; 18  } 19     
20     for (i = 0; i < k; i++){ 21         a[first + i] = temp[i]; 22  } 23 } 24 
25 //歸併排序遞歸拆分函數
26 void mergerSelf(int arr[], int left, int right, int arrTmp[]){ 27     if(left < right){ 28         int center = (left + right)/2; 29  mergerSelf(arr, left, center, arrTmp); 30         mergerSelf(arr, center+1, right, arrTmp); 31  mergeSoft(arr, left, center, right, arrTmp); 32  } 33 } 34 //歸併排序 
35 void merger(int arr[], int n){ 36     int *arrTmp = new int[n]; 37     mergerSelf(arr, 0, n - 1, arrTmp); 38     delete[] arrTmp; 39 } 
複製代碼

 

七、快速排序

快速排序是一種交換排序,說白了就是一種對起泡排序的改進,不過改進了不少,快速排序被認爲是最好的一種排序算法(同量級別)。

1:簡要過程:

選擇一個基準元素,我們稱之爲中樞,通常選擇第一個元素或者最後一個元素,中樞的選擇很重要,直接影響排序的性能,這是因爲如果代排序列有序,快速排序就退化爲冒泡排序。將序列分割成左右兩個序列。其中一部分記錄的元素值均比基準元素值小。另一部分記錄的 元素值比基準值大。一趟排序的具體做法將兩個附設指針left和right,然後分別對這兩部分記錄用同樣的方法繼續進行排序,直到整個序列有序。

2:排序效率:

 在所有同級別的排序算法中其平均性能最好O(nlogn)。如果代排序列有序,快速排序就退化爲冒泡排序,效率及其低下,這個可以在下面的效率比拼圖中體現出來逆序的時候快速排序非常慢,效率很低下。所以可以在中樞的選擇上優化可以選着最左邊和最右邊中間的元素取其平均值即可。

3:排序過程圖:

 

4:具體算法實現:

複製代碼
 1 //快速排序的元素移動 
 2 int fastSort(int arr[], int left, int right){  3     int center = (left+right)/2;  4     int num = arr[center]; //記錄中樞點的位置,這裏選擇最左邊點當做中樞點 
 5     while(left < right){  6         //比樞紐小的移動到左邊 
 7         while(left < right && arr[right] >= num){  8             right--; //一直到有一個元素小於所選關鍵中樞爲止 
 9  } 10         arr[left] = arr[right]; 11         
12         while(left < right && arr[left] <= num){ 13             left++; //一直到有一個元素大於所選關鍵中樞爲止 
14  } 15         arr[right] = arr[left]; //關鍵字入列 
16  } 17     arr[left] = num; 18     return left; 19 } 20 
21 //快速排序遞歸劃分
22 int fastSortSelf(int arr[], int left, int right){ 23     if(left < right){ 24         int lowLocation = fastSort(arr,left,right); //第一個指針的位置 
25         fastSortSelf(arr,left,lowLocation - 1); 26         fastSortSelf(arr,lowLocation + 1,right); 27  } 28 } 29 
30 //快速排序 
31 void fast(int arr[], int n){ 32     fastSortSelf(arr,0,n-1); 33     //記錄時間
34 } 
複製代碼

 

八、各種算法效率比拼

下面是我自己寫的一個算法比拼,測試數據有100-50000都有,分爲最壞情況(逆序->正序)和隨機情況(隨機->正序),測試數據會因爲機器的不同而不同,我的是win8,64, 4G,cpu2.5,歡迎大家測試

說明兩點:要注意時間的得到方法,需要引入的頭文件包括

1 #include <iostream>
2 #include <iomanip> 
3 #include <stdlib.h>
4 #include <time.h>
1 end_time = clock(); 
2 程序。。。。
3 times = static_cast<double>(end_timestart_time)/CLOCKS_PER_SEC*1000;cout <<right ; cout <<setw(7) <<times<<"ms";

下面是程序代碼:

複製代碼
 1 #include <iostream>
 2 #include <iomanip> 
 3 #include <stdlib.h>
 4 #include <time.h>
 5 using namespace std;  6 
 7 clock_t start_time, end_time;  8 int times;  9 
 10 void printfArr(int arr[], int n){  11     for(int i = 0; i < n; i++){  12         cout<<arr[i]<<" ";  13  }  14 }  15 
 16 //冒泡排序 
 17 void bubbling(int arr[], int n){  18     start_time = clock();  19     int i, j, temp;  20     for (j = 0; j < n - 1; j++){  21            for (i = 0; i < n - 1 - j; i++){  22                if(arr[i] > arr[i + 1]){  23                   temp = arr[i];  24                      arr[i] = arr[i + 1];  25                      arr[i + 1] = temp;  26  }  27  }  28  }  29     end_time = clock();  30     times = static_cast<double>(end_time-start_time)/CLOCKS_PER_SEC*1000;  31     cout <<right ; cout <<setw(7) <<times<<"ms";  32 }  33 
 34 //直接選擇排序 
 35 void directDialing(int arr[], int n){  36     start_time = clock();  37     int i,j,k,num;  38     for(i = 0; i < n; i++){  39         num = 0;  40         for(j = 0; j < n-i; j++){  41             if(arr[j] > arr[num]){  42                 num = j;  43  }  44  }  45         k = arr[n-i-1];  46         arr[n-i-1] = arr[num];  47         arr[num] = k;  48  }  49     end_time = clock();  50     times = static_cast<double>(end_time-start_time)/CLOCKS_PER_SEC*1000;  51     cout <<right ; cout <<setw(7) <<times<<"ms";  52 }  53 
 54 //直接插入排序 (On2)
 55 void  insert(int arr[], int n){  56     start_time = clock();  57     int m,i,k;  58     for(i = 1; i < n; i++){  59         m = arr[i];  60         for(k = i-1; k >= 0; k--){  61             if(arr[k] > m){  62                 arr[k+1] = arr[k];  63             }else{  64                 break;  65  }  66  }  67         arr[k+1] = m;  68  }  69     end_time = clock();  70     times = static_cast<double>(end_time-start_time)/CLOCKS_PER_SEC*1000;  71     cout <<right ; cout <<setw(7) <<times<<"ms";  72 }  73 
 74 //希爾排序 
 75 void Heer(int arr[], int n){  76     start_time = clock();  77     int i,j,k,num;  78     k = n/2;  79     while(k > 0){  80         for(i = k; i < n; i++){  81             num = arr[i];  82             for(j = i - k;j >= 0; j -= k){  83                 if(arr[j] > num){  84                     arr[j+k] = arr[j];  85                 }else{  86                     break;  87  }  88  }  89             arr[j+k] = num;  90  }  91         k = k/2;  92  }  93     end_time = clock();  94     times = static_cast<double>(end_time-start_time)/CLOCKS_PER_SEC*1000;  95     cout <<right ; cout <<setw(7) <<times<<"ms";  76     start_time = clock();  77     int i,j,k,num;  78     k = n/2;  79     while(k > 0){  80         for(i = k; i < n; i++){  81             num = arr[i];  82             for(j = i - k;j >= 0; j -= k){  83                 if(arr[j] > num){  84                     arr[j+k] = arr[j];  85                 }else{  86                     break;  87  }  88  }  89             arr[j+k] = num;  90  }  91         k = k/2;  92  }  93     end_time = clock();  94     times = static_cast<double>(end_time-start_time)/CLOCKS_PER_SEC*1000;  95     cout <<right ; cout <<setw(7) <<times<<"ms";  96 }  97 
 98 //堆調整 
相關文章
相關標籤/搜索