排序算法是經常使用的算法,在STL中也有一個比較牛X的快速排序(sort),但是我們不能只會調用sort呀!?作為一個好學的同學,我們要知道各種排序的內部是怎么實現滴~~~提到排序算法我們要知道兩個經常提到的概念:
(1)排序算法的穩定性:所謂“穩定性”是指,在待排序數組出現的兩個相同的元素,排序之后相對維持保持不變。比如:待排序數組為arr[] = {1,4,3,1},排序之后元素變為arr_new[] = {1,1,4,3},並且arr_new中的第一個是arr中的第一個1,arr_new中的第二個1是arr中的第二個1,這是我們就說這種排序時穩定的。
(2)原地排序:所謂原地排序是指,不申請多余的空間來輔助完成排序算法,而是在原來的待排序的數據之上直接進行比較,交換,移動等操作。
1.插入排序
算法原理:將待排序的數組分為:有序區 和 無序區。然后每次從無序區取出第一個數據插入到有序區的正確位置,最終完成排序。
算法代碼:

1 #include <iostream> 2 3 using namespace std; 4 5 void insert_sort(int *arr,int n) 6 { 7 int i,j; 8 for(i = 1 ; i < n ; ++i) 9 { 10 int tmp = arr[i]; 11 j = i - 1; 12 while( j >= 0 && arr[j] > tmp) 13 { 14 arr[j+1] = arr[j]; 15 j--; 16 } 17 arr[j+1] = tmp; 18 } 19 } 20 21 int main() 22 { 23 int arr[] = {2,4,1,3,5,8,7,6,8}; 24 insert_sort(arr,9); 25 for(int i = 0 ; i < 9 ; ++i) 26 { 27 cout<<arr[i]<<" "; 28 } 29 cout<<endl; 30 return 0; 31 }
小結:看代碼可以知道這種排序算法的時間復雜度是O(n^2),並且插入排序時穩定的,屬於原地排序。那么什么時候使用插入排序比較好呢?那就是當數組中的大部分數據已經有序時,使用插入排序算法的效率比較高,這種情況下,所需要進行的數據移動較少,而數據移動正式插入排序算法的主要步驟~~~~
2.冒泡排序
算法原理:冒泡排序是經過n-1趟子排序完成的,第 i 趟子排序從第1個數至第 n-i+1 個數,若第 i 個數比第 i+1 個數大,則交換這兩個數,實際上這樣經過 i 次子排序就使得 第1個數至第 n-i +1個數之間最大的數交換到了n-i+1 的位置上了。實際上冒泡排序時可以優化的,那就是當第 i 次子排序並沒有發生元素的交換時,就說明數組已經排好序了,以后的子排序就不用做了。
算法代碼:

1 #include <iostream> 2 3 using namespace std; 4 5 void swap(int &x,int &y) 6 { 7 x = x^y; 8 y = x^y; 9 x = x^y; 10 } 11 12 void bubble_sort(int *arr,int n) 13 { 14 int i,j; 15 for(i = n - 1 ; i > 0 ; --i) 16 { 17 bool flag = true; 18 for(j = 0 ; j < i ; ++j) 19 { 20 if(arr[j] > arr[j+1]) 21 { 22 flag = false; 23 swap(arr[j],arr[j+1]); 24 } 25 } 26 if(flag) //數組已經排好序沒必要在繼續進行其他子排序了 27 break; 28 } 29 } 30 31 int main() 32 { 33 int arr[] = {2,1,4,3,8,7,5,6}; 34 bubble_sort(arr,8); 35 for(int i = 0 ; i < 8 ; ++i) 36 { 37 cout<<arr[i]<<" "; 38 } 39 cout<<endl; 40 return 0; 41 }
小結:冒泡排序算法的時間復雜度是O(n^2),同時冒泡排序也是穩定的,並且屬於原地排序,排序的效率取決於逆序對的多少。采用一點小優化也加速了冒泡排序。
3.選擇排序
算法原理:所謂選擇排序經過 n-1 次選擇,當進行第 i 次選擇時,是從第1個元素到第 n-i+1 的元素中選擇最大的元素和第 n-i+1 個位置的元素交換,這樣做比如第1 次選擇使得最大的元素到了數組的最后一個位置。注意哦,在選擇排序中每次選擇時只進行一次數據的交換。
算法代碼:

1 #include <iostream> 2 3 using namespace std; 4 5 void swap(int &x,int &y) 6 { 7 int tmp = x; 8 x = y; 9 y = tmp; 10 } 11 12 void select_sort(int *arr,int n) 13 { 14 int i,j; 15 for(i = n-1 ; i > 0 ; --i) 16 { 17 int tmp = 0; 18 for(j = 1 ; j <= i ; ++j) 19 { 20 if(arr[j] >= arr[tmp])//這里的“=”是保證選擇排序穩定的關鍵 21 { 22 tmp = j; 23 } 24 } 25 swap(arr[i],arr[tmp]); 26 } 27 } 28 int main() 29 { 30 int arr[] = {2,1,4,3,8,7,5,6}; 31 select_sort(arr,8); 32 for(int i = 0 ; i < 8 ; ++i) 33 { 34 cout<<arr[i]<<" "; 35 } 36 cout<<endl; 37 return 0; 38 }
小結:選擇排序的思路非常的簡單,實現起來也不難。時間復雜度是O(n^2),選擇排序也是穩定的排序,並且也是原地排序。選擇排序的時間基本不受數據的影響,因為不管怎樣都要進行n-1次選擇排序。
4.歸並排序
算法原理:歸並排序的思想是分治,將一個帶排序的數組分成兩個較小的數組,然后分別進行排序,組后將兩個排好序的較小的數組合並起來,就得到了原來數組的排序后的結果。應該注意的是這種將兩個排好序的數組合並有一個較好的算法,時間復雜度是O(n1+n2)的。n1、n2分別是兩個小數組的長度。
算法代碼:

1 #include <iostream> 2 3 using namespace std; 4 5 void merge_sort(int *arr,int start,int end,int *temp) 6 { 7 if(end > start+1) 8 { 9 int mid = start + (end - start) / 2; 10 merge_sort(arr,start,mid,temp); 11 merge_sort(arr,mid,end,temp); 12 int i = start , j = mid , k = start; 13 while(i < mid || j < end) 14 { 15 if(j >= end || (i < mid && arr[i] <= arr[j])) 16 { 17 temp[k++] = arr[i++]; 18 } 19 else 20 { 21 temp[k++] = arr[j++]; 22 } 23 } 24 for(i = start ; i < end ; ++i) 25 { 26 arr[i] = temp[i]; 27 } 28 } 29 } 30 31 32 int main() 33 { 34 int temp[8]; 35 int arr[] = {2,1,4,3,8,7,5,6}; 36 merge_sort(arr,0,8,temp); 37 for(int i = 0 ; i < 8 ; ++i) 38 { 39 cout<<arr[i]<<" "; 40 } 41 cout<<endl; 42 return 0; 43 }
小結:歸並排序時穩定的排序,但是不屬於原地排序,因為用了額外的O(n)的空間,時間復雜度降到了O(n*log n),並且對任意的數組進行排序時間復雜度都能控制在O(n*logn)。
5.堆排序
算法原理:所謂的堆排序是利用完全二叉樹的思想實現的。首先應該提到的是最大堆,在最大堆中(完全二叉樹二叉樹)中每個父節點都大於等於兩個兒子節點的值,這時候很明顯堆頂是元素的最大值,然后把堆頂元素和堆中最后一個元素(分層遍歷的節點編號最大的元素)交換,這樣最大值就落到了數組的arr[n-1]的位置,然后把前n-1元素繼續按照上面的方式處理,如此進行n-1次就完成堆排序。
算法代碼:

1 #include <iostream> 2 3 using namespace std; 4 5 void swap(int &x,int &y) 6 { 7 x = x + y; 8 y = x - y; 9 x = x - y; 10 } 11 12 void restore(int *arr,int s,int e) 13 { 14 int i = s , m; 15 while(i <= e/2) 16 { 17 if(2*i+1 <= e && arr[2*i] > arr[2*i-1]) 18 { 19 m = 2 * i + 1; 20 } 21 else 22 { 23 m = 2 * i; 24 } 25 if(arr[i-1] < arr[m-1]) 26 { 27 swap(arr[i-1],arr[m-1]); 28 i = m; 29 } 30 else 31 { 32 i = e; 33 } 34 } 35 } 36 void heap_sort(int *arr,int n) 37 { 38 int i; 39 for(i = n / 2 ; i > 0 ; --i) 40 { 41 restore(arr,i,n); 42 } 43 for(i = n ; i > 1 ; --i) 44 { 45 swap(arr[0],arr[i-1]); 46 restore(arr,1,i-1); 47 } 48 } 49 50 int main() 51 { 52 int arr[] = {2,1,4,3,8,7,5,6}; 53 heap_sort(arr,8); 54 for(int i = 0 ; i < 8 ; ++i) 55 { 56 cout<<arr[i]<<" "; 57 } 58 cout<<endl; 59 return 0; 60 }
小結:堆排序是不穩定的排序,但是堆排序屬於原地排序。時間復雜度是O(n*log n),並且不需要額外的輔助空間,也就是說堆排序是一種不錯的排序算法哦~~~
6.快速排序
算法原理:快速排序時這樣的一種排序,選取數組中的第一個元素arr[0]作為依據,遍歷一遍數組后,使得數組中的第一個元素進入正確的位置,即在該位置左面的元素均小於等於arr[0],在該位置右面的元素均大於等於arr[0]。然后,在對該位置左面和右面的元素分別進行快速排序,如此一來完成整個數組的排序。
算法代碼:

1 #include <iostream> 2 3 using namespace std; 4 5 void swap(int &x,int &y) 6 { 7 x = x + y; 8 y = x - y; 9 x = x - y; 10 } 11 12 void quick_sort(int *arr,int s,int e) 13 { 14 if(s+1 < e) 15 { 16 int tmp = arr[s]; 17 int i = s+1; 18 int j = e-1; 19 while(i < j) 20 { 21 while(i <= j && arr[i] <= tmp) 22 { 23 i++; 24 } 25 while(i <= j && arr[j] >= tmp) 26 { 27 j--; 28 } 29 if(i < j) 30 { 31 swap(arr[i],arr[j]); 32 } 33 } 34 swap(arr[s],arr[i-1]); 35 quick_sort(arr,s,i-1); 36 quick_sort(arr,i,e); 37 } 38 } 39 40 int main() 41 { 42 int arr[] = {2,1,4,3,8,7,5,6}; 43 quick_sort(arr,0,8); 44 for(int i = 0 ; i < 8 ; ++i) 45 { 46 cout<<arr[i]<<" "; 47 } 48 cout<<endl; 49 return 0; 50 }
小結:首先還是說明快速排序時不穩定的,但是是原地排序,不需要額外的空間,時間復雜度是O(nlog n),實際上,這種把第一個元素作為依據元素只是快速排序的一種,STL中的sort內部實現是根據排序到了不同的階段選用不同的排序算法。當數據量大是采用quick_sort排序,當分段遞歸到了數據量小於某個數值時,為避免quick_sort的遞歸調用帶來的額外開銷,就改用insert_sort 了;如果遞歸層次過深,還會考慮使用heap_sort 。
學習中的一點總結,歡迎拍磚哦^^