排序
基于比较的排序
根据排序的原则,内排序可以分为:
-
插入排序
-
交换排序
-
选择排序
-
归并排序
预备知识:
1.等差数列之和:S=n*(a1+an)/2
等比数列之和:S=a1(1-q^n)/(1-q)
2.使用哨兵提高效率
比如基本的顺序查找我们可以这样做:
int search(int a[],int n,int key){ for(int i=0;i<n;i++) if(a[i]==key) return i+1; //返回第几个,而不是返回下标 return 0; //返回0说明没有找到 }
注意到每次for循环都对边界进行检查(i<n),使用哨兵就不需要进行边界检查.
int search(int a[],int n,int key){ a[0]=key; for(int i=n;a[i]!=key;i--); return i; }
但是使用哨兵的前提是在数组中a[1]--a[n]存储的是实际的元素,a[0]是拿来做哨兵的,即a的长度是n+1.
3.time()返回从1970年1月1日到现在的秒数,是实际时间。
clock返回开启进程和调用clock()之间的的CPU时钟计时单元(clock tick)数,不包括显式调用sleep()的时间,常用来测试任务执行的速度。
插入排序
简单插入排序
非常的简单,想想你玩牌的时候一边起牌,一边就把牌排好序了,那就是插入排序.
时间复杂度:O(N^2),1+2+...+(N-1)=N^2/2。这是最坏的情况,其实大致上说插入排序的平均情况和最坏情况一样差。
空间上来讲,任一时刻最多只有一个数字在存储数组之外,属于原地排序,O(1)。
稳定的排序.
/*插入排序*/ template <typename Comparable> void InsertSort(vector<Comparable> &vec,int begin,int end){ for(int i=begin+1;i<=end;i++){ if(vec[i]<vec[i-1]){ vec[0]=vec[i]; //把vec[i]放入哨兵 int k; for(k=i-1;vec[0]<vec[k];k--) //从vec[0]<vec[k]看出是稳定排序 vec[k+1]=vec[k]; vec[k+1]=vec[0]; } } } template <typename Comparable> void InsertSort(vector<Comparable> &vec){ InsertSort(vec,1,vec.size()-1); }
希尔排序
希尔排序利用利用了插入排序的两个特点:
-
基本有序时直接插入排序最快
-
对于数据很少的无序数列,直接插入也很快
谢尔排序的时间复杂度在O(nlogn)和O(n^2)之间,空间复杂度为O(1).
为了使集合基本有序,而不是局部有序,不能简单地逐段分割,而应将相距为某个”增量”的元素组成一个子序列.通常取增量为d1=n/2,di+1=di/2.
/*希尔排序*/ template <typename Comparable> void ShellSort(vector<Comparable> &vec){ int n=vec.size()-1; for(int inc=n/2;inc>=1;inc/=2){ //以inc为增量,vec.size()-1才是vec里面存储的有效元素的个数 for(int i=inc+1;i<=n;i++){ //一趟希尔排序 if(vec[i]<vec[i-inc]){ vec[0]=vec[i]; int k; for(k=i-inc;k>0&&vec[0]<vec[k];k-=inc) vec[k+inc]=vec[k]; vec[k+inc]=vec[0]; } } } }
冒泡排序
把待排序的序列分为有序区和无序区,每次把无序区最大的数放到无序区的最后面,这样无序区的末元素成为有序区的首元素.
时间复杂度为O(n^2),空间复杂度O(1).
/*冒泡排序*/ template <typename Comparable> void BubbleSort(vector<Comparable> &vec){ int pos=vec.size()-1; //用pos标记无序区域的最后一个元素.大数往后排,所以无序区域在前,有序区域在后 while(pos!=0){ //当pos=0时说明1--n都已经有序了 int bound=pos; //本次操作前无序区域的边界 pos=0; for(int i=1;i<bound;i++){ if(vec[i]>vec[i+1]){ vec[0]=vec[i]; vec[i]=vec[i+1]; vec[i+1]=vec[0]; pos=i; //只要发生交换就更改pos } } } }
快速排序
快速排序是对冒泡排序的改进,由于冒泡排序是不断比较相邻元素然后进行交换,需要比较移动多次才能到达最终位置.而快速排序的比较和移动是从两端向中间进行,因而元素移动的距离较远.
初始主轴的选取采用三元取中法.经过N趟排序,当元素已基本有序后采用直接插入排序法.
/*快速排序*/ template <typename Comparable> void median3(vector<Comparable> & a,int left,int right){ int center=(left+right)/2; if(a[center]<a[left]) swap(a[center],a[left]); if(a[right]<a[left]) swap(a[left],a[right]); if(a[center]>a[right]) swap(a[center],a[right]); //place pivot at position left swap(a[center],a[left]); } template <typename Comparable> const int Partion(vector<Comparable> & a,int left,int right){ a[0]=a[left]; //选取基准存放在a[0]中 while(left<right){ while((left<right)&&(a[right]>=a[0])) //右侧扫描 right--; a[left]=a[right]; while((left<right)&&(a[left]<=a[0])) //左侧扫描 left++;
//注意左右侧扫描时,当a[right]或a[left]等于基准时,left或right指针也要移动。否则当有其他元素和基准相同时,外层的while将成为死循环
a[right]=a[left]; } a[left]=a[0]; return left; } template <typename Comparable> void QuickSort(vector<Comparable> &vec,int begin,int end){ median3(vec,begin,end); if(begin+10<end){ int pivotloc=Partion(vec,begin,end); QuickSort(vec,begin,pivotloc-1); QuickSort(vec,pivotloc+1,end); } else{ InsertSort(vec,begin,end); } }
理想情况下,每次划分左右两侧的序列长度是相同的,长度为n的序列可划分为logn层.定位一个元素要对整个序列扫描一遍,所需时间为O(n),总的时间复杂度为O(nlogn).
如果每次都取最左边的元素作主轴,最坏情况下待排序列完全有序(正序或逆序),每次划分只得到比上一次少一个的子序列,总的比较次数为1+2+3+...+(n-1)=O(n^2).
平均来说快速排序的时间复杂度为O(nlogn),栈的深度为O(logn).
快速排序是一种不稳定的排序算法.
选择排序
简单选择排序
简单选择排序和冒泡排序很像,每趟排序把把无序序列中最小的元素放到有序列的最后面,有序序列在前,无序序列在后.但有一个重要的区别:冒泡排序在一趟排序中边比较,边交换;而简单选择排序在一趟排序中只作一次交换.
简单选择排序是稳定的,时间复杂度为O(n^2),空间复杂度为O(1).
/*简单选择排序*/ template <typename Comparable> void SelectSort(vector<Comparable> &vec){ int n=vec.size()-1; for(int begin=1;begin<n;begin++){ int tmp=begin; //记录本趟排序的起点 for(int i=tmp+1;i<=n;i++) if(vec[i]<vec[tmp]) tmp=i; //tmp跟踪最小的元素 if(tmp!=begin){ vec[0]=vec[tmp]; vec[tmp]=vec[begin]; vec[begin]=vec[0]; } } }
堆排序
堆排序是对简单选择排序的改进,在简单选择排序中前一次的比较结果没有保存下来,后一趟排序中反复对以前已经做过的比较重新做了一遍.
时间复杂度为O(nlogn),不稳定的排序.
/*堆排序*/ template <typename Comparable> void percolate(vector<Comparable> & a,int k,int n){ //"渗透",k是我们当前关注的节点,我们要保证它比其左右孩子都要小 int i=k; //i是要筛选的节点 int j=2*i; //j是i的左孩子 while(j<=n){ if(j<n && a[j]>a[j+1]) j++; //确保a[j]是左右孩子中的较小者 if(a[i]<a[j]) //满足小根堆的条件,结束 break; else{ swap(a[i],a[j]); i=j; j=2*i; } } } template <typename Comparable> void HeapSort(vector<Comparable> & a){ int n=a.size()-1; for(int i=n/2;i>=1;i--) //n/2是第1个有孩子的节点.从下往上渗透,建堆 percolate(a,i,n); for(int j=1;j<n;j++){ //cout<<a[1]<<" "; //输出堆顶元素 swap(a[1],a[n-j+1]); //C++内置swap()函数 percolate(a,1,n-j); //重新建堆 } }
归并排序
二路归并排序
/*归并排序*/ template<typename Comparable> void Merge(vector<Comparable> & a,vector<Comparable> & tmpArray,int leftPos,int rightPos,int rightEnd){ int leftEnd=rightPos-1; int tmpPos=leftPos; int numElements=rightEnd-leftPos+1; while(leftPos<=leftEnd && rightPos<=rightEnd) if(a[leftPos]<=a[rightPos]) tmpArray[tmpPos++]=a[leftPos++]; else tmpArray[tmpPos++]=a[rightPos++]; while(leftPos<=leftEnd) tmpArray[tmpPos++]=a[leftPos++]; while(rightPos<=rightEnd) tmpArray[tmpPos++]=a[rightPos++]; for(int i=0;i<numElements;i++,rightEnd--) a[rightEnd]=tmpArray[rightEnd]; } template<typename Comparable> void MergeSort(vector<Comparable> & a,vector<Comparable> & tmpArray,int left,int right){ if(left<right){ int center=(left+right)/2; MergeSort(a,tmpArray,left,center); MergeSort(a,tmpArray,center+1,right); Merge(a,tmpArray,left,center+1,right); } }
总结
对以上种算法进行一次测试,比一比哪个快.
测试的完整代码:
#include<iostream> #include<ctime> //time() #include<vector> #include<cstdlib> //srand()和rand() using namespace std; /*插入排序*/ template <typename Comparable> void InsertSort(vector<Comparable> &vec,int begin,int end){ for(int i=begin+1;i<=end;i++){ if(vec[i]<vec[i-1]){ vec[0]=vec[i]; //把vec[i]放入哨兵 int k; for(k=i-1;vec[0]<vec[k];k--) //从vec[0]<vec[k]看出是稳定排序 vec[k+1]=vec[k]; vec[k+1]=vec[0]; } } } template <typename Comparable> void InsertSort(vector<Comparable> &vec){ InsertSort(vec,1,vec.size()-1); } /*希尔排序*/ template <typename Comparable> void ShellSort(vector<Comparable> &vec){ int n=vec.size()-1; for(int inc=n/2;inc>=1;inc/=2){ //以inc为增量,vec.size()-1才是vec里面存储的有效元素的个数 for(int i=inc+1;i<=n;i++){ //一趟希尔排序 if(vec[i]<vec[i-inc]){ vec[0]=vec[i]; int k; for(k=i-inc;k>0&&vec[0]<vec[k];k-=inc) vec[k+inc]=vec[k]; vec[k+inc]=vec[0]; } } } } /*冒泡排序*/ template <typename Comparable> void BubbleSort(vector<Comparable> &vec){ int pos=vec.size()-1; //用pos标记无序区域的最后一个元素.大数往后排,所以无序区域在前,有序区域在后 while(pos!=0){ //当pos=0时说明1--n都已经有序了 int bound=pos; //本次操作前无序区域的边界 pos=0; for(int i=1;i<bound;i++){ if(vec[i]>vec[i+1]){ vec[0]=vec[i]; vec[i]=vec[i+1]; vec[i+1]=vec[0]; pos=i; //只要发生交换就更改pos } } } } /*快速排序*/ template <typename Comparable> void median3(vector<Comparable> & a,int left,int right){ int center=(left+right)/2; if(a[center]<a[left]) swap(a[center],a[left]); if(a[right]<a[left]) swap(a[left],a[right]); if(a[center]>a[right]) swap(a[center],a[right]); //place pivot at position left swap(a[center],a[left]); } template <typename Comparable> const int Partion(vector<Comparable> & a,int left,int right){ a[0]=a[left]; //选取基准存放在a[0]中 while(left<right){ while((left<right)&&(a[right]>=a[0])) //右侧扫描 right--; a[left]=a[right]; while((left<right)&&(a[left]<=a[0])) //左侧扫描 left++; a[right]=a[left]; } a[left]=a[0]; return left; } template <typename Comparable> void QuickSort(vector<Comparable> &vec,int begin,int end){ median3(vec,begin,end); if(begin+10<end){ int pivotloc=Partion(vec,begin,end); QuickSort(vec,begin,pivotloc-1); QuickSort(vec,pivotloc+1,end); } else{ InsertSort(vec,begin,end); } } /*简单选择排序*/ template <typename Comparable> void SelectSort(vector<Comparable> &vec){ int n=vec.size()-1; for(int begin=1;begin<n;begin++){ int tmp=begin; //记录本趟排序的起点 for(int i=tmp+1;i<=n;i++) if(vec[i]<vec[tmp]) tmp=i; //tmp跟踪最小的元素 if(tmp!=begin){ vec[0]=vec[tmp]; vec[tmp]=vec[begin]; vec[begin]=vec[0]; } } } /*堆排序*/ template <typename Comparable> void percolate(vector<Comparable> & a,int k,int n){ //"渗透",k是我们当前关注的节点,我们要保证它比其左右孩子都要小 int i=k; //i是要筛选的节点 int j=2*i; //j是i的左孩子 while(j<=n){ if(j<n && a[j]>a[j+1]) j++; //确保a[j]是左右孩子中的较小者 if(a[i]<a[j]) //满足小根堆的条件,结束 break; else{ swap(a[i],a[j]); i=j; j=2*i; } } } template <typename Comparable> void HeapSort(vector<Comparable> & a){ int n=a.size()-1; for(int i=n/2;i>=1;i--) //n/2是第1个有孩子的节点.从下往上渗透,建堆 percolate(a,i,n); for(int j=1;j<n;j++){ //cout<<a[1]<<" "; //输出堆顶元素 swap(a[1],a[n-j+1]); //C++内置swap()函数 percolate(a,1,n-j); //重新建堆 } } /*归并排序*/ template<typename Comparable> void Merge(vector<Comparable> & a,vector<Comparable> & tmpArray,int leftPos,int rightPos,int rightEnd){ int leftEnd=rightPos-1; int tmpPos=leftPos; int numElements=rightEnd-leftPos+1; while(leftPos<=leftEnd && rightPos<=rightEnd) if(a[leftPos]<=a[rightPos]) tmpArray[tmpPos++]=a[leftPos++]; else tmpArray[tmpPos++]=a[rightPos++]; while(leftPos<=leftEnd) tmpArray[tmpPos++]=a[leftPos++]; while(rightPos<=rightEnd) tmpArray[tmpPos++]=a[rightPos++]; for(int i=0;i<numElements;i++,rightEnd--) a[rightEnd]=tmpArray[rightEnd]; } template<typename Comparable> void MergeSort(vector<Comparable> & a,vector<Comparable> & tmpArray,int left,int right){ if(left<right){ int center=(left+right)/2; MergeSort(a,tmpArray,left,center); MergeSort(a,tmpArray,center+1,right); Merge(a,tmpArray,left,center+1,right); } } int main(){ const int N=100000; //对十万个随机数进行排序 clock_t begin; clock_t end; srand((unsigned)time(NULL)); vector<int> v; v.push_back(0); //数组第1个元素存放哨兵,而不是参与排序的数据 for(int i=0;i<N/10;i++) v.push_back(rand()); begin=clock(); InsertSort(v); end=clock(); cout<<"InsertSort: "<<(double)(end-begin)/CLOCKS_PER_SEC<<endl; v.clear(); v.push_back(0); for(int i=0;i<N;i++) v.push_back(rand()); begin=clock(); ShellSort(v); end=clock(); cout<<"ShellSort: "<<(double)(end-begin)/CLOCKS_PER_SEC<<endl; v.clear(); v.push_back(0); for(int i=0;i<N/10;i++) v.push_back(rand()); begin=clock(); BubbleSort(v); end=clock(); cout<<"BubbleSort: "<<(double)(end-begin)/CLOCKS_PER_SEC<<endl; v.clear(); v.push_back(0); for(int i=0;i<N;i++) v.push_back(rand()); begin=clock(); QuickSort(v,1,N); end=clock(); cout<<"QuickSort: "<<(double)(end-begin)/CLOCKS_PER_SEC<<endl; v.clear(); v.push_back(0); for(int i=0;i<N/10;i++) v.push_back(rand()); begin=clock(); SelectSort(v); end=clock(); cout<<"SelectSort: "<<(double)(end-begin)/CLOCKS_PER_SEC<<endl; v.clear(); v.push_back(0); for(int i=0;i<N;i++) v.push_back(rand()); begin=clock(); HeapSort(v); end=clock(); cout<<"HeapSort: "<<(double)(end-begin)/CLOCKS_PER_SEC<<endl; v.clear(); v.push_back(0); for(int i=0;i<N;i++) v.push_back(rand()); vector<int> vv(v.size()); begin=clock(); MergeSort(v,vv,1,v.size()-1); end=clock(); cout<<"MergeSort: "<<(double)(end-begin)/CLOCKS_PER_SEC<<endl; return 0; }
我们来排个序,直接插入,冒泡和简单选择排序都是对1万条数据排序
直接插入:0.73秒
简单选择:0.78秒
冒泡:1.88秒
以下算法是对10万条数据进行排序
快速排序:0.06秒
归并排序:0.08秒
堆排序:0.09秒
希尔:0.12秒
排序方法 |
平均情况 |
最好情况 |
最坏情况 |
辅助空间 |
稳定性 |
直接插入 |
O(n^2) |
O(n) |
O(n^2) |
O(1) |
是 |
希尔 |
O(nlogn)~O(n^2) |
O(n^1.3) |
O(n^2) |
O(1) |
否 |
冒泡 |
O(n^2) |
O(n) |
O(n^2) |
O(1) |
是 |
快速 |
O(nlogn) |
O(nlogn) |
O(n^2) |
O(nlogn)~O(n) |
否 |
简单选择 |
O(n^2) |
O(n^2) |
O(n^2) |
O(1) |
是 |
堆排序 |
O(nlogn) |
O(nlogn) |
O(nlogn) |
O(1) |
否 |
归并 |
O(nlogn) |
O(nlogn) |
O(nlogn) |
O(n) |
是 |
注:冒泡排序采用pos来标记已有序的序列位置后,最好情况才是O(n),如果没有采用此改进算法,最好情况也是O(n^2).我们的快速排序每次都把主轴放在vec[0]中,没用另外使用单独的变量,所以辅助空间为O(1),否则就是O(nlogn)~O(n).
STL排序
STL中的所有排序算法都需要输入一个范围,[begin,end).如果要自己定义比较函数,可以把定义好的仿函数(functor)作为参数传入.
函数名 |
功能描述 |
sort |
指定区间排序 |
stable_sort |
指定区间稳定排序 |
partial_sort |
对指定区间所有元素部分排序 |
partial_sort_copy |
对指定区间复制并排序 |
nth_element |
找到指给定区间某个位置上对应的元素 |
is_sorted |
判断给定区间是否已排序好 |
partition |
使得符合条件的元素放在前面 |
stable_partion |
相对稳定地使得符合条件的元素放在前面 |
#include<iostream> #include<algorithm> #include<functional> #include<vector> using namespace std; class myclass{ public: myclass(int a,int b):first(a),second(b){} int first; int second; bool operator < (const myclass &m)const{ return first<m.first; } }; //自定义仿函数 bool less_second(const myclass &m1,const myclass &m2){ return m1.second<m2.second; } int main(){ vector<myclass> vec; for(int i=0;i<10;i++){ myclass my(10-i,i*3); vec.push_back(my); } //原始序列 for(int i=0;i<10;i++) cout<<vec[i].first<<" "<<vec[i].second<<endl; sort(vec.begin(),vec.end()); cout<<"按照第1个数从小到大:"<<endl; for(int i=0;i<10;i++) cout<<vec[i].first<<" "<<vec[i].second<<endl; sort(vec.begin(),vec.end(),less_second); cout<<"按照第2个数从小到大:"<<endl; for(int i=0;i<10;i++) cout<<vec[i].first<<" "<<vec[i].second<<endl; return 0; }
stable_sort是稳定的排序,或许你会问既然相等又何必在乎先后顺序呢?这里的相等是指提供的比较函数认为两个元素相等,并不是指两个元素真的一模一样.
sort采用的是”成熟”的快速排序(结合了内插排序算法),保证很好的平均性能,时间复杂度为O(nlogn).stable_sort采用的是归并排序,分派内存足够时,其时间复杂度为O(nlogn),否则为O(nlognlogn).
线性时间排序
最好的基于比较的排序算法,其在最坏的情况下做次比较。因此堆排序和归并排序都是渐近最优的比较排序算法。
下面介绍3种不是基于比较的排序算法,它们线性的时间度,但应用范围也受到局限。
计数排序
计数排序仅适用于整数,并且要求待排数据在[0,max]上。
1 #include<iostream> 2 #include<vector> 3 using namespace std; 4 5 void CountSort(vector<int> &arr,int max){ //max是arr中的最大者 6 vector<int> C(max+1); 7 vector<int> B(arr.size()); 8 for(int i=0;i<arr.size();i++) 9 C[arr[i]]++; 10 for(int i=1;i<C.size();i++) 11 C[i]+=C[i-1]; 12 for(int i=arr.size()-1;i>=0;i--){ 13 B[C[arr[i]]-1]=arr[i]; 14 C[arr[i]]--; 15 } 16 arr=B; 17 } 18 19 int main(){ 20 int arr[8]={2,5,7,9,2,3,0,16}; 21 vector<int> A; 22 for(int i=0;i<8;i++) 23 A.push_back(arr[i]); 24 CountSort(A,16); 25 for(int i=0;i<8;i++) 26 cout<<A[i]<<" "; 27 cout<<endl; 28 return 0; 29 }
第8、10、12行有3个for循环,算法复杂度为,当时,算法复杂度为。
计算排序是稳定的排序,这一点对下面要讲的基数排序非常重要。因为计算排序是基数排序的子过程,计算排序的稳定性对基数排序的正确性至关重要。
基数排序
同样只适用于整数,并且还要求位数相同。第一次按个位排序,第二次按百位排序,第三次按千位排序……
1 #include<iostream> 2 #include<cmath> 3 #include<vector> 4 using namespace std; 5 6 void RadixSort(vector < int >&arr, int len) 7 { 8 vector < int >C(10); 9 vector < int >B(arr.size()); 10 for (int j = 0; j < 3; j++) { 11 C.assign(10, 0); 12 B.assign(arr.size(), 0); 13 for (int i = 0; i < arr.size(); i++){ 14 if(j==0) 15 C[arr[i] % 10]++; 16 else 17 C[arr[i] / (int)pow(10,j) % 10]++; 18 } 19 for (int i = 1; i < C.size(); i++) 20 C[i] += C[i - 1]; 21 for (int i = arr.size() - 1; i >= 0; i--) { 22 if(j==0){ 23 B[C[arr[i] % 10] - 1] = arr[i]; 24 C[arr[i] % 10]--; 25 }else{ 26 B[C[arr[i] / (int)pow(10,j) % 10] - 1] = arr[i]; 27 C[arr[i] / (int)pow(10,j) % 10]--; 28 } 29 } 30 arr = B; 31 } 32 } 33 34 int main() 35 { 36 int arr[7] = { 329, 457, 657, 839, 436, 720, 355 }; 37 vector < int >vec; 38 for (int i = 0; i < 7; i++) 39 vec.push_back(arr[i]); 40 RadixSort(vec, 3); 41 for (int i = 0; i < 7; i++) 42 cout << vec[i] << " "; 43 cout << endl; 44 return 0; 45 }
桶排序
需要要事先知道数据的范围,比如在[1,100]上,则我们用10个桶,把每个数num映射到第num/10桶中去,对每个桶内元素进行排序,最后连接起来就是全局的排序了。
本文来自博客园,作者:高性能golang,转载请注明原文链接:https://www.cnblogs.com/zhangchaoyang/articles/1812997.html