下面分别介绍插入排序,快速排序,选择排序和希尔排序,堆排序在第十四章介绍。
其中选择排序是不稳定的,时间复杂度是O(n^2),输入不敏感,即使是输入序列有序,仍然需要O(n^2)时间。插入排序是稳定的,时间复杂度是O(n^2),但是在数组是接近有序的情况下,选择排序将会很快,因此是输入敏感型的算法。希尔排序是插入排序的一个快速版本,时间复杂度是O(n^3/4),也是输入敏感型的,当输入数据已经排序好时,其复杂度为O(n),是不稳定的排序,因为其交换间隔为h,因此可能会破坏稳定性。选择排序时间复杂度为O(nlogn),是不稳定的排序。
插入排序,希尔排序和选择排序没有什么特别的地方,程序也很常规,重点讨论一下快速排序。
快速排序在这里总共给了三个版本,quickSort,quickSort1和quickSort2。其中quickSort是最简单的一个版本,从左到右遇到一个小于A[0]的就交换,比较简单,但是这里也有问题,就是如果A[N]中都是相同的数,则每次必然只有第一个数会就位,这样就会出现严重的不平衡性,导致算法降低到O(n^2),因此用了一个改进的算法。quickSort1是一个改进的版本,该版本在循环中没有用到swap,只是赋值,因此速度很快,而且当与A[0]值相等的时候也进行赋值交换,这样虽然赋值交换的次数增加了,但是使得随后的两个递归调用更加平衡,从而使总体的时间复杂度仍然在O(nlogn),这个可以把<和>号改成<=和>=来尝试改后的版本,用A[N]都是相同的数来测试,将耗时非常长。quickSort2是《编程珠玑》第11章中讲的算法,这个和quickSort1类似。
当n<10时,一般采用插入排序会有较好的性能,因为此时n较小,而用快速排序时递归的调用会浪费时间和空间。而且,快排由于需要递归调用,在VS2010中,当N=100000时,就会出现栈溢出的错误。而用迭代版的选择或者希尔排序则可以正常运行。
再说一下调试时的方法:看了《编程珠玑》中测试的方法,很有启发。对于排序来说,有两个是为了保证排序算法正确的必须要测试的数据。其中一个是随机数,任意多的随机数,这样可以保证算法在相对多的数据下的正确性和速度。另一个是各种排列的数据的测试。如尝试0-9这10个数据的10!种排列的排序,如果正确,就能说明这个排序算法在这些情况下都可以正常运行,就可以相信这个算法是正确的了。
当然这种测试方法对于其他算法的测试也是适用的。
#include<iostream>
#include<algorithm>
using namespace std;
void insertSort(int A[],int n)
{
for(int i=1;i<n;i++)
{
int temp=A[i];
int j=i-1;
while(j>=0 && A[j]>temp)
{
A[j+1]=A[j];
j--;
}
A[j+1]=temp;
}
}
//这个算法的运行效率不高,因为交换两个元素开销还是有的,而且
//这里当i==k时是A[i]本身自己交换
//当N=100000;且A[N]填充相同数字,如10,此时,每次quickSort时
//分成的两部分都极度不均匀,导致栈溢出。
void quickSort(int A[],int n)
{
if(n<=1)
return ;
int k=0;
for(int i=0;i<n-1;i++)
{
if(A[i]<=A[n-1])//这里<和<=差别不大
{
swap(A[i],A[k]);
k++;
}
}
swap(A[k],A[n-1]);
quickSort(A,k);
quickSort(A+k+1,n-k-1);
}
//通过把比A[0]小的元素不断放到左边,而把
//大于等于A[0]的元素放到右边,没有交换,
//只有赋值,效率高,但是比较次数多
void quickSort1(int A[],int n)
{
if(n<=10)
{
insertSort(A,n);
return;
}
swap(A[0],A[rand()%n]);
int temp=A[0];
int lo=0,hi=n-1;
while(lo<hi)
{
while(hi>lo && A[hi]>temp)//这里>和>=差别很大,尽管加上=
hi--; //后会减少交换,但是这样使得分成的两部分更加均匀了
if(hi==lo)
break;
A[lo++]=A[hi];
while(lo<hi && A[lo]<temp)//前面已经保证了必然有元素大于等于A[0],
lo++; //因此lo<hi在这里必然成立
A[hi--]=A[lo];
}
A[lo]=temp;
quickSort1(A,lo);
quickSort1(A+lo+1,n-lo-1);
}
//《编程之美》第11章程序
void quickSort2(int A[],int n)
{
if(n<=10)
{
insertSort(A,n);
return;
}
swap(A[0],A[rand()%n]);
int temp=A[0],lo=0,hi=n;//始终保证lo中存的是<=A[0]的元素
while(lo<hi)
{
while(--hi>lo && A[hi]>A[0]);
if(hi==lo)
break;
while(++lo<hi && A[lo]<A[0]);
swap(A[lo],A[hi]);
}
swap(A[0],A[lo]);
quickSort2(A,lo);
quickSort2(A+lo+1,n-lo-1);
}
void selectSort(int A[],int n)
{
for(int i=0;i<n;i++)
{
int k=i,min=A[i];
for(int j=i;j<n;j++)
{
if(A[j]<min)
{
k=j;
min=A[j];
}
}
swap(A[i],A[k]);
}
}
void shellSort(int A[],int n)
{
int h=1;
for(h=1;h<n;h=h*3+1);
for(h=h/3;h>=1;h=h/3)
{
cout<<h<<endl;
for(int i=h;i<n;i++)
{
int j=i-h;
int temp=A[i];
while(j>=0 && A[j]>temp)
{
A[j+h]=A[j];
j=j-h;
}
swap(temp,A[j+h]);
}
}
}
int main()
{
const int N=10000;
int A[N];
// fill_n(A,N,10);
generate_n(A,N,rand);
shellSort(A,N);
bool b=is_sorted(A,A+N);
system("pause");
return 0;
}