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 }