1插入排序
template <typename E,typename Comp>
void inssort(E A[],int n)
{
for(int i0;i<n;i++)
for(int j=i;(j>0)&&(Comp::prior(A[j],A[j-1]));j--)
swap(A,j,j-1);
}
最差情况下:总比较次数是θ(n^2);
最佳情况下:总比较次数是θ(n);
平均执行时间n*n/4,即θ(n^2);
总排序次数即总比较次数-(n-1);
2冒泡排序
从记录数组的底部比较到顶部,比较相邻关键码值。如果低序号的关键码值比高序号的关键码值大,则将二者交换顺序。每一轮循环都是比较相邻关键码值,但是都将比上一轮循环少一个关键码。
template <typename E,typename Comp>
void bubsort(E A[],int n)
{
for(int i=0;i<n-1;i++)
for(int j=n-1;j>i;j--)
{
if(Comp::prior(A[j],A[j-1]))
swap(A,j,j-1);
}
最佳,平均,最差情况的运行时间几乎相同,均为θ(n*n);
3选择排序
从未排序的序列中找到最小关键码值,接着是次小关键码值,以此类推,独特之处在于交换操作很少。
本质上是冒泡排序。通过选择最小元素的位置,一次交换到位,而不是不断交换相邻记录。
template<typename E,typename Comp>
void selsort(E A[],int n)
{
for(int i=0;i<n-1;i++)
{
int lowindex=i;
for(int j=n-1;j>i;j--)
if(Comp::prior(A[j],A[lowindex]))
lowindex=j;
swap(A,i,lowindex);
}
}
以上三种均属于交换排序,关键的瓶颈是只交换相邻元素,因此,比较和移动只能一步步地进行(除选择排序外)。任何一种交换排序地时间代价都是数组中所有记录移动到正确地位置所需要地总步数。
4Shell排序
又名缩小增量排序。在不相邻地记录之间进行比较与交换,利用了插入排序地最佳时间代价特性。
把序列分成多个子序列,然后分别对子序列进行排序,最后把子序列组合起来。
template<typename E,typename Comp>
void inssort2(E A[],int n,int incr){
for(int i=incr;i<n;i+=incr)
for(int j=i;(j>=incr)&&(Comp::prior(A[j],A[j-incr]));j-=incr)
swap(A,j,j-incr);
}
template<typename E,typename Comp>
void shellsort(E A[],int n){
for(int i=n/2;i>2;i/=2)
for(int j=0;j<i;j++)
inssort2<E,Comp>(&A[j],n-j,i);
inssort2<E,Comp>(A,n,1);
}
好的增量序列的共同特征:
① 最后一个增量必须为1
② 应该尽量避免序列中的值(尤其是相邻的值)互为倍数的情况
5归并排序
思想不难理解,无非是分分分分到底再一个个合起来。
template<typename E,typename Comp>
void mergesort(E A[],E temp[],int left,int right){
if((right-left)<=THRESHOLD){
inssort<E,Comp>(&A[left],right-left+1);
return;
}
int i,j,k,mid=(left+right)/2;
mergesort<E,Comp>(A,temp,left,mid);
mergesort<E,Comp>(A,temp,mid+1,right);
for(i=mid;i>=left;i--)temp[i]=A[i];
for(j=1;j<=right-mid;j++)temp[right-j+1]=A[j+mid];
for(i=left,j=right,k=left;k<=right;k++)
if(Comp::prior(temp[i],temp[j]))A[k]=temp[i++];
else A[k]=temp[j--];
}
归并排序最佳,平均,最差运行时间都是θ(n*log(n)),不依赖于待排序数组中数值地相对顺序。
6快速排序
较之归并排序,快速排序无需额外的数组空间;
template<typename E,typename Comp>
void qsort(E A[],int i,int j){
if(j<=i)return;
int pivotindex = findpivot(A,i,j);
swap(A,pivotindex,j);
int k=partition<E,Comp>(A,i-1,j,A[j]);
swap(A,k,j);
qsort<E,Comp>(A,i,k-1);
qsort<E,Comp>(A,k+1,j);
}
inline int findpivot(E A[],int i,int j)
{return (i+j)/2;}
inline int partition(E A[],int l,int r,E& pivot){
do{
while(Comp::prior(A[++l],pivot));
while((l<r)&&Comp::prior(pivot,A[--r]));
swap(A,l,r);
}while(l<r);
return l;
}
快速排序首先选择一个轴值,对数组进行划分,partition函数的执行过程就是快速划分的实现过程。每一轮循环,从左边找第一个比轴值大的数,返回下标l,从右边找第一个比轴值小的数,返回下标r,swapA[l]和A[r];直到l和r相遇,即完成两边划分,再递归快排分别对两边进行排序。
最差情况下,每个轴值都未能把数组划分好,数组中记录随机,时间代价为θ(n*n);
最佳情况下,每个轴值都把数组分成相等的两部分,时间代价是θ(n*log(n));
平均情况下,θ(n*log(n));
7分配排序
template<typename E,class getKey>
void binsort(E A[],int n){
List<E> B[MaxKeyValue];
E item;
for(int i=0;i<n;i++)B[A[i]].append(getKey::key(A[i]));
for(int i=0;i<MaxKeyValue;i++)
for(B[i].setStart();B[i].getValue(item);B[i].next())
output(item);
}
时间代价为θ(n);每个盒子只有一个关键码,关键码可重复。
8基数排序
template <typename E,typename getKey>
void radix(E A[],E B[],int n,int k,int r,int cnt[])
{
int j;
for(int i=0,rtoi=1;i<k;i++,rtoi*=r){
for(j=0;j<r;j++)cnt[j]=0;//初始化数组cnt
for(j=0;j<n;j++)cnt[(getKey::key(A[j])/rtoi)%r]++;//循环计算要放到每个盒子的记录数
for(j=1;j<r;j++)cnt[j]=cnt[j-1]+cnt[j];//将cnt数组中的值设置为该盒子在数组B中的下标位置
for(j=n-1;j>=0;j--)B[--cnt[(getKey::key(A[j])/rtoi)%r]]=A[j];//把记录分配到数组B的盒子中
for(j=0;j<n;j++)A[j]=B[j];//简单把记录复制到数组A
}
}