排序算法小结(C++实现)
前言:在这里总结一下各种排序方式以增强理解和之后复习方便,附带一些优化方式
目录
非线性时间
1.比较
1冒泡排序
2快速排序
2.插入
1插入排序
2希尔排序
3.选择
1选择排序
2堆排序
4.归并
1二路归并
2多路归并
线性O(n)
1.计数排序
2.堆排序
3.基数排序
=====================================================================================================
=====================================================================================================
正文
1.简单冒泡排序
两层循环遍历数组:
内层循环进行交换:如果pre大于next ,swap(pre, next) => 实现最大元素交换到队尾
外层循环进行遍历:每次都把剩余数组中最大元素交换到队尾,最后完成排序
最好最坏都是O(n^2)
平均时间复杂度O(n^2)
void BubbleSort(int *a, int n) //3. 冒泡 { for(int i=0 ; i<n ; i++) { for(int j=0 ; j<size-i-1 ; j++) { if(a[j]>a[j+1]) { swap(a[j], a[j+1]); } } } }
1.5优化冒泡排序
优化一:标记是否交换过(数组是否已经有序)
对于像1,2,3,5,4这种基本有序的数列,只用交换一次,后来的循环都是多余的,所以可以加入falg标记是这一趟否交换过,没有的话直接退出
void BubbleSort(int *a, int n) //3. 冒泡 { for(int i=0 ; i<n ; i++) { int flag=0; //flag for(int j=0 ; j<n-i-1; j++) { if(a[j]>a[j+1]) { swap(a[j], a[j+1]); flag = 1; } } if(!flag) //优化一:如果没改动过(排好序了),直接返回 return; } }
在优化一的基础上,我们考虑这样的情况:前面大部分无序,后面有序(如1,2,5,7,4,3,6,8,9,10),优化一并不起作用
优化二:记录最后一次交换的位置,如果后面没有交换,说明后面是有序的,跳过就好
void BubbleSort(int *a, int n) //3. 冒泡 { int k=n-1; for(int i=0 ; i<n ; i++) { int pos=0; int flag=0; for(int j=0 ; j<k; j++) { if(a[j]>a[j+1]) { swap(a[j], a[j+1]); flag = 1; pos=j;//记录 } } if(!flag) return; k=pos;//优化二:跳到最后一次交换的位置 } }
优化三:每次选择最大的与队尾交换,其实就是选择排序,下面写
=====================================================================================================
2.快速排序
分治法(divide and conquer)、递归(recursion)的一种应用,总的思想:设置一个基准p(pivot),把数组分为两部分:大于p,小于p。
对于每一部分再找p,再分,一直循环直到每个小队列只剩一个元素,排序完成
方法:下标left ,right:当前部分的边界
下表 r, l:从两边往中间遍历的两个“指针”(后面被定在“出问题”(需要换位置)的我位置)
基准p=a[left] , l=left, r=right
从右边开始,第一个出问题的数赋给a[left](第一个出问题的位置留一个坑), l往右移动到第一个出问题的数赋给刚才右边的坑(l位置出现一个坑)
一直循环,到最后坑的位置就是中间的位置,把p赋给坑,完成这次大循环
void QuickSort(int *a,int left,int right) //4.快速 { if(left>right) return; int flag=a[left]; int l=left; int r=right; while(l<r) { while(l<r) //从最右找比flag小的 { if(a[r]>=flag) { r--; //这个数据没问题,标志左移 } else//要把它放到左边 { a[l]=a[r]; l++; break; //找到了一个,退出当前循环 } } while(l<r) //从最左找比flag大的 { if(flag>=a[l])//数据没问题 { l++; } else { a[r]=a[l]; r--; break; } } } a[l]=flag; QuickSort(a, left, l-1); QuickSort(a,r+1,right); }
2.5优化快速排序
快排的优化大部分就是对基准选择的优化,在上面的代码中,我们使用的是最左边的元素a[left]作为p,这种选择对于基本排好的情况(快排的最差情况)很慢,
基本山和最垃圾的冒泡排序差不多O(n^2),甚至更差,这肯定不行啊:
优化一:三数取中法
解决数据基本有序的(就是找到数组中最小下标,最大下标,中间下标的数字,进行比较,把中间大的数组放在最左边)
取a[mid],a[left], a[right] 三者之中间大小的数作为基准p
int m=left+(right-left)/2;//找到中间的数字的下标 if(arr[left]>arr[right])//最左大于最右的时候,交换左右 { swap(arr,left,right); } if(arr[m]>arr[right])//如果中间的>right ,交换 { swap(arr,m,right); } if(arr[m]>arr[left])//如果中间的>left,交换 { swap(arr,m,right); } //这样就把三个数中中间大小的数换到了left位置上,之后的程序和前面一样
while() { ... }
优化二:随机选择法
序列部分有序的时候,固定位置选取不好,随机选
/*随机选择p的位置,区间在low和high之间*/ int SelectPivotRandom(int arr[],int low,int high) { //产生p的位置 srand((unsigned)time(NULL)); int pivotPos = rand()%(high - low) + low; //把p位置的元素和low位置元素互换,此时可以和普通的快排一样调用划分函数 swap(arr[pivotPos],arr[low]); return arr[low]; }
优化三:序列够小是使用插入排序
在数列比较短的时候,使用快排“大材小用”,5-20个数据以下的时候插入排序就比快排更快了,我们取10
#define max_len 10 //数据量小于10的时候用插入 void quick(int *arr,int left,int right) { int length=right-left; if(length>max_len ) { int pivot=partition(arr,left,right); quick(arr,left,pivot-1); quick(arr,pivot+1,right); } else { //用插入排序 } }
=====================================================================================================