https://github.com/yuwei67/Play-with-Algorithms
(nlogn)為最優排序算法
選擇排序
整個數組中,先選出最小元素的位置,將該位置與當前的第一位交換;然后選出剩下數組中,最小元素的位置,將此元素與第二位元素交換;以此類推
srand和rand函數使用前,需要包含 stdlib.h和time.h;
插入排序
類似於玩撲克牌時的思想,看后面牌中的每一張牌,然后插入到前方合適的位置
單獨看8,不需要排序
6與前面的8比,6小於8,於是6和8互換位置
2先和8比,2小於8,於是互換位置
接下來2和6比,2小於6,於是互換位置,至此前三個數排序結束,后面的排序同理。
相較於選擇排序,不需要每次遍歷所有內容,有提前終止機會,當數組近乎有序時,插入排序效率遠高於選擇排序,甚至比很多O(nlogn)級別排序算法效率高;
優化插入排序:不貿然交換位置,思路如下
當考察 “2” 時,先把2這個元素復制一個副本,看是否應該放在這個位置,發現2比前面的8小,所以不應該放在這兒,那么將此位置的值賦值為8,然后考察2是不是應該放在原來8的位置;2比這個位置的前一個位置的6要小,所以6放到這個位置;之后看2是不是應該放在原來6的位置,由於是第0個位置,所以2應該放在此處,至此,對2的排序結束。
一次swap是3次賦值,優化后變為多次比較后1次賦值
插入排序,對近乎有序的數組,可以降到O(N)的復雜度
歸並排序(自頂向下,使用遞歸)
將數組對半分成2份,左右分別單獨排序,本質是遞歸排序的過程。
時間復雜度比O(N^2)小,但是需要開辟輔助空間
1比2小,所以1放在藍色指針當前位置,藍色指針后移,輔助空間中1對應的紅色指針后移
之后比較2和4,具體過程同前一步,以此類推。
具體實現:i 和 j 表示當前正在考慮的元素,k 指向這兩個元素相比較之后,最終應該放到歸並數組中的位置(是下一個需要放置的位置,不是已經排好序的最后一位)
代碼示例(未優化,對近乎有序數組性能較差):
1 // 將arr[l...mid]和arr[mid+1...r]兩部分進行歸並 2 template<typename T> 3 void __merge(T arr[], int l, int mid, int r){ 4 5 // 經測試,傳遞aux數組的性能效果並不好 6 T aux[r-l+1]; 7 for( int i = l ; i <= r; i ++ ) 8 aux[i-l] = arr[i]; 9 10 int i = l, j = mid+1; 11 for( int k = l ; k <= r; k ++ ){ 12 13 if( i > mid ) { arr[k] = aux[j-l]; j ++;} 14 else if( j > r ){ arr[k] = aux[i-l]; i ++;} 15 else if( aux[i-l] < aux[j-l] ){ arr[k] = aux[i-l]; i ++;} 16 else { arr[k] = aux[j-l]; j ++;} 17 } 18 } 19 20 // 遞歸使用歸並排序,對arr[l...r]的范圍進行排序 21 template<typename T> 22 void __mergeSort(T arr[], int l, int r){ 23 24 if( l >= r ) 25 return; 26 27 int mid = (l+r)/2; 28 __mergeSort(arr, l, mid); 29 __mergeSort(arr, mid+1, r); 30 __merge(arr, l, mid, r); 31 } 32 33 template<typename T> 34 void mergeSort(T arr[], int n){ 35 36 __mergeSort( arr , 0 , n-1 ); 37 }
代碼優化(第一次)
20 // 遞歸使用歸並排序,對arr[l...r]的范圍進行排序
21 template<typename T>
22 void __mergeSort(T arr[], int l, int r){
23
//對所有條件的優化(小數組使用插入排序,一是因為此時數組近乎有序的概率會比較大,
//二是因為 N^2 和 NlogN 前是有常數的系數的,對於這個系數,插入排序比歸並排序小,所以當N小到一定程度時,插入排序會比歸並排序快一些)
//15這個數是最優的么?
24 //if( l >= r ) 25 // return;
if( r - l <= 15)
{
insertionSort(arr,l,r);
} 26 27 int mid = (l+r)/2; 28 __mergeSort(arr, l, mid);
29 __mergeSort(arr, mid+1, r);
//對近乎有序數組的優化
if(arr[mid] > arr[mid+1]) 30 __merge(arr, l, mid, r); 31 }
歸並排序(自底向上,使用迭代,統計意義上效率稍弱於遞歸方式實現)
由於沒有使用索引直接獲取元素,可以非常好的使用NlogN的時間對鏈表這樣的數據結構進行排序
快速排序
思路:每次從當前數組中選擇一個元素,將這個元素想辦法挪到排好序的數組中應該在的位置,那么以這個元素為基點,前面的數比他小,后面的數比他大。
之后對前后兩個數組分別繼續使用快速排序。
子過程如下:如果當前訪問的元素 e 比 v 大,i 指針直接后移;否則調換 i 和 j 后面元素的位置,j 指針后移, i 指針后移;遍歷完成后,交換 l 和 j 元素的位置。
1 // 對arr[l...r]部分進行partition操作 2 // 返回p,使得arr[l...p-1] < arr[p] ; arr[p+1...r] > arr[p] 3 template <typename T> 4 int __partition(T arr[], int l, int r){ 5 6 T v = arr[l]; 7 8 int j = l; // arr[l+1...j] < v ; arr[j+1...i) > v 9 for( int i = l + 1 ; i <= r ; i ++ ) 10 if( arr[i] < v ){ 11 j ++; 12 swap( arr[j] , arr[i] ); 13 } 14 15 swap( arr[l] , arr[j]); 16 17 return j; 18 } 19 20 // 對arr[l...r]部分進行快速排序 21 template <typename T> 22 void __quickSort(T arr[], int l, int r){ 23 24 if( l >= r ) 25 return; 26 27 int p = __partition(arr, l, r); 28 __quickSort(arr, l, p-1 ); 29 __quickSort(arr, p+1, r); 30 } 31 32 template <typename T> 33 void quickSort(T arr[], int n){ 34 35 __quickSort(arr, 0, n-1); 36 }
快速排序優化(針對近乎有序數組)
1 template <typename T> 2 int _partition(T arr[], int l, int r){ 3 4 swap( arr[l] , arr[rand()%(r-l+1)+l] ); 5 6 T v = arr[l]; 7 int j = l; 8 for( int i = l + 1 ; i <= r ; i ++ ) 9 if( arr[i] < v ){ 10 j ++; 11 swap( arr[j] , arr[i] ); 12 } 13 14 swap( arr[l] , arr[j]); 15 16 return j; 17 } 18 19 template <typename T> 20 void _quickSort(T arr[], int l, int r){ 21 22 // if( l >= r ) 23 // return; 24 if( r - l <= 15 ){ 25 insertionSort(arr,l,r); 26 return; 27 } 28 29 int p = _partition(arr, l, r); 30 _quickSort(arr, l, p-1 ); 31 _quickSort(arr, p+1, r); 32 } 33 34 template <typename T> 35 void quickSort(T arr[], int n){ 36 37 srand(time(NULL)); 38 _quickSort(arr, 0, n-1); 39 }
快速排序優化(雙路快速排序法,針對大量重復元素的數組)
之前小於 v 和大於 v 的數組放於數組的一段,優化后放於數組的兩端
與之前的partition相比較,此方式的最大特點是將等於 v 的元素分散到兩側,不會出現大量相同元素集中在一側的情況
1 template <typename T> 2 int _partition2(T arr[], int l, int r){ 3 4 swap( arr[l] , arr[rand()%(r-l+1)+l] ); 5 T v = arr[l]; 6 7 // arr[l+1...i) <= v; arr(j...r] >= v 8 int i = l+1, j = r; 9 while( true ){ 10 while( i <= r && arr[i] < v ) 11 i ++; 12 13 while( j >= l+1 && arr[j] > v ) 14 j --; 15 16 if( i > j ) 17 break; 18 19 swap( arr[i] , arr[j] ); 20 i ++; 21 j --; 22 } 23 24 swap( arr[l] , arr[j]); 25 26 return j; 27 } 28 29 template <typename T> 30 void _quickSort(T arr[], int l, int r){ 31 32 // if( l >= r ) 33 // return; 34 if( r - l <= 15 ){ 35 insertionSort(arr,l,r); 36 return; 37 } 38 39 int p = _partition2(arr, l, r); 40 _quickSort(arr, l, p-1 ); 41 _quickSort(arr, p+1, r); 42 }
三路快速排序
1 template <typename T> 2 void __quickSort3Ways(T arr[], int l, int r){ 3 4 if( r - l <= 15 ){ 5 insertionSort(arr,l,r); 6 return; 7 } 8 9 swap( arr[l], arr[rand()%(r-l+1)+l ] ); 10 11 T v = arr[l]; 12 13 int lt = l; // arr[l+1...lt] < v 14 int gt = r + 1; // arr[gt...r] > v 15 int i = l+1; // arr[lt+1...i) == v 16 while( i < gt ){ 17 if( arr[i] < v ){ 18 swap( arr[i], arr[lt+1]); 19 i ++; 20 lt ++; 21 } 22 else if( arr[i] > v ){ 23 swap( arr[i], arr[gt-1]); 24 gt --; 25 } 26 else{ // arr[i] == v 27 i ++; 28 } 29 } 30 31 swap( arr[l] , arr[lt] ); 32 33 __quickSort3Ways(arr, l, lt-1); 34 __quickSort3Ways(arr, gt, r); 35 } 36 37 template <typename T> 38 void quickSort3Ways(T arr[], int n){ 39 40 srand(time(NULL)); 41 __quickSort3Ways( arr, 0, n-1); 42 }