目录
注意:
[1]时间复杂度:O(n)~O(n2)~O(n2)表示最好情况下的时间复杂度~平均情况下的时间复杂度~最坏情况下的时间复杂度
[2]稳定性:如果两条记录关键词相同,经排序后它们的相对次序不变,则称该排序算法是稳定的。例如,对任意两条记录R1和R2,如果它们的关键词K1=K2,并且初始状态下R1在R2前面,那么经稳定的排序算法排序之后R1依然在R2前面。
一、插入排序
1.直接插入排序
算法思想:进行n-1次插入,其中第i次插入是将a[i]插入一个已经排序好的数组a[0..i-1]
算法 直接插入insertSort(待排序数组a[],数组长度n)
S1.[特殊情况处理]
如果数组长度小于等于1,算法结束.
S2.[初始化]
i<-1.
S3.[假设a[0],...,a[i-1]已经排好序,将a[i]插入a[0],...,a[i-1]中]
ends<-i-1.
m<-a[i].
WHILE ends>=0 && m<a[ends] DO(
a[ends+1]<-a[ends].
ends<-ends-1.
)
//将m(原来的a[i])插入a[ends]的后面:
a[ends+1]<-m.
S3.[i<n?]
//a[n-1]的插入是否已经完成?
i<-i+1.
IF i<n
THEN GOTO S3.
ELSE
算法结束.
/*直接插入排序,从小到大*/
void insertSort(int a[],int n){
for(int i=1;i<n;i++){
int ends=i-1;
int m=a[i];
for(;ends>=0&&m<=a[ends];ends--){
a[ends+1]=a[ends];
}
a[ends+1]=m;
}
return;
}
/*直接插入排序2,从小到大*/
void insertSort2(int a[],int n){
for(int j=1;j<n;j++){
/*把a[j]插入a[0],a[1],...,a[j-1]*/
int i=j-1;
int key=a[j];
//寻找a[j]应该插入的位置:
while(i>=0&&key<a[i]){
a[i+1]=a[i];
i--;
}
a[i+1]=key;
}
return;
}
2.希尔排序
算法思想:把待排序的记录按步长step进行分组,对每个分组进行直接插入排序;然后按一定的规则减小步长,重复上面的步骤;当步长减小到0时,算法结束
(步长为step的分组为:a[0],a[step],a[step*2],...、a[1],a[1+step],a[1+step*2],...、a[2],a[2+step],a[2+step*2],...、......、a[step-1],a[2*step-1],a[3*step-1],...)
算法 希尔排序shellSort(待排序数组a[],数组长度n)
S1.[计算初始步长]
step<-n/2.
S2.[对每个步长为step的分组插入排序]
/*
例如当n=16,step=4时,分组如下:
a[0],a[4],a[8],a[12]; a[1],a[5],a[9],a[13]; a[2],a[6],a[10],a[14]; a[3],a[7],a[11],a[15]
先把a[4]插入a[0]、a[5]插入a[1]、a[6]插入a[2]、a[7]插入a[3];
再把a[8]插入a[4],a[0]、a[9]插入a[5],a[1]、a[10]插入a[6],a[2]、a[11]插入a[7],a[3];
……
以此类推,直至每个分组都完成了排序.
*/
S3.[缩短步长]
step<-step/2.
IF step>0 THEN GOTO S2.
ELSE 算法结束.
/*希尔排序,从小到大*/
void shellSort(int a[],int n){
//步长:
int step=n/2;
while(step>0){
/*对每个步长为step的分组插入排序*/
for(int j=step;j<n;j++){
int i=j-step;
int key=a[j];
//把a[j]插入a[0],..,a[j-step]:
while(i>=0&&]key<a[i){
a[i+step]=a[i];
i=i-step;
}
a[i+step]=key;
}
step=step/2;
}
return;
}
二、交换排序
1.冒泡排序
算法思想:不断交换序列中的反序对,直到不再有反序对为止
算法 冒泡排序bubbleSort(待排序数组a[],数组长度n)
S1.[假设数组中有反序对]
exchange<-true.
S2.[判断是否具有反序列]
先假设没有反序对:exchange<-false.
如果相邻两元素是反序列,则交换这两个元素,并令exchange<-true.
如果数组中具有反序列(exchange==true),则跳转到步骤S2;否则结束算法.
//注意:由于每趟冒泡至少能把序列中最大的数找出来,所以不必每趟冒泡都把整个数组遍历一遍
/*冒泡排序,从小到大*/
void bubbleSort(int a[],int n){
bool exchange = true;
for(int ends=n-1;ends>0&&exchange;ends--){
exchange = false;
for(int i=0;i<ends;i++){
if(a[i]>a[i+1]){
int m=a[i];
a[i]=a[i+1];
a[i+1]=m;
exchange=true;
}
}
}
return;
}
2.快速排序
算法思想:先从序列中选择一个数作为基准,通常选最左边的数(也可以都选最右边的数);每趟排序,都把作为基准的数放到了它在序列中的正确位置,并且这时候基准数将序列划分为了两部分;再分别对这两部分序列作快排
算法 快速排序Qsort(待排序数组a[],数组下届low,数组上界up)
Q1.[递归出口]
IF low>=up THEN RETURN.
Q2.[寻找基准数在序列中的正确位置]
Q2.1.取最左边的数a[low]为基准数key. left<-low. right<-up.
Q2.2.right从右往左找小于key的数
Q2.3.left从左往右找大于key的数
Q2.4.交换a[left]和a[right]
Q2.5.IF left<right THEN GOTO Q2.2
Q2.6.交换a[left]和a[low]//此时left==right
Q3.[用基准数将序列划分为两部分,对它们分别进行快速排序]
Qsort(a,low,l-1).
Qsort(a,l+1,up).
void Qsort(int a[],int low,int up){
if(low>=up)
return;
int l=low,r=up,key=a[low];
while(l<r){
//r从右往左找小于key的值
while(l<r&&a[r]>=key){
r--;
}
//l从左往右找大于key的值
while(l<r&&a[l]<=key){
l++;
}
int m=a[l];
a[l]=a[r];
a[r]=m;
}
a[low]=a[l];
a[l]=key;
Qsort(a,low,l-1);
Qsort(a,l+1,up);
}
三、选择排序
1.直接选择排序
算法思想:进行n-1次选择,其中第i次选择是从序列中找出第i小的元素
算法 直接选择selectSort(待排序数组a[],数组长度n)
S1.[初始化]
i<-0.
S2.[从a[i],...,a[n-1]中找出第i小元素的位置min]
min<-i.
FOR j=i+1 TO n-1 DO
IF a[j]<a[min] THEN min<-j.
交换a[min]和a[i].
S3.[是否找到倒数第二小的元素]
IF i<(n-1)
THEN i<-i+1.GOTO S2.
ELSE
算法结束.
/*直接选择排序,从小到大*/
void selectSort(int a[],int n){
for(int i=0;i<n-1;i++){
int mins=i;
for(int j=i+1;j<n;j++){
if(a[j]<a[mins])
mins=j;
}
if(mins!=i){
int m=a[i];
a[i]=a[mins];
a[mins]=m;
}
}
return;
}
2.堆排序
算法思想:先构建一个大根堆(父节点比左右儿子节点大的二叉树),大根堆的根节点就是序列中最大的数;交换根节点与最后节点的位置,将最后一个节点(交换位置前的根节点)剔除出大根堆;重建大根堆,之后重复上一步骤,直至大根堆为空
- 实际编程中用数组存储大根堆就可以了,不需要使用二叉链表
- 注意:对于第i个节点:
- 它的父节点为第(i-1)/2个节点;
- 它的左子节点为第2i+1个节点;
- 它的右子节点为第2i+2个节点。
算法 堆排序heapSort(待排序数组a[],数组长度n)
H1.[构建大根堆]
/*因为包含一个节点的二叉树是大根堆,所以我们从最后一个节点a[n-1]的父节点a[(n-2)/2]开始,把以a[(n-2)/2]为根节点的二叉树恢复成大根堆*/
FOR i=(n-2)/2 TO 0 DO
restore(a,i,n).
H2.[排序]
length<-n-1.
//如果大根堆不为空,就取出根节点,然后重建大根堆:
WHILE length>0 DO(
交换a[length]和a[0].
restore(a,0,length).//使包含length个节点的二叉树重新成为大根堆
length<-length-1.
)
算法 重建堆restore(待重建的大根堆a[],根节点下标root,节点个数length)
/*假设a[length]原来是一个大根堆,因为根节点是大根堆中最大的数,我们交换了大根堆的根节点和最后一个节点的位置,并且使大根堆的节点数减1。这时a[length-1]可能不再是一个大根堆了,restore要做的就是把a[length-1]重新恢复成一个大根堆*/
R1.[初始化]
j<-root
R2.[建堆]
//只要a[j]不是a[length]的父节点:
WHILE j<=(length-1)/2 DO(
求a[j]的左右子节点的最大值a[m].
IF a[j]<a[m]
THEN 交换a[j]和a[m].
ELSE
j<-length.//a已经是大根堆了,算法结束.
)
/*swapa:交换数组a下标为i、j的元素*/
void swapa(int a[],int i,int j){
int t=a[i];
a[i]=a[j];
a[j]=t;
}
/*restore:重建堆*/
void restore(int a[],int root,int length){
int j=root;
while((j<=(length-1)/2)){
/*求a[j]的左右子节点的最大值a[m]*/
int m=2*j+1;//取a[m]为a[j]的左子节点
if(m+1<length&&a[m]<a[m+1])
m++;//右子节点比左子节点大
/*a[j]与a[m]比较*/
if(m<length&&a[j]<a[m]){
swapa(a,j,m);
j=m;
}else{
j=length;
}
}
return;
}
/*堆排序,从小到大*/
void heapSort(int a[],int n){
//构建大根堆:
for(int i=(n-1)/2;i>=0;i--)
restore(a,i,n);
//重建堆:
int length=n-1;
while(length>0){
swapa(a,0,length);
restore(a,0,length);
length=length-1;
}
return;
}
四、合并排序
算法思想:对长度为n的序列,将其划分为n个文件,合并相邻的两个文件(合并后的文件应是有序的);此时共有(n+1)/2个文件,再次合并相邻的两个文件;以此类推,直至剩下两个文件合并成一个文件之后,算法结束。
注意:合并排序需要使用额外的空间
算法 文件合并Merge(源文件a[],i,i,n,目标文件x[])
/*假设数组a[i],a[i+1],...,a[j-1]部分以及a[j],a[j+1]...,a[n-1]部分都已经排好序了,现将文件1 (a[i],a[i+1],...,a[j-1]) 以及文件2 (a[j],a[j+1]...,a[n-1]) 按从小到大的顺序合并到文件x[i],...,x[n-1]中*/
M1.[初始化]
j0<-j.counts<-i.
M2.[合并文件1和文件2到文件x中,直至有一个文件为空]
M3.[将文件1或文件2剩下的内容全部复制到文件x中]
算法 一趟合并MPass(源文件a[],步长l,文件长度n,目标文件x[])
M1.[初始化]
i<-0.
M2.[合并相邻两个长度都为l的子文件]
WHILE i<n DO(
Merge(a,i,i+l,i+2*l,x).//合并a[i],a[i+1],...,a[i+l-1]和a[i+l],...,a[i+2l-1]
i<-i+l*2.
)
M3.[合并剩余的文件]
IF i+l<n THEN Merge(a,i,i+l,n,x)
ELSE IF i<n THEN WHILE i<n DO( x[i]<-a[i].i++. )
算法 合并排序MSort(源文件a[],文件长度n,目标文件x[])
M1.[初始化步长]
l<-1.
M2.[交替合并长度为l、2l的文件]
MPass(a,l,n,x).
l<-l*2.
MPass(x,l,n,a).
l<-l*2.
M3.[l<n?]
IF l<n THEN GOTO M2.
ELSE 算法结束.
/*
*-合并相邻的两个文件Merge-
*文件1下标为i,i+1,...,j-1;文件2下标为j,j+1,...,n-1
*x为辅助空间
*/
void Merge(int a[],int i,int j,int n,int x[]){
int j0=j;
int counts=i;//计数
while(i<j0&&j<n){
if(a[i]<a[j]){
x[counts]=a[i];
i++;
counts++;
}else{
x[counts]=a[j];
j++;
counts++;
}
}
while(i<j0){
x[counts]=a[i];
i++;
counts++;
}
while(j<n){
x[counts]=a[j];
j++;
counts++;
}
return;
}
/*
*-一趟合并MPass-
*将长度为l(或小于l)的子文件合并
*/
void MPass(int a[],int l,int n,int x[]){
int i=0;
if(l>=n){
//如果l>=n,就把a复制给x
for(i=0;i<n;i++)
x[i]=a[i];
return;
}
/*合并相邻的长度为l的子文件*/
for(i=0;i<n;i+=2*l){
Merge(a,i,i+l,i+2*l,x);
}
/*合并剩余长度小于2l或l的子文件*/
if(i<n){
if(i+l<n){
//剩余长度<2*l,但>l
Merge(a,i,i+l,n,x);
}else{
//剩余长度<=l
for(;i<n;i++){
x[i]=a[i];
}
}
}
return;
}
/*
*-合并排序MSort,从小到大-
*由于使用了两趟交替合并,排序的结果在a[]中
*/
void MSort(int a[],int n,int x[]){
int l=1;
while(l<n){
MPass(a,l,n,x);
l*=2;
MPass(x,l,n,a);
l*=2;
}
}