本文将介绍七大排序算法的三种,其中另外四种见博客冒泡排序、选择排序、插入排序、堆排序,其中本文用到的两个数的交换函数也在该博客中有介绍。
1.希尔排序
(1)实现思想:
(2)时间复杂度:取决于步长序列,若使用希尔序列:N/2,N/4,N/8.......1,时间复杂度为O(n^2);若选择一个最优序列,可达到O(n^1.3)
(3)空间复杂度:O(1)
(4)稳定性:不稳定排序
(5)实现代码:
//5.希尔排序:分组式的插入排序 //定义一个初始步长(下标间隔)gap,按步长将待排序数列分组 //将分好的组,每组插入排序,然后gap-- //重复以上步骤,直至gap=0 //N/2,N/4,N/8...希尔序列,时间复杂度为O(N) void ShellSort(int arr[], size_t size) { if(size <= 1) return; int64_t gap = size/2;//使用希尔序列 //1.第一重循环,生成步长序列 for(; gap>0; gap /= 2) { //2.第二重循环,进入插入排序 //此循环的执行顺序,相当于先处理第一组的第一个 //再处理第二组的第一个.... //再第二组的第一个.... int64_t bound = gap;//此处相当于插入排序中的bound=1 for(; bound<size;++bound) { int bound_value = arr[bound];//待插入元素 //3.第三重循环,线性表的搬运(一边找一边搬运) int64_t cur = bound; for(; cur>=gap;cur -= gap)//cur-=gap是为了找到同组元素的上一个元素 { if(arr[cur-gap] > bound_value)//进行搬运 arr[cur] = arr[cur-gap]; else//找到了合适的插入位置 break; } arr[cur] = bound_value; } } }
2. 归并排序
(1)实现思想:
(2)时间复杂度:O(n^logn)
(3)空间复杂度:O(n)
(4)稳定性:稳定排序
(5)实现代码:
1)递归实现:
//6.归并排序 //(1)递归版本 void _MergeArr(int arr[], int64_t beg, int64_t mid, int64_t end, int* tmp) {//归并两个子区间,传入的两个子区间本身有序 int64_t cur1 = beg; int64_t cur2 = mid; int64_t tmp_index = beg; while(cur1<mid && cur2<end)//有一个子区间比较结束了,循环结束,可将另一个子区间剩下的元素一次性全放进去,因为每个子区间本身有序 {//将数据按从小到大存放到tmp空间中 if(arr[cur1] < arr[cur2]) tmp[tmp_index++] = arr[cur1++]; else tmp[tmp_index++] = arr[cur2++]; } while(cur1 < mid) tmp[tmp_index++] = arr[cur1++]; while(cur2 < end) tmp[tmp_index++] = arr[cur2++]; //把tmp中的内容拷贝到数组,进行归并处理时的区间是arr[beg,end) memcpy(arr+beg, tmp+beg, sizeof(int)*(end-beg)); return; } void _MergeSort(int arr[], int64_t beg, int64_t end, int* tmp) {//[beg,end)为当前要处理的子区间 //size_t类型作减法很危险,所以这里用int64_t if(end - beg <= 1)//最多一个元素或非法区间 return; int64_t mid = beg + (end-beg)/2; //此时有两个区间[beg,mid)、[mid,end) _MergeSort(arr, beg, mid, tmp); _MergeSort(arr, mid, end, tmp); _MergeArr(arr, beg, mid, end, tmp); } void MergeSort(int arr[], size_t size) { if(size <= 1) return; //创建临时空间,用于辅助归并元素 int* tmp = (int*)malloc(sizeof(int)*size); _MergeSort(arr, 0, size, tmp); free(tmp); }
2)非递归实现:
//(2)非递归版本 void MergeSortByLoop(int arr[], size_t size) { if(size <= 1) return; int* tmp = (int*)malloc(sizeof(int)*size); //定义一个初始步长gap=1,相当于每次合并两个长度为gap的有效区间 size_t gap = 1; for(; gap<size; gap*=2) { //在当前gap下,我们利用i辅助完成所有长度为gap的区间的合并 size_t i = 0; for(; i<size; i+=2*gap) {//[beg,mid)、[mid,end) size_t beg = i; size_t mid = i+gap; if(mid > size)//防止mid超过数组最大范围 mid = size; size_t end = i+2*gap; if(end > size)//防止end超过数组最大范围 end = size; _MergeArr(arr, beg, mid, end, tmp); } } free(tmp); }
3. 快速排序
(1)实现思想:
(2)时间复杂度:平均情况O(n^logn),最坏时为O(n^2)
(3)空间复杂度:O(1)
(5)实现代码:
1)递归实现:
//7.快速排序 //(1)递归版本 int64_t Partion(int arr[], int64_t beg, int64_t end)//1.交换法 { if(end-beg <= 1) return beg; int64_t left = beg; int64_t right = end-1;//前闭后开区间 int key = arr[right];//最后一个元素作为基准值 while(left < right) { //从左到右找一个大于key的元素 while(left < right && arr[left] <= key) ++left; //从右到左找一个小于Key的元素 while(left < right && arr[right] >= key) --right; if(left < right) Swap(&arr[left], &arr[right]); } Swap(&arr[left], &arr[end-1]); return left; } int64_t Partion1(int arr[], int64_t beg, int64_t end)//1.挖坑法 { if(end-beg <= 1) return beg; int64_t left = beg; int64_t right = end-1;//前闭后开区间 int key = arr[right];//最后一个元素作为基准值 while(left < right) { while(left < right && arr[left] <= key) ++left; //此时left指向一个大于基准值Key的元素 //可以把该值填到right所指的坑,赋值完成后,left所指又变成一个坑 if(left < right) arr[right--] = arr[left]; while(left < right && arr[right] >= key) --right; if(left < right) arr[left++] = arr[right]; } //left与right重合,整个区间整理完毕,只剩下left=right位置为坑,将基准值填入即可 arr[left] = key; return left; } void _QuickSort(int arr[], int64_t beg,int64_t end) { if(end-beg <= 1)//当前区间内最多只有一个元素 return; //[beg,mid)左半部分区间,[mid+1, end)右半部分区间,左半部分元素一定小于等于右半部分区间元素 //int64_t mid = Partion(arr, beg, end); int64_t mid = Partion1(arr, beg, end); _QuickSort(arr, beg, mid); _QuickSort(arr, mid+1, end); return; } void QuickSort(int arr[], size_t size) { _QuickSort(arr, 0, size); return; }
2)非递归实现:
//(2)非递归版本实现 void QuickSortByLoop(int arr[], size_t size) { if(size <= 1) return; SeqStack stack; SeqStackInit(&stack); int64_t beg = 0; int64_t end = size; SeqStackPush(&stack, beg); SeqStackPush(&stack, end); while(1) { int ret = SeqStackGetTop(&stack, &end); if(ret == 0)//栈空,说明快排结束 break; SeqStackPop(&stack); SeqStackGetTop(&stack, &beg); //[beg,end)相当于即将要进行快排整理的区间 if(end-beg <= 1) continue; //int64_t mid = Partion(arr, beg, end); int64_t mid = Partion1(arr, beg, end); //[beg, mid),[mid+1, end) SeqStackPush(&stack, beg); SeqStackPush(&stack, mid); SeqStackPush(&stack, mid+1); SeqStackPush(&stack, end); } }
快排的改进方法:
(1)可以调整选取基准值的方法:头、尾、中间,三个元素取值为中间大的数为基准值;
(2)当递归到一定深度时,就不再递归,而是将排序算法改为堆排序
(3)当递归到一定程度时,子区间的元素较少时,把排序算法改为插入排序
下面介绍几种不常用的排序算法,这里只做简单介绍,并无实现。
4. 计数排序
(1)实现思想
(2)只适用于正整数
(3)时间复杂度:O(n)
(4)空间复杂度:O(n)
5. 基数排序
(1)实现思想:
(2)适用于解决数据范围大
6. 睡眠排序
实现思想:
借助线程,每读一个待排序元素,都创建一个线程,并让它sleep元素值得秒数,按照醒来时间,我们即可排序。
7. 猴子排序
(1)实现思想:
一直排(没有方法的排),直至整个有序,排序结束。
(2)时间复杂度:无穷大
本站转载的文章为个人学习借鉴使用,本站对版权不负任何法律责任。如果侵犯了您的隐私权益,请联系我们删除。