8.1 基本概念和排序方法概述
1、排序:排序是按关键字的非递减或非递减顺序对一组记录重新进行排列的操作。
2、稳定性:
在序列中若有Ri=Rj(Ri领先于Rj),排序后序列中Ri仍领先于Rj,则称所用的排序方法是稳定的;反之,则称所用的排序方法是不稳定的。
3、内部排序:待排序记录全部存放在计算机内存中进行排序的过程。
外部排序:待排序记录的数量很大,以致内存一次不能容纳全部记录,在排序过程中尚需对外存进行访问的排序过程。
4、内部排序方法的分类:
插入类:主要包括直接插入排序、折半插入排序和希尔排序。
交换类:主要包括冒泡排序和快速排序。
选择类:主要包括简单选择排序、树形选择排序和堆排序。
归并类:最常见的是2-路归并排序。
分配类:利用分配和收集两种基本操作来完成,主要包括基数排序。
5、存储方式:顺序表、链表。
6、排序算法效率的评价指标:执行时间(时间复杂度)、辅助空间(空间复杂度)
8.2 插入排序
1、直接插入排序
将一条记录插入到已排好序的有序表中,从而得到一个新的、记录数量增1的有序表。
void InsertSort(SqList &L) { for(i=2;i<=L.length;++i) if(L.r[i].key1].key) //"<",需将r[i]插入有序子表 { L.r[0]=L.r[i]; //将待插入的记录暂时存到监视哨中 L.r[i]=L.r[i-1]; //r[i-1]后移 for(j=i-2;L.r[0].key //从后向前寻找插入位置,<保证稳定 L.r[j+1]=L.r[j]; //记录逐个后移,直到找到插入位置 L.r[j+1]=L.r[0]; //将r[0]即原r[i],插入到正确位置 } }
时间复杂度O(n^2),空间复杂度O(1).
稳定性:稳定排序。循环条件r[0].key < r[j].key保证的。
适用于顺序表和链式表
当初始记录无序,n比较大时,此算法时间复杂度较高,不宜采用。
2、折半插入法
利用“查找”中的“折半查找”来实现插入排序
时间复杂度O(n^2),空间复杂度O(1)
稳定性:稳定排序。
只能用顺序表,不能用链式表
适合初始记录无序,n比较大时的情况。
3、希尔排序(缩小增量排序法)
思想:把待排序序列分成若干较小的子序列,然后逐个使用直接插入排序法排序,最后再对一个较为有序的序列进行一次排序,主要是为了减少移动的次数,提高效率。原理应该就是从无序到渐渐有序,要比直接从无序到有序移动的次数会少一些。
void ShellInsert(RecordType r[], int length, int delta) { for(i = 1 + delta; i <= length; i++)/*1+delta为第一个子序列的第二个元素的下表*/ if(r[i].key < r[1 - delta].key) { r[0] = r[i]; for(j = i – delta; j > 0 && r[0].key < r[j].key; j -=delta) r[j + delta] = r[j]; r[j + delta] = r[0]; } } void ShellSort(RecordType r[], int length, int delta[], int n) { for(i = 0; i <= n – 1; ++i) ShellInsert(r, length, delta[i]); }
时间复杂度O(n^3/2),空间复杂度O(1)
稳定性:不稳定排序。{2,4,1,2},2和1一组4和2一组,进行希尔排序,第一个2和最后一个2会发生位置上的变化。
只能用顺序结构,不能用链式结构
8.3 交换排序
交换排序基本思想:凉凉比较待排序记录的关键字,一旦发现两个记录不满足次序要求时则进行交换,直到整个序列全部满足要求为止。
1、冒泡排序
最简单的交换排序方法。它通过两两比较相邻记录的关键字,如果发生逆序,则进行交换。
void BubbleSort(SqList &L) { m=L.length-1;flag=1; while((m>0)&&(flag==0)) { flag=0; for(j=1;j<=m;j++) if(L.r[j].key>L.r[j+1].key) { flag=1; t=L.r[j]; L.r[j]=L.r[j+1]; L.r[j+1]=t; } --m; } }
时间复杂度O(n^2),空间复杂度O(1)
可用顺序结构,也可用链式结构
稳定性:稳定排序
移动记录次数较多,算法平均时间性能比直接插入排序差。当初始记录无序,n比较大时,此算法不宜采用。
2、快速排序
是冒泡排序的改进版。一次交换可能消除多个逆序。
void QKSort(RecordType r[], int low, int high) { int pos; if(low < high) { pos = QKPass(r, low, high); QKSort(r, low, pos - 1); QKSort(r, pos + 1, high); } } int QKPass(RecordType r[], int left, int right) { RecordType x; int low, high; x = r[left]; low = left; high = right; while(low < high) { while(low < high && r[high].key >= x.key) high--; if(low < high) { r[low] = r[high]; low++; } while(low < high && r[low].key < x.key) low++; if(low < high) { r[high] = r[low]; high--; } } r[low] = x; return low; }
时间复杂度:平均复杂度O(nlog2n),最坏时间复杂度O(n^2)
空间复杂度:最好:O(log2n),最坏:O(n)
稳定性:非顺次的移动导致排序方法是不稳定的。
排序过程中需要定位表的下界和上界,所以适合用于顺序结构,很难用于链式结构。
适合记录无序,n比较大时的情况
8.4 选择排序
1、简单选择排序(直接选择排序)
void SelectSort(SqList &L) { for(i = 1; i <= L.length; ++i) { k = i; for(j = i + 1; j <= L.length; ++j) if(L.r[j].key < L.r[k],key) k = j; if(k != i) { t = L.r[i]; L.r[i] = L.r[k]; L.r[k] = t; } } }
时间复杂度O(n^2),空间复杂度O(1)
稳定性:不稳定
2、树形选择排序(锦标赛排序)
对n个记录的关键字进行两两比较,然后在其中n/2(向下取值)个较小者之间再进行两两比较,如此重复,直至选出最小关键字的记录为止。
时间复杂度O(nlog2n)
稳定性:稳定排序
3、堆排序
是一种树形选择排序,在排序过程中,将待排序的记录r[1...n]看成是一棵完全二叉树的顺序存储结构,利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序的序列中选择关键字最大(或最小)的记录。
void sift(RecordType r[], int k, int m) { /*假设r[k...m]是以r[k]为根的完全二叉树,而且分别以r[2k]和r[2k+1]为根的左右子树为大根堆,调整r[k],使整个序列r[k...m]满足堆的性质*/ t = r[k];/*暂存“根”记录r[k]*/ x = r[k].key; i = k; j = 2 * i; finished = FALSE; while(j <= m && !finished) { if(j < m && r[j].key < r[j + 1].key) j = j + 1;/*若存在右子树,且右子树根的关键字大,则沿右分支“筛选”*/ if(x >= r[j].key) finished = TRUE;/*筛选完毕*/ else { r[i] = r[j]; i = j; j = 2 * i; }/*继续筛选*/ } r[i] = t;/*将r[k]填入到恰当的位置*/ }
void crt_heap(recordType r[], int length) { n = length; for(i = n / 2; i >= 1; --i)/*自第n/2向下取整 个记录开始进行筛选建堆*/ sift(r, i, n); }
void HeapSort(RecordType r[], int length) { crt_heap(r, length); n = length; for(i = n; i >= 2; --i) { b = r[1];/*将堆顶记录和堆中的最后一个记录互换*/ r[1] = r[i]; r[i] = b; sift(r, 1, i - 1);/*进行调整,使r[1…i-1]变成堆*/ } }
时间复杂度O(nlog2n),空间复杂度O(1)
稳定性:不是稳定排序
只能用于顺序结构,不能用于链式结构
初建堆所需的比较次数比较多,因此记录数较少是不宜采用
8.5 归并排序
将两个或两个以上的有序表合并成一个有序表的过程。将两个有序表合并成一个有序表的过程称为2-路归并,2-路归并最为简单和常用。
基本思想:假设初始序列含有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到n/2(向下取值)个长度为2或1的有序子序列;再两两归并,……,如此重复,直至得到一个长度为n的有序序列为止。
void Merge(RecordType r1[], int low, int mid, int high, RecordType r2[]) { /*已知r1[low...mid]和r1[mid + 1...high]分别按关键字有序排列,将它们合并成一个有序序列,存放在r2[low...high]*/ i = low; j = mid + 1; k = low; while((i <= mid) && (j <= high)) { if(r1[i].key <= r1[j].key) { r2[k] = r1[i]; ++i; } else { r2[k] = r1[j]; ++j; } ++k; } while(i <= mid) { r2[k] = r1[i]; k++; i++; } while(j <= high) { r2[k] = r1[j]; k++; j++; } } void MSort(RecordType r1[], int low, int high, RecordType r3[]) { /*r1[low...high]经过排序后放在r3[low...high]中,r2[low...high]为辅助空间*/ RecordType r2[N]; if(low == high) r3[low] = r1[low]; else { mid = (low + high) / 2; MSort(r1, low, mid, r2); MSort(r1, mid + 1, high, r2); Merge(r2, low, mid, high, r3); } } void MergeSort(RecordType r[], int n) { /*对记录数组r[1...n]做归并排序*/ MSort(r, 1, n, r); }
时间复杂度O(nlog2n),空间复杂度O(n)
稳定性:稳定排序
可用顺序结构,也可用于链式结构,且不需要附加存储空间,但递归实现时仍需要开辟相应的递归工作栈。
8.8 小结
8.9
最后一次博客作业啦!!!完美撒花!!!
祝我考试顺顺利利!!!