本文论及的算法包含插入排序、归并排序、快速排序、冒泡排序以及选择排序
首先,说明什么是排序算法的稳定性。
当待排序的序列中存在a[i]与a[j],且a[i]=a[j],在经过某算法排序后,a[i]与a[j]的相对位置不发生改变,例如a[i]在a[j]之前,排序过程中,a[i]一直在a[j]之前,则我们认为该排序算法是稳定的,以冒泡排序为例。
for(int i=1;i<=n;i++) {
sign=i;
for(int j=i+1;j<=n;j++) {
if(a[sign] > a[j]){
t=a[sign]; a[sign]=a[j]; a[j]=t;
sign=j;
}
}
}
其中,如果将a[sign] > a[j] 改为a[sign] >= a[j]则该算法由稳定变为不稳定。
接下来,开始算法的介绍。
1、插入排序:
插入排序的过程就像是在排序一手扑克牌,左手是排列好的牌,而右手不断向左手排列好的牌中插入新牌,最终得到排列好的牌。
for(int i=2;i<=n;i++) {
bool sign = false;
lab = a[i]; key = i-1;
while(sign == false) { //实现整体右移,为lab的插入留空
if(a[key] > lab) {
a[key + 1] = a[key];
key--;
}
else sign = true;
}
a[key+1] = lab;
}
其中,a[1..i]描述的就是“左手”中排列好的“牌”,也就是大小排好的序列,而a[i+1..n]则是待排序的“乱牌堆”;有趣的是,a[1..i]中的元素仍是原来a[1..i]中的元素,这也被称为循环不变式。
这样的话,可见插入排序是一种稳定的排序。
2、归并排序
归并排序的本质思想就是分治,将序列分为两堆,对两堆分别在分划,从而由递归使得待处理的当前问题成为将两个排好序的子序列并为一个排好序的序列。
void msort(int s,int t) {
memset(r,0,sizeof(r));
if(s==t) return; //递归到最底端
int mid=(s+t)/2; //二分
msort(s,mid); //左序列再分割
msort(mid+1,t); //右序列再分割
int i=s,j=mid+1,k=s;
while(i<=mid && j<=t) {
if(a[i]<=a[j]) {
r[k]=a[i];
i++;
k++;
}
else {
r[k]=a[j];
j++;
k++;
}
}
while(i<=mid) {
r[k]=a[i];
k++;
i++;
}
while(j<=t) {
r[k]=a[j];
k++;
j++;
}
for(int i=s;i<=t;i++) {
a[i]=r[i];
}
}
3、快速排序:
快速排序的思路,是选定一个标准数,将序列中比标准数小的扔到左边,将序列中比标准数大的扔到右边,再分别对左序列与右序列进行同样的操作。
void qsort(int l,int r) {
int mid,i,j,k;
i=l;j=r;
mid=a[(l+r)/2]; //选定标准数
do {
while(a[i]<mid) i++;
while(a[j]>mid) j--;
if(i<=j) {
k=a[j];
a[j]=a[i];
a[i]=k;
i++;
j--;
} //寻找左端比mid大和右端比mid小的数对交换
}while(i<=j);
//此时,j的左端都是小于等于mid的数,i的右端都是大于等于mid的数
if(j>l) qsort(l,j);
if(i<r) qsort(i,r);
}
4、选择排序
先从n个数中选出最小的放在第一位,再从剩下(n-1)个数中选出次小的放在第二位......以选择当前状态最小值的方式完成排序,类似于贪心的想法。
for(int i=1;i<=n-1;i++) {
key=a[i];
t=i;
for(int j=i;j<=n;j++) {
if(a[j]<key) {
key=a[j];
t=j;
}
}
a[t]=a[i];
a[i]=key;
}