本节介绍的排序算法的数据存储结构使用顺序表,为讨论方便定义顺序表为:
#define MAXSIZE 100
typedef int KeyType; /*关键字类型*/
typedef int DataType;
typedef struct{ /*数据元素类型*/
KeyType key; /*关键字*/
DataType data; /*其他数据*/
}SortItem , SqList[MAXSIZE]
一、冒泡排序
假设数据元素存放于数组L中。初始时, L[0]~L[n-1]是无序的。在无序区中,每次均从头至尾依次比较相邻的两个数据元素L[j]与L[j+1],若存在逆序(即L[j]>L[j+1]),则交换二者的位置。每执行这一个过程称为一趟冒泡排序。
void BubbleSort(SqList L,int n){
int i,j,over; SortItem p; /*over为是否发生交换的标志*/
for(i=0;i<n-1;i++) {
over=1; /*最多n-1趟排序*/
for(j=n-1;j>i;j--) /*一趟冒泡排序*/
if(L[j].key<L[j-1].key) { /*逆序,则交换*/
p=L[j]; L[j]=L[j-1];
L[j-1]=p;
over=0; /*发生了交换,不能结束排序*/
}
if(over) break; /*如果不再发生交换,则排序结束*/
}
}
最好的情况下时间复杂度为O(n)。
最坏的情况下时间复杂度为O(n2)。
冒泡排序是稳定的排序方法。
二、快速排序
快速排序是对冒泡排序本质的改进,快速排序通过一次扫描,使某个数(以它为基点)的左边各数都比它小,右边各数都比它大。然后又用同样的方法处理它左右两边的数,直到基准点的左右只有一个元素为止。
数据元素存放于数组L中,当前序列为L[low]~L[upper],upper和low分别为序列的上下界。
(1)一次划分:在序列中,任选一个数据元素L[pivot](称pivot为枢轴)作为基准元素。然后,依次从序列的两端交替向序列中央扫描,将序列中关键字小于L[pivot]的数据元素均移到pivot位置的左边,将大于等于L[pivot]的数据元素均移到pivot位置的右边,这样经过一趟快速排序之后,序列就被基准元素划分为左、右两个子序列L[low]~L[pivot-1]和L[pivot+1]~L[upper],并且左端子序列中所有数据元素均小于基准元素,右边的子序列中所有数据元素均大于等于基准元素。
(2)重复以上划分过程,直至序列被划分为只含有1个数据元素的子序列为止,此时整个序列有序。
使用递归方式的算法实现:
void QuickSort(SqList L,int low,int upper){
int pivot; /*划分后枢轴的位置*/
if(low<upper){
pivot=Partition(L,low,upper); /*划分区间*/
QuickSort(L,low,pivot-1);/*划分后左子序列递归排序*/
QuickSort(L,pivot+1,upper); /*右子序列递归排序*/
}
}
int Partition(SqList L,int i,int j){
SortItem pivot; pivot=L[i]; /*用第1个元素作为基准元素*/
while(i<j) { /*从序列两端交替向中央扫描,直至i=j为止*/
/*从右向左扫描,查找第1个关键字小于基准元素的元素*/
while(i<j && pivot.key<=L[j].key) j--;
if(i<j){L[i]=L[j]; i++;} /*找到第1个小于基准元素的元素*/
/*从左向右扫描,查找第1个关键字大于等于基准元素的数据元素*/
while(i<j && pivot.key>L[i].key ) i++;
if(i<j) {L[j]=L[i];j--;}
}
L[i]=pivot;/*确定枢轴的位置*/
return i;
}
最好的情况下时间复杂度为O(nlog2n)。
最坏的情况下时间复杂度为O(n2)。
冒泡排序是不稳定的排序方法。
三、直接插入排序
直接插入排序基本思想:依次从数据元素集合中取出一个数据元素,按其关键字大小将其插入到集合的适当位置上,直到全部数据元素都被取出过为止。
void InsertSort(SqList L,int n){
int i,j;
SortItem p;
for(i=1;i<n;i++) {
p=L[i];
for(j=i-1;j>=0 && p.key<L[j].key;j--)
L[j+1]=L[j];
L[j+1]=p; /*插入*/
}
}
最好的情况下时间复杂度为O(n)。
最坏的情况下时间复杂度为O(n2)。
直接插入排序是稳定的排序方法。
四、直接选择排序
直接选择排序的基本思想:将数据元素序列分成有序区和无序区两部分。每趟排序都从无序区中选取出关键字最小的数据元素放在有序区的最后,直到全部数据元素排序完毕。
假设数据元素存放于数组L中。初始时,有序区为空,将L[0]~L[n-1]作为无序区。每次从无序区中选出关键字最小的数据元素L[min]( 0≤min≤n-1),与无序区的第1个数据元素交换,使有序区长度增一,无序区长度减一。每执行这一过程称为一趟直接选择排序。
void SelectSort(SqList L,int n){
int i,j,min;/*min为无序区关键字值最小的元素的下标*/
SortItem p;
for(i=0;i<n-1;i++){
min=i;
for(j=i+1;j<n;j++) /*从无序区寻找最小关键字的下标*/
if(L[j].key<L[min].key) min=j;
if(min!=i){/*将最小关键字值元素交换到有序区最后*/
p=L[i];L[i]=L[min];L[min]=p;}
}
}
直接选择排序的时间复杂度为O(n2)。
直接插入排序是不稳定的排序方法。
五、归并排序
归并是指将两个或多个有序序列合并为一个有序序列的过程。
将一个长度为n的无序序列看成是由n个长度为1的有序子序列组成,并把这些子序列中相邻的子序列两两归并,得到个长度为2的有序子序列。然后,再将这些子序列两两归并,如此重复,直至形成一个长度为n的有序序列,这种反复将两个有序子序列归并为一个有序子序列的排序方法称为两路归并排序。
void MergeTwo(SqList L,int low,int mid,int upper){
SortItem* p;/*临时存放归并结果的临时数组*/
int low1=low; int low2=mid+1; int pos=0;
p=(SortItem*)malloc((upper-low+1)*sizeof(SortItem));
while(low1<=mid && low2<=upper)/*两个子序列归并*/
p[pos++]=(L[low1].key<=L[low2].key) ? L[low1++] : L[low2++];
/*将两个子序列尚未处理完的部分复制到p中*/
for(;low1<=mid;low1++,pos++) p[pos]=L[low1];
for(;low2<=upper;low2++,pos++) p[pos]=L[low2];
/*归并完成,p中数据元素复制回L*/
for(pos=0,low1=low;low1<=upper;pos++,low1++) L[low1]=p[pos];
free(p);
}
void Merge(SqList L,int len,int n){
int i;
for(i=0;i+2*len-1<n;i+=2*len)
MergeTwo(L,i,i+len-1,i+2*len-1);
if(i+len-1<n)/*对余下的两个子序列归并*/
MergeTwo(L,i,i+len-1,n-1);
for(i=0;i<n;i++) /*输出每趟归并的结果*/
printf("%d\t",L[i].key);
printf("\n\n");
}
void MergeSort(SqList L,int n){
int i;
for(i=1;i<n;i*=2)
Merge(L,i,n);
}
六、基数排序
基数排序是一种特殊的排序方法,它不比较关键字的大小,而是对组成关键字的各个数位进行排序。基数排序把一个关键字看成是m位r进制数,不是m位的关键字在其前面补零。
基本思想:设置r个队列,编号分别为0,1,2,…,r-1。
按数据元素关键字最低位上的数字值依次把n个数据元素分配到这r个队列中(入队)。
按照队列编号从小到大的顺序,将队列中的元素收集起来,形成一个新的元素序列,这是第一趟基数排序。
接着对第一趟基数排序后得到的数据元素序列,再按照数据元素关键字的次低位上的数字值依次把各个数据元素再次分配到r个队列中,然后按照队列编号从小到大的顺序,将队列中的数据元素收集起来。如此反复,经过m趟基数排序后,就得到了数据元素的有序序列。
typedef int KeyType;
typedef struct{
KeyType key;
}DataType;
void RadixSort(DataType L[],int n,int m,int r){
/* L中的关键字为m位r进制数,L的长度为n */
LinkQueue* q; /*r个链队列的头尾指针存放在q数组中*/
int i,j,k;
q=(LinkQueue*)malloc(r*sizeof(LinkQueue));
for(i=0;i<r;i++) InitQueue(&q[i]);/*初始化r个链队列*/
for(i=0;i<m;i++){ /*进行m次分配与收集*/
for(j=0;j<n;j++) { /*分配*/
k=digit(L[j].key,i,r);
EnQueue(&q[k],L[j]); /*入队操作,见2.5.5节*/
}
k=0;
for(j=0;j<r;j++) /*收集*/
for(;!QueueEmpty(q[j]);k++)
DeQueue(&q[j],&(L[k]));
for(j=0;j<n;j++) /*输出每趟基数排序结果*/
printf("%d\t",L[j].key);
printf("\n\n");
}
for(i=0;i<r;i++) DestroyQueue(q);
}
int digit(KeyType key,int m,int r){
/* 获取key的第m位的数字,该数字是r进制的*/
int I,d;
if(m==0) return key % r;
d=r;
for(i=1;i<m;i++) d*=r;
return ((int)(key/d)%r);
}