這些天復習了排序這個模塊,排序算法在程序員的日常工作中是必不可少的,有時候我們不知不覺就用到了排序,這是因為高級語言系統已經比較完美的封裝和優化了排序算法,並且在筆試,面試等方面我們都能見到它的身影。下面結合那本大三的教材:嚴版的《數據結構》,來說一說這幾個經典的排序算法,如果有不對的歡迎指正!
首先我們還是先說基礎概念(按書上說的),萬變離不開概念,沒有概念沒有規矩,那可不行。
一、冒泡排序
二、直接選擇排序
三、直接插入排序
四、希爾排序
五、堆排序
六、歸並排序
七、快速排序
八、各種算法效率比拼
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)) | 穩定 |

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 時,整個序列作為一個表來處理,表長度即為整個序列的長度。2:排序效率:
3:排序過程圖:
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
ki <= k2i+1 ki >= 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"; 96 } 97
98 //堆調整
99 void adjust(int arr[], int n, int length){ 100 int max,b; 101 while(n * 2 +1 <= length){//說明存在左節點
102 max = n * 2 + 1; 103 if(n * 2 + 2 <= length){//說明存在右節點
104 if(arr[max] < arr[n * 2 + 2]){ 105 max = n * 2 + 2; //跟新最小的值
106 } 107 } 108 if(arr[n] > arr[max]){ 109 break;//順序正確,不需要再調整
110 }else{ 111 b = arr[n]; 112 arr[n] = arr[max]; 113 arr[max] = b; 114 n = max; 115 } 116 } 117 } 118
119 //堆排序
120 void stack(int arr[], int length){ 121 start_time = clock(); 122 int i,k,m = 0; 123 for(i = length/2-1; i >=0; i--){ 124 adjust(arr,i,length-1); 125 } 126 //調整堆
127 for(i = length-1 ;i >= 0; i--){ 128 //調整后把最后一個和第一個交換,每次調整少一個元素,依次向前
129 k = arr[i]; 130 arr[i] = arr[0]; 131 arr[0] = k; 132 adjust(arr,0,i-1); 133 } 134 end_time = clock(); 135 times = static_cast<double>(end_time-start_time)/CLOCKS_PER_SEC*1000; 136 cout <<right ; cout <<setw(7) <<times<<"ms"; 137 } 138
139 //歸並排序合並函數
140 void mergeSoft(int a[], int first, int mid, int last, int temp[]){ 141 int i = first, j = mid + 1; 142 int m = mid, n = last; 143 int k = 0; 144 while (i <= m && j <= n){ 145 if (a[i] <= a[j]){ 146 temp[k++] = a[i++]; 147 }else{ 148 temp[k++] = a[j++]; 149 } 150 } 151 while (i <= m){ 152 temp[k++] = a[i++]; 153 } 154 while (j <= n){ 155 temp[k++] = a[j++]; 156 } 157
158 for (i = 0; i < k; i++){ 159 a[first + i] = temp[i]; 160 } 161 } 162
163 //歸並排序遞歸拆分函數
164 void mergerSelf(int arr[], int left, int right, int arrTmp[]){ 165 if(left < right){ 166 int center = (left + right)/2; 167 mergerSelf(arr, left, center, arrTmp); 168 mergerSelf(arr, center+1, right, arrTmp); 169 mergeSoft(arr, left, center, right, arrTmp); 170 } 171 } 172 //歸並排序
173 void merger(int arr[], int n){ 174 start_time = clock(); 175 int *arrTmp = new int[n]; 176 mergerSelf(arr, 0, n - 1, arrTmp); 177 delete[] arrTmp; 178 //記錄時間
179 end_time = clock(); 180 times = static_cast<double>(end_time-start_time)/CLOCKS_PER_SEC*1000; 181 cout <<right ; cout <<setw(7) <<times<<"ms"; 182 } 183
184 //快速排序的元素移動
185 int fastSort(int arr[], int left, int right){ 186 int center = (left+right)/2; 187 int num = arr[center]; //記錄中樞點的位置,這里選擇最左邊點當做中樞點
188 while(left < right){ 189 //比樞紐小的移動到左邊
190 while(left < right && arr[right] >= num){ 191 right--; //一直到有一個元素小於所選關鍵中樞為止
192 } 193 arr[left] = arr[right]; 194
195 while(left < right && arr[left] <= num){ 196 left++; //一直到有一個元素大於所選關鍵中樞為止
197 } 198 arr[right] = arr[left]; //關鍵字入列
199 } 200 arr[left] = num; 201 return left; 202 } 203
204 //快速排序遞歸划分
205 int fastSortSelf(int arr[], int left, int right){ 206 if(left < right){ 207 int lowLocation = fastSort(arr,left,right); //第一個指針的位置
208 fastSortSelf(arr,left,lowLocation - 1); 209 fastSortSelf(arr,lowLocation + 1,right); 210 } 211 } 212
213 //快速排序
214 void fast(int arr[], int n){ 215 start_time = clock(); 216 fastSortSelf(arr,0,n-1); 217 //記錄時間
218 end_time = clock(); 219 times = static_cast<double>(end_time-start_time)/CLOCKS_PER_SEC*1000; 220 cout <<right ; cout <<setw(7) <<times<<"ms"; 221 } 222
223 int main(){ 224 cout<<"\n****************************整數最壞(逆序)測試***********************\n\n"; 225 cout<<"數據量 冒泡 選擇 插入 希爾 堆 歸並 快速\n"; 226 int size; //記錄每個測試數量
227 int tmpNum; //記錄臨時數
228 int arr1[8] = {49,38,65,97,76,13,27,49}; 229 //測試數據的各個范圍
230 int nums[6] = {100,1000,5000,10000,30000,50000}; 231 /*
232 printfArr(arr1,8); 233 printf("\n"); 234 fast(arr1,8); 235 printfArr(arr1,8);*/
236
237 for(int p = 0; p < 6; p++){ 238 size = nums[p]; 239 int arrs1[size],arrs2[size],arrs3[size],arrs4[size],arrs5[size],arrs6[size],arrs7[size]; 240 cout<<left; cout <<"n="<<setw(6)<<size; 241 //產生有序數
242 for(int m = 0; m < size; m++){ 243 int tmpNum = size - m; 244 arrs1[m] = tmpNum; 245 arrs2[m] = tmpNum; 246 arrs3[m] = tmpNum; 247 arrs4[m] = tmpNum; 248 arrs5[m] = tmpNum; 249 arrs6[m] = tmpNum; 250 arrs7[m] = tmpNum; 251 } 252 bubbling(arrs1,size); //冒泡排序法
253 directDialing(arrs2,size);//直接選擇排序
254 insert(arrs3,size); //直接插入排序
255 Heer(arrs4,size); //希爾排序
256 stack(arrs5,size); //堆排序
257 merger(arrs6,size); //歸並排序
258 fast(arrs7,size); //快速排序
259 cout<<"\n"; 260 } 261
262 //*****************隨機數測試法******************
263 int number; 264 int nlong; //數據量
265 cout<<"\n******************************隨機數測試*******************************\n\n"; 266 cout<<"數據量 冒泡 選擇 插入 希爾 堆 歸並 快速\n"; 267 for(int r = 0; r < 6; r++){ 268 size = nums[r]; 269 int rands1[size],rands2[size],rands3[size],rands4[size],rands5[size],rands6[size],rands7[size]; 270 //產生測試隨機數
271 srand((unsigned) time(NULL)); 272 for (int rd = 0; rd < size; rd++){ 273 tmpNum = rand() % size; 274 rands1[rd] = tmpNum; 275 rands2[rd] = tmpNum; 276 rands3[rd] = tmpNum; 277 rands4[rd] = tmpNum; 278 rands5[rd] = tmpNum; 279 rands6[rd] = tmpNum; 280 rands7[rd] = tmpNum; 281 } 282 //輸出測試數據量
283 cout<<left; cout <<"n="<<setw(6)<<size; 284 //依次調用各個排序函數
285 bubbling(rands1,size); //冒泡排序法
286 directDialing(rands2,size);//直接選擇排序
287 insert(rands3,size); //直接插入排序
288 Heer(rands4,size); //希爾排序
289 stack(rands5,size); //堆排序
290 merger(rands6,size); //歸並排序
291 fast(rands7,size); //快速排序
292 cout<<"\n"; 293 } 294 }

