1、第九章 内部排序,9.1 概述9.2 插入排序9.3 快速排序9.4 选择排序9.5 归并排序9.6 基数排序,排序的定义 所谓排序,就是要整理表中的记录,使之按关键字递增(或递减)有序排列。其确切定义如下:输入:n个记录,R0,R1,Rn-1,其相应的关键字分别为k0,k1,kn-1。输出:R,R,R,使得kkk(或kkk)。,9.1 概述,排序的性质稳定性 当待排序记录的关键字均不相同时,排序的结果是惟一的,否则排序的结果不一定惟一。 如果待排序的表中,存在有多个关键字相同的记录,经过排序后这些具有相同关键字的记录之间的相对次序保持不变,则称这种排序方法是稳定的; 反之,若具有相同关键字的
2、记录之间的相对次序发生变化,则称这种排序方法是不稳定的。 排序的分类 在排序过程中,若整个表都是放在内存中处理,排序时不涉及数据的内、外存交换,则称之为内排序; 反之,若排序过程中要进行数据的内、外存交换,则称之为外排序。,9.1 概述,排序的基本操作 比较关键码大小(比较元素) 移动记录(调整记录的顺序)待排序记录序列的存储方式 存放在数组中(地址连续) 存放在静态链表中 存在一组地址连续的存储单元中,另附一地址向量 常用排序算法 插入、交换、选择、归并、计数排序,9.1 概述,本章讨论中,设待排序的顺序表类型的类型定义如下:typedef Maxsize 20typedef int Key
3、Type; /*定义关键字类型*/typedef struct /*记录类型*/ KeyType key; /*关键字项*/InfoType data; /*其他数据项,类型为 InfoType*/ RecType; /*排序的记录类型定义*/ typedef structRecType rMaxsize+1;int length;SqList;,排序的一种基本方法是维护一个已经排序的子序列: 每步把一个待排序的记录按关键字大小插入已排序的部分序列中的适当位置 一个记录的序列是已排序的(作为工作的开始) 当所有记录都插入排序序列时,排序完成 插入排序包括 直接插入排序 折半插入排序 2路插入排
4、序 表插入排序 希尔排序,9.2 插入排序,直接插入排序 待排序的n个记录R0,R1,Rn-1 存在数组中,直接插入法在插入记录Ri(i=1,2n-1)时,记录序列分为两个区间R0,Ri-1 和Ri,Rn-1 ,前一区间里的记录已排好序,后一区间尚未排序。每次取出排序码Ki依次与Ki-1,Ki-2,K0比较,找出应插入位置将Ri插入,原位置的记录向后顺移。,9.2 插入排序,K,空位,例 直接插入排序方法进行排序的过程。,49 38 97 76 65 13 27 49,初 始: L.r0,38 49 97 76 65 13 27 49,38 49 97 76 65 13 27 49,49,38
5、 49 76 97 65 13 27 49,13 27 38 49 65 76 97 49,13 38 49 65 76 97 27 49,38 49 65 76 97 13 27 49,13 27 38 49 49 65 76 97,(38),(97),(76),(65),(13),(27),(49),n个数据的排序需要n-1趟插入,例 直接插入排序方法每趟插入的过程。,初 始: L.r0,38 49 76 97 65 13 27 49,38 49 76 97 13 27 49,第5趟:,(65),1 2 3 4 5 6 7 8,每趟插入分三步:(设为第i趟) 将第i个记录存放到哨兵位l.r
6、0 找要插入的位置,同时后移记录 从右到左将前i-1个记录的关键值与第i个记录进行比较 (设j为当前比较的记录的下标,且j从i-1开始) 若第i个记录l.rj.key 则l.rj后移 否则,说明找到插入位置,停止比较 插入第i个记录,void InsertSort(SqList /*在j+1处插入Ri*/ ,直接插入排序性能分析 排序的时间效率与什么直接有关? 与排序过程中元素之间的比较次数直接有关。若原始序列为一个按值递增的序列,则排序过程中一共要经过多少次元素之间的比较? 由于每一趟排序只需要经过一次元素之间的比较就可以找到被插入元素的合适位置,因此,整个n-1趟排序一共要经过n-1 次元
7、素之间的比较。若原始序列为一个按值递减的序列,则排序过程中一共要经过多少次元素之间的比较? 由于第i 趟排序需要经过i-1次元素之间的比较才能找到被插入元素的合适位置,因此,整个n-1趟排序一共要经过 (i-1)=n(n-1)/2次元素之间的比较。时间复杂度?,9.2 插入排序,折半插入排序 插入排序的基本操作是在有序表中进行查找,这种查找可以用二分查找实现。这样实现的排序称为二分法插入排序。,9.2 插入排序,13 38 49 65 76 97 27 49,low,high,mid,high,high,mid,low,low,mid,high,mid,high,void BiInsertSo
8、rt(SqList /*high+1处插入Ri*/ ,直接插入排序的特点 时间复杂度为O(n2) 若待排序记录序列为“正序”时,其时间复杂度可以提高到O (n) 直接插入算法简单,则在n值很小时效率也很高 希尔排序 充分利用直接插入排序的特点进行改进 基本思想:先将整个待排记录序列分割成若干子序列分别进行直接插入排序,待整个序列中的记录“基本有序”时,再对全体记录进行一次直接插入排序,9.2 插入排序,希尔排序 希尔排序也是一种插入排序方法,实际上是一种分组插入方法。 思路: 先取定一个小于n的整数d1作为第一个增量,把表的全部记录分成d1个组,所有距离为d1的倍数的记录放在同一个组中,在各组
9、内进行直接插入排序 然后,取第二个增量d2(d1),重复上述的分组和排序 直至所取的增量dt=1(dtdt-1d2d1),即所有记录放在同一组中进行直接插入排序为止。,9.2 插入排序,例 直接插入排序方法进行排序的过程。,49 38 65 97 76 13 27 49 55 04,初 始:,d=5,d=3,d=1,13 27 49 55 04 49 38 65 97 76,13 04 49 38 27 49 55 65 97 76,04 13 27 38 49 49 55 65 76 97,void ShellInsert(SqList ,直接插入排序 时间复杂度O(n2) 稳定的 希尔排序
10、 时间复杂度 不稳定的,9.2 插入排序,交换排序的基本思想: 两两比较待排序记录的关键字,发现两个记录的次序相反时即进行交换,直到没有反序的记录为止。 两种交换排序: 冒泡排序 快速排序划分思想,9.3 快速排序,冒泡排序 基本思想 通过无序区中相邻记录关键字间的比较和位置的交换,使关键字最小的记录如气泡一般逐渐往上“漂浮”直至“水面”,而关键字较大的记录好比石块往下沉 思路 第i 趟排序对序列的前n-i+1个元素从第一个元素开始依次作如下操作:相邻的两个元素比较大小,若前者大于后者,则两个元素交换位置,否则不交换。,9.3 快速排序,冒泡排序 思路 第i 趟排序对序列的前n-i+1个元素从
11、第一个元素开始依次作如下操作:相邻的两个元素比较大小,若前者大于后者,则两个元素交换位置,否则不交换。,9.3 快速排序,49 97 38 13 27 50 76 65,49 38 13 27 50 76 65 97,38 13 27 49 50 65 76 97,13 27 38 49 50 65 76 97,13 27 38 49 50 65 76 97,void BubbleSort(RecType R,int n) int i,j; RecType temp;for (i=1;iRj+1.key) temp=Rj; /*Rj与Rj-1进行交换*/Rj=Rj+1;Rj+1=temp; ,
12、冒泡排序 最坏时间复杂度为O(n2),平均时间复杂度为O(n2)。最好情况是序列已经排序,时间为O(n) 冒泡排序算法中的辅助空间是O(1) 这一冒泡排序算法是稳定的 冒泡排序算法具有适应性,9.3 快速排序,快速排序 快速排序是由冒泡排序改进而得的, 基本思想 在待排序的n个记录中任取一个记录(通常取第一个记录),把该记录放入适当位置后,数据序列被此记录划分成两部分。所有关键字比该记录关键字小的记录放置在前一部分,所有比它大的记录放置在后一部分,并把该记录排在这两部分的中间(称为该记录归位),这个过程称作一趟快速排序。 之后对所有的两部分分别重复上述过程,直至每部分内只有一个记录为止。 简而
13、言之,每趟使表的第一个元素放入适当位置,将表一分为二,对子表按递归方式继续这种划分,直至划分的子表长为1。,9.3 快速排序,49,38,65,97,76,13,27,49,9.3 快速排序,如何找到记录的适当位置? 并使得所有关键字比该记录关键字小的记录放置在前一部分,所有比它大的记录放置在后一部分,快速排序一趟排序示例,49,38,65,97,76,13,27,49,38,65,97,76,13,27,49,27,38,65,97,76,13,49,27,38,97,76,13,65,49,27,38,13,97,76,65,49,27,38,13,76,97,65,49,27,38,13
14、,49,76,97,65,49,49,pivotkey,49,49,49,49,49,快速排序 一趟排序步骤 low1 (即i), high=n(即j) 从右边开始比较 ,找一个比枢轴小的记录 若rhigh.key=pivotkey,则high继续左移动 否则将其移到左边右边有空位 从左边开始比较 ,找一个比枢轴大的记录 若rlow.key=pivotkey,则low继续右移动 否则将其移到右边左边有空位 直到low=high时,说明pivotkey已经位于的合适位置,9.3 快速排序,快速排序算法 int PARTITION(SqList ,快速排序示例,49,38,65,97,76,13,
15、27,49,38,65,97,76,13,27,49,27,38,65,97,76,13,49,27,38,97,76,13,65,49,27,38,13,97,76,65,49,27,38,13,76,97,65,49,27,38,13,49,76,97,65,49,49,pivotkey,快速排序算法 int PARTITION(SqList ,快速排序示例,27,38,13,49,76,49,65,49,一次划分之后,分别进行 快速排序,27,38,13,76,97,65,49,65,结束,结束,结束,49,结束,13,27,38,49,49,65,76,97,有序序列,简而言之,每趟使
16、表的第一个元素放入适当位置,将表一分为二,对子表按递归方式继续这种划分,直至划分的子表长为1。,void Qsort(Sqlist ,快速排序 时间复杂度分析 最好情况 O(nlogn) 最坏情况 O(n2) 平均时间复杂度 O(nlogn) 不稳定的,9.3 快速排序,选择排序基本思想 每一趟在n-i+1(i=1,2,n-1)个记录中选取关键字最小的记录作为有序序列中的第i个记录 简单选择排序 树型选择排序 堆排序,9.4 选择排序,简单选择排序 第i趟排序开始时,当前有序区和无序区分别为R0i-1和Rin-1(0in-1),该趟排序则是从当前无序区中选出关键字最小的记录Rj,将它与无序区的
17、第1个记录Ri交换,使R0i和Ri+1n-1分别变为新的有序区和新的无序区。,9.4 选择排序,49 38 65 97 49 13 27 76 13 38 65 97 49 49 27 76 13 27 65 97 49 49 38 76 13 27 38 97 49 49 65 76 13 27 38 49 97 49 65 76 13 27 38 49 49 97 65 76 13 27 38 49 49 65 97 76 13 27 38 49 49 65 76 97,i 1 2 3 4 5 6 7,void SelectSort(RecType R,int n) int i,j,k;
18、RecType temp;for (i=0;in-1;i+) /*做第i趟排序*/ j=i;for (k=i+1;kn;k+) /*在in-1中选key最小的Rk */if (Rk.keyRj.key)j=i; /*k记下的最小关键字所在的位置*/if (j!=i) /*交换Ri和Rk */ temp=Ri; Ri=Rk; Rk=temp; ,简单选择排序 时间复杂度 O(n2) 主要操作是关键字之间的比较 改进 在n个关键字中选出最小值,至少进行n-1次比较,然而,继续在剩余的n-1个关键字中选择次小值就并非一定要进行n-2次比较,若能利用前n-1次比较所得信息,则可减少以后各趟选择排序中所
19、用的比较次数 体育比赛中锦标赛,9.4 选择排序,体育比赛中锦标赛,9.4 选择排序,树型选择排序 是一种按照锦标赛的思想进行选择排序的方法 首先对n各记录的关键字进行两两比较,然后在其中n/2个较小者之间再进行两两比较,如此重复,直至选出最小关键字的记录为止,9.4 选择排序,树型选择排序 8个叶子结点中依次存放排序之前的8个关键字 非终端结点的关键字等于其左右孩子的较小者,9.4 选择排序,树型选择排序从叶子结点开始重新修改从叶子结点到根的路径上各结点的关键字,得到次小关键字,9.4 选择排序,树型选择排序 继续,9.4 选择排序,堆排序 堆排序是一树形选择排序,它的特点是,在排序过程中,
20、将R1n看成是一棵完全二叉树的顺序存储结构(数组),利用完全二叉树中双亲结点和孩子结点之间的内在关系,在当前无序区中选择关键字最大(或最小)的记录,9.4 选择排序,堆的定义是:n个关键字序列K1,K2,Kn称为堆,当且仅当该序列满足如下性质(简称为堆性质):(1)KiK2i 且 KiK2i+1 或 (2)KiK2i 且 KiK2i+1(1in/2) 满足第(1)种情况的堆称为小根堆,满足第(2)种情况的堆称为大根堆。,9.4 选择排序,1 2 3 4 5 6,1 2 3 4 5 6 7 8,堆排序的基本过程(考虑从小到大排序): 首先通过调整记录,把数组变成一个大(或小)顶堆 取出堆顶的最大
21、(或最小)元素后,再调整数组,使数组里剩下的元素序列重新成为一个(大/小顶)堆 反复选取和重建堆的操作,直至得到所有记录的有序序列 两个问题 如何由一个无序序列建成一个堆 在输出堆顶元素后,如何调整得到一个新堆筛选,9.4 选择排序,筛选自堆顶至叶子的调整过程 假设输出堆顶元素后,以堆中最后一个元素替代堆顶元素 此时根结点的左右子树根均为堆,则仅需自上而下进行调整(小根堆) 先对堆顶元素的左、右子树根结点的值进行比较,得到两者中较小者 若堆顶元素大于较小者,则交换,否则停止 交换后破坏了左(或右)子树的堆,则需进行和上述相同的调整(调整下一层),直至叶子结点 s :表示待调整的结点位置,初值为
22、堆顶元素下标j :表示s结点的左子树根结点的较小者,设初值为2*s,9.4 选择排序,筛选自堆顶至叶子的调整过程,9.4 选择排序,97,38,49,49,76,65,27,13,13,13,堆排序,9.4 选择排序,void HeapAdjust(Sqlist /*被筛选结点的值放入最终位置*/,初始堆的建立 从一个无序序列建堆的过程就是一个反复“筛选”的过程,若将此序列看成是一个完全二叉树,则最后一个非终端结点是第n/2个元素,由此筛选只需从第 n/2个元素开始。 例子:关键字序列为 49, 38, 65, 97, 76, 13, 27, 49,n=8,故从第四个结点开始调整,9.4 选择
23、排序,例子:关键字序列为 49, 38, 65, 97, 76, 13, 27, 49,n=8,故从第四个结点开始调整,9.4 选择排序,堆排序,9.4 选择排序,void HeapSort(Sqlist ,堆排序,9.4 选择排序,堆排序的时间复杂性为O(n log2n) 空间复杂性为 O(1)堆排序是不稳定的排序方法。,归并排序 是多次将两个或两个以上的有序表合并成一个新的有序表。最简单的归并是直接将两个有序的子表合并成一个有序的表。 2路归并排序基本思想 假设初始序列含有n个记录,则可看成是n个有序的子序列,每个子序列的长度为1,然后两两归并,得到个n/2 个长度为2或1的有序子序列;再
24、两两归并,如此重复,直至得到一个长度为n的有序序列为止。,9.5 归并排序,归并排序过程示例,初始关键字,第,1,趟归并,第,2,趟归并,第,3,趟归并,二路归并 将一维数组中两个位置相邻、并且各自按值有序的子序列合并为一个按值有序的子序列的过程,9.5 归并排序,(12, 45, 78, 90, 100, 125),(27, 32, 51, 93 ),( 12, 27, 32, 45, 51, 78, 90, 93, 100, 125 ),i,j,k,如有序的sr1m和srm+1n归并为有序的trin 则i变化范围? j?,void Merge(RecType sr, int i, int
25、m, int n) for (j=m+1,k=i;i=m ,实现一次二路归并算法,二路归并排序算法分析 时间复杂度 n个元素的序列采用二路归并排序,排序趟数为log2n. 每趟需要比较n次 时间复杂度为O(n log2n) 空间复杂度 需和待排序记录等数量的辅助空间 归并排序是稳定的排序,9.5 归并排序,基本原理,采用“分配”和“收集”的办法,用对多关键字进行排序的思想实现对单关键字进行排序的方法。 多关键字排序方法 如对扑克牌的排序 每张扑克牌有两个“关键字”:花色和面值它们之间有次序的优先。 对以上排序,可以先对花色排序,或先对面值排序。,9.6 基数排序,多关键字有序的概念,9.6 基
26、数排序,考虑对象序列V0,V1,., Vn-1,每个对象Vi含d个关键字(Ki1,Ki2,., Kid)。若对序列中的任意两个对象Vi和Vj(1=ij=n)都有(Ki1,Ki2,., Kid) (Kj1,Kj2,., Kjd)则称序列对关键字(Ki1,Ki2,., Kid)有序,且K1称为最高位关键字,Kd称为最低位关键字。,多关键字排序,9.6 基数排序,原理:根据组成关键字的各位的值进行排序。实现多关键字排序的两种方法:1 最高位优先(MSD)排序:从关键字的高位到低位2 最低位优先(LSD)排序:从关键字的低位到高位MSD方法通常是一个递归的过程。,多关键字排序,9.6 基数排序,LSD
27、和MSD方法也可应用于对一个关键字进行的排序。此时可将单关键字Ki看成是一个子关键字组:(Ki1,Ki2,., Kid)如对关键字取值范围为0到999的一组对象,可看成是(K1,K2,K3)的组合。MSD方法按K1,K2,K3的顺序对所有对象排序;LSD方法按K3 ,K2 , K1的顺序对所有对象排序。,链式基数排序,9.6 基数排序,基数排序是一种典型的LSD排序方法,它利用“分配”和“收集”两种运算对单关键字进行排序。 此时可将单关键字K看成是一个子关键字组:(Ki1,Ki2,., Kid)每一位子关键字的值都在0kir范围内,其中,r称为基数。排序过程:设关键字共有d位,让j= d, d
28、-1,.,1, 依次执行d次“分配”与“收集”,链式基数排序 假设线性表由结点序列a0,a1,an-l构成,每个结点aj的关键字由d元组(k0,k1,ki,kd-1)组成,其中0kir-1 (0jn,0id-1),在排序过程中,使用r个链队列Q0,Q1,Qr-1(f为队头,e为队尾) 排序过程如下:进行d次的分配和收集,9.6 基数排序,分配:开始时,把Q0,Q1,Qr-1各个队列置成空队列,然后依次考察线性表中的每一个结点aj(j=0,1,n-1),如果aj的关键字k=k,就把aj放进Qk队列中。收集:把Q0,Q1,Qr-1各个队列中的结点依次首尾相接,得到新的结点序列,从而组成新的线性表。,按照个位进行分配,按照十位进行分配,按照百位进行分配,作业,题集p61 10.1,10.10,10.12,