第9章 排序(Sort)
排序:假设含有n个记录的序列为{r1,r2,……,rn},其相应的关键字分别为{k1,k2,……,kn},需确定1,2,……,n的一种排序
p1,p2,……,pn,使其相应的关键字满足kp1<=kp2<=……<=kpn(非递减或非递增)关系,即使得序列称为一个按关键字有序
的序列{rp1,rp2,……,rpn},这样的操作就称为排序
主要介绍7中排序的算法,按照算法的复杂度分为两大类:
简单算法:冒泡排序、简单选择排序和直接插入排序
改进算法:希尔排序、堆排序、归并排序和快速排序
结构定义
#define MAXSIZE 10 /* 用于要排序数组个数最大值,可根据需要修改 */ typedef struct { int r[MAXSIZE+1]; /* 用于存储要排序数组,r[0]用作哨兵或临时变量 */ int length; /* 用于记录顺序表的长度 */ }SqList;
void swap(SqList *L, int i, int j) { int temp = L->r[i]; L->r[i] = L->r[j]; L->r[j] = temp; }
冒泡排序(Bubble Sort)
冒泡排序,一种交换排序,它的基本思想是:两两比较相邻记录的关键字,如果反序则交换,直到没有反序的记录为止。
时间复杂度O(n^2)
/* 对顺序表L作交换排序(冒泡排序初级版) */ void BubbleSort0(SqList *L) { int i, j; for(i=1;i<L->length;i++) { for(j=i+1;j<L->length;j++) { if (L->r[i]>L-r[j]) { swap(L,i,j); } } } } /* 对顺序表L作冒泡排序 */ void BubbleSort(SqList *L) { int i, j; for(i=1;i<L->length;i++) { for(j=L->length-1;j>=i;j--) /* 注意j是从后往前循环 */ { if (L-r[j] > L->r[j+1]) /* 若前者大于后者(注意这里与上一算法差异) */ { swap(L, j, j+1); /* 交换L->r[j]与L->r[j+1]的值 */ } } } } /* 对顺序表L作改进冒泡算法 */ void BubbleSort(SqList *L) { int i, j; Status flag = true; /* flag用来作为标记 */ for(i=1;i<L->length && flag;i++) /* 若flag为true则退出循环 */ { flag = false; /* 初始为false */ for(j=L->length-1;j>=i;j--) /* 注意j是从后往前循环 */ { if (L-r[j] > L->r[j+1]) /* 若前者大于后者(注意这里与上一算法差异) */ { swap(L, j, j+1); /* 交换L->r[j]与L->r[j+1]的值 */ flag = true; /* 如果有数据交换,则flag为true */ } } } }
简单选择排序(Simp Selection Sort)
简单选择排序法就是通过n-i次关键字间的比较,从n-i+1个记录中选出关键字最小的记录,并和第i(1<=i<=n)个记录交换之。
/* 对顺序表L作简单选择排序 */ void SelectSort(SqList *L) { int i,j,min; for(i=1;i<L->length;i++) { min = i; /* 将当前下标定义为最小值小标 */ for(j=i+1;j<=L->length;j++) /* 循环之后的数据 */ { if (L->r[min]>L->r[j]) /* 如果有小于当前最小值的关键字 */ min = j; /* 将此关键字的下标赋值给min */ } if (i != min) /* 若min不等于i,说明直到最小值,交换 */ swap(L,i,min); /* 交换L->r[i]与L->r[min]的值 */ } }
直接插入排序(Straight Insertion Sort)
直接插入排序的基本操作是将一个记录插入到已经排好序的有序表中,从而得到一个新的、记录数增1的有序表。
/* 对顺序表L作直接插入排序 */ void InsertSort(SqList *L) { int i,j; for(i=2;i<L->length;i++) { if (L->r[i]<L-r[i-1]) /* 需将L->r[i]插入有序子表 */ { L->r[0] = L->r[i]; /* 设置哨兵 */ for(j=i-1;L->r[j]>r[0];j--) L->r[j+1] = L->r[j]; /* 记录后移 */ L->r[j+1] = L->r[0]; /* 插入到正确位置 */ } } }
希尔排序(Shell Sort)
时间复杂度O(n^3/2)
/* 对顺序表L作希尔排序 */ void ShellSort(SqList *L) { int i, j; int increment = L->length; do { increment = increment/3 + 1; /* 增量序列 */ for(i=increment+1;i<L->length;i++) { if (L->r[i]<L[i-increment]) { /* 需将L->r[i]插入有序增量子表 */ L->r[0] = L->r[i]; /* 暂存在L->r[0] */ for(j=i-increment;j>0 && L->r[0]<L->r[j];j-=increment) L->r[j+increment] = L->r[j]; /* 记录后移,查找插入位置 */ L->r[j+increment] = L->r[0]; /* 插入 */ } } }while (increment > 1); }
堆排序(Heap Sort)
堆是具有下列性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;
或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
堆排序就是利用堆(假设利用大顶堆)进行排序的方法。它的基本思想就是,将待排序的序列构造成一个大顶堆。
此时整个序列的最大值就是堆顶的根节点。将它移走(其实就是将其与堆数组的末尾元素交换,此时末尾元素就是
最大值),然后将剩余的n-1个序列重新构造成一个堆,这样就会得到n个元素中的次小值。如此反复执行,便能
得到一个有序序列了。
时间复杂度为O(nlogn)
/* 对顺序表L进行堆排序 */ void HeapSort(SqList *L) { int i; for(i=L->length/2;i>0;i--) /* 把L中的r构建成一个大顶堆 */ HeapAdjust(L,i,L->length); for(i=L->length;i>1;i--) { swap(L,1,i); /* 将堆顶记录和当前未经排序子序列的最后一个记录交换 */ HeapAdjust(L,1,i-1); /* 将L->r[1..i-1]重新调整为大顶堆 */ } } /* 已知L->r[s..m]中记录的关键字除L->r[s]之外均满足堆的定义 */ /* 本函数调整L->r[s]的关键字,使L->r[s..m]成为一个大顶堆 */ void HeapAdjust(SqList *L, int s, int n) { int temp,j; temp = L->r[s]; for(j=2*s;j<=m;j*=2) /* 沿关键字较大的孩子结点向下筛选 */ { if (j<m && L->r[j]<L->r[j+1]) ++j; /* j为关键字中较大的记录的下标 */ if (temp>=L->r[j]) break; /* rc应插入在位置s上 */ L->r[s] = L->r[j]; s= j; } L->r[s] = temp; /* 插入 */ }
归并排序(Merging Sort)
归并排序就是利用归并的思想实现的排序方法,原理是假设初始序列含有n个记录,则可以看成是n个有序的子序列,每个子序列的长度
为1,然后两两归并,得到[n/2]([x]表示不小于x的最小整数)个长度为2或1的有序子序列;再两两归并,……,如此重复,直至得到
一个长度为n的有序序列为止,这种排序方法称为2路归并排序。
时间复杂度为O(nlogn)
/* 对顺序表L作归并排序 */ void MergeSrot(SqList *L) { MSort(L->r,L->r,l,L->length); } /* 将SR[s..t]归并排序为TR1[s..t] */ void MSort(int SR[], int TR1[], int s, int t) { int m; int TR2[MAXSIZE+1]; if (s == t) TR1[s] = SR[s]; else { m = (s+t)/2; /* 将SR[s..t]平分为SR[s..m]和SR[m+1..t] */ MSort(SR,TR2,s,m); /* 递归将SR[s..m]归并为有序的TR2[s..m] */ MSort(SR,TR2,m+1,t); /* 递归将SR[m+1..t]归并为有序TR2[m+1..t] */ Merge(TR2,TR1,s,m,t); /* 将TR2[s..m]和TR2[m+1..t]归并到TR1[s..t] */ } } /* 将有序的SR[i..m]和SR[m+1..n]归并为有序的TR[i..n] */ void Merge(int SR[],int TR[],int i,int m, int n) { int j,k,l; for(j=m+1,k=i;i<=m && j<=n;k++) /* 将SR中记录由小到大归并入TR */ { if (SR[i]<SR[j]) TR[k] = SR[i++]; else TR[k] = SR[j++]; } if (i<=m) { for(l=0;l<=m-i;l++) TR[k+1] = SR[i+1]; /* 将剩余的SR[i..m]复制到TR */ } if (j<=n) { for(l=0;l<=n-j;l++) TR[k+1] = SR[j+1]; /* 将剩余的SR[j..m]复制到TR */ } }
非递归实现归并排序
/* 对顺序表L作归并非递归排序 */ void MergeSort2(SqList *L) { int* TR = (int*) malloc(L->length*sizeof(int)); /* 申请额外空间 */ int k = 1; while(k<L->length) { MergePass(L->r,TR,L->length); k = 2*k; /* 子序列长度加倍 */ MergePass(TR,L->r,k,L->length); k = 2*k; /* 子序列长度加倍 */ } } /* 将SR[]中相邻长度为s的子序列两两归并到TR[] */ void MergePass(int SR[],int TR[],int s,int n) { int i=1; int j; while(i <= n-2*s +1) { Merge(SR, TR, i+s-1,i+2*s-1); /* 两两归并 */ i = i+2*s; } if (i<n-s+1) /* 归并最后两个序列 */ Merge(SR,TR,i,i+s-1,n); else /* 若最后只剩下单个子序列 */ for(j=1;j<=n;j++) TR[j] = SR[j] }
非递归的迭代方法,避免了递归时深度为log2n的栈空间,空间只是用到申请归并临时用得TR数组,
因此空间使用归并也在时间性能上有一定的提升,应该说,使用归并排序时,尽量考虑用非递归方法。
快速排序(Quick Sort)
快速排序的基本思想是:通过一趟排序将待排记录分隔成独立的两部分,其中一部分记录的关键字均
比另一部分记录的关键字小,则可分别对这两部分记录继续进行排序,以达到整个序列有序的目的。
最优的情况下,快速排序算法的时间复杂度O(nlogn)
最坏的情况下,快速排序算法的时间复杂度O(n^2)
/* 对顺序表L作快速排序 */ void QuickSort(SqList *L) { QSort(L,1,L->length); } /* 对顺序表L中的子序列L->r[low..high]作快速排序 */ void QSort(SqList *L,int low, int high) { int pivot; if (low < high) { pivot = Partition(L,low,high); /* 将L->r[low..high]一分为二,算出枢轴值pivot */ QSort(L,low,pivot-1); /* 对低子表递归排序 */ QSort(L,pivot+1,high); /* 对高子表递归排序 */ } } /* 交换顺序表L中子表的记录,是枢轴记录到位,并返回其所在位置 */ /* 此时在它之前(后)的记录均不大(小)于它。 */ int Partition(SqList *L, int low, int high) { int pivotkey; /* 采用了三数取中法,来确定顶枢轴 */ int m = low + (high - low); if (L->r[low]>L->r[high]) swap[L,low,high]; if (L->r[m] > L->r[high]) swap(L,high,m); if (L->r[m] > L->r[low]) swap(L,m,low); /* 三数取中法(结束) */ pivotkey = L->r[low]; /* 用子表的第一个记录作枢轴记录 */ while(low<high) /* 从表的两端交替向中间扫描 */ { while(low<high && L->r[high]>=pivotkey) high--; swap(L,low,high); /* 将比枢轴记录小的记录交换到低端 */ while(low<high&& L->r[low]<=pivotkey) low++; swap(L,low,high); /* 将比枢轴记录大得记录交换到高端 */ } return low; /* 返回枢轴所在位置 */ } /* 快速排序优化算法 */ int Partition1(SqList *L, int low, int high) { int pivotkey; /* 采用了三数取中法,来确定顶枢轴 */ int m = low + (high - low); if (L->r[low]>L->r[high]) swap[L,low,high]; if (L->r[m] > L->r[high]) swap(L,high,m); if (L->r[m] > L->r[low]) swap(L,m,low); /* 三数取中法(结束) */ pivotkey = L->r[low]; /* 用子表的第一个记录作枢轴记录 */ L->r[0] = pivotkey; /* 将枢轴关键字备份到L->r[0] */ while(low<high) /* 从表的两端交替向中间扫描 */ { while(low<high && L->r[high]>=pivotkey) high--; L->r[low] = L->r[high]; while(low<high&& L->r[low]<=pivotkey) low++; L->r[high] = L->r[low]; } L->r[low] = L->r[0]; /* 将枢轴数值替换回L->r[low] */ return low; /* 返回枢轴所在位置 */ }
优化小数组时的排序
#define MAX_LENGTH_INSERT_SORT 7 void QSort(SqList &L, int low, int high) { int pivot; if ((high-low)>MAX_LENGTH_INSERT_SORT) { pivot = Partition(L,low,high); QSort(L,low,pivot-1); QSort(L,pivot+1,high); } else { InsertSort(L); /* 采用直接插入排序 */ } }
优化递归操作
void QSort(SqList &L, int low, int high) { int pivot; if ((high-low)>MAX_LENGTH_INSERT_SORT) { while(low<high) { pivot = Partition1(L,low,high); QSort1(L,low,pivot-1); low = pivot+1; } } else { InsertSort(L); /* 采用直接插入排序 */ } }