排序
基于比较的排序
根据排序的原则,内排序可以分为:
-
插入排序
-
交换排序
-
选择排序
-
归并排序
预备知识:
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桶中去,对每个桶内元素进行排序,最后连接起来就是全局的排序了。
本文来自博客园,作者:张朝阳讲go语言,转载请注明原文链接:https://www.cnblogs.com/zhangchaoyang/articles/1812997.html

浙公网安备 33010602011771号