第5章排序 排序是数据结构的一种重要运算。本章5.1节~5.5. 6节介绍内排序的各种方法,7节 介绍外排序方法。此外,堆排序也是一种典型的选择排序,有关堆排序算法,将在第8章 介绍。 5.基本概念 1 在讨论排序的概念之前,首先引入排序码的概念。所谓排序码是结点中的一个或多 个字段,其值作为排序运算中的依据。排序码可以是关键码,这时排序即是按关键码对文 件进行排序;排序码也可以不是关键码,这时可能有多个结点的排序码具有相同的值,因 而排序结果就可能不唯一。排序码的数据类型可以是整数,也可以是实数、字符串,乃至 复杂的组合数据类型。 习惯上,在排序中将结点称为记录,将一系列结点构成的线性表称为文件。在本书中 后续涉及排序时,都要使用记录和文件这两个概念,请读者将它们和外存中的记录、文件 等概念加以区别。 排序(sorting)又称分类。假定具有 n 个记录{A1,A2,…,An }的文件,每个记录有 一个排序码Ki,{K1,K2,…,Kn }是相应的排序码的集合。排序运算就是将上述文件中 的记录按排序码非递减(或非递增)的次序排列成有序序列。 由于各种待排序的文件中,记录的大小和数量不等,有的文件记录本身较大、数量很 多,有的文件的记录本身较小、数量较少。对于较小的文件,可以一次将文件全部调入内 存进行排序处理;而对于很大的文件,无法一次全部调入内存进行排序处理,因而在排序 过程中需要涉及内外存之间的数据交换。在排序过程中,文件全部放在内存处理的排序 算法称为“内排序”;在排序过程中,不仅需要使用内存,而且还要使用外存的排序算法称 为“外排序”。按照所采用的策略的不同,排序方法可以分为5种类型,即插入排序、选择 排序、交换排序、分配排序、归并排序。当然,由于关注的重点不同,一个具体的排序算法 既可以看成是这种排序,也可以看成是那种排序,也就是说,一个具体的排序算法究竟应 该属于上述5种类型中的哪一种并不是唯一的。 在待排序的文件中,可能存在着多个具有相同排序码的记录。如果一个排序算法对 于任意具有相同排序码的多个记录在排序之后,这些具有相同排序码的记录的相对次序 仍然保持不变,则称该排序算法为“稳定的”;否则称该排序算法是“不稳定的”。排序的方 法很多,就其性能而言,很难说哪一种算法是最好的。每一种算法都有各自的优缺点,适 合于不同的应用领域。有两个评价排序算法性能的重要指标:一个是算法执行时所需的 时间;另一个是算法执行时所需的内存空间。其中,时间开销是衡量一个排序算法好坏的 最重要的性能指标。为便于分析,排序算法的时间开销通常用算法执行中的比较次数和 记录移动次数表示。许多排序算法执行排序时所耗费的时间不仅与算法本身有关,而且 与待排序文件的记录顺序有关,衡量这些排序算法的性能可以采用最大执行时间和平均 执行时间。 为讨论方便起见,假定排序要求都是按非递减序进行排序的。 本章后续各节将分别讨论插入排序、选择排序、交换排序、分配排序、归并排序和外排 序,给出典型排序算法的面向对象的具体实现描述。同时,对主要排序算法的性能进行必 要的分析和讨论。执行排序算法所需要的空间量一般都不大,对算法性能好坏的影响并 不大,我们只给出结果,而不加以讨论。 5.插入排序 2 插入排序的基本思想是:每次选择待排序的记录序列的第1个记录,按照排序码的 大小将其插入已排序的记录序列中的适当位置,直到所有记录全部排序完毕。 5.1 直接插入排序 2. 直接插入排序是一种最简单的排序方法,整个排序过程为:先将第1个记录视为一 个有序的记录序列,然后从第2个记录开始,依次将未排序的记录插入这个有序的记录序 列中,直到整个文件中的全部记录排序完毕。在排序过程中,前面的记录序列是已经排好 序的,而后面的记录序列有待排序处理。 例5- 1 假设有5个元素构成的数组,其排序码依次为50 、20 、40 、75 、35,整个数组完 成直接插入排序的过程如图5. 1所示。 图5.直接插入排序的过程 1 下面给出直接插入排序的函数DirectInsertionSort,该函数的参数是存放被排序文件 的数组A和文件中所包含的记录个数(即数组A的大小)。考察第i(遍的 n 1≤i≤n-1) 情况,子文件A[到A[已按非递减序排列,这一遍将记录A[插入到子文件 0] i-1] i] A[0] i-1] i] 0] i-1] i] 到A[中。将A[向子文件A[到A[的前部移动,移动A[前,将 A[的排序码与记录A[A[等进行比较。在小于或等于A[的排序码的第 i] i-1]、i-2] i] 1个记录A[或到达第1个记录A[处停止扫描。当A[往前移动时,要将子文件中j] j] 0] i] i] 将其插入到位置j。 每个遇到的记录A[后移一个位置。当找到A[的正确位置 j 后, 假设文件的排序码是ElementType类型的,key是它的一个排序码。 soth文件如下: r. 94 95 typedef int ElementType; struct forSort { ElementType key; }; typedef struct forSort ForSort; void InitForSort(ForSort *FS, int a) { FS->key=a; } 算法5.1 直接插入排序。 void DirectInsertionSort(ForSort A[], int n) { int i,j; ForSort temp; for(i=1; i0 && temp.keyk;r--) A[r]=A[r-1]; A[k]=temp; /*将temp 插入*/ } } 在算法中,采用k>r 来控制折半的结束,此时,k 就是A[i]应该插入的位置。在判别 下一步应该是在前半部分还是在后半部分继续使用折半时,采用temp.key0;k>>=1) { for(i=k;i=0&&temp.key0&&flag;i--) { flag=FALSE; /*设置未交换标志*/ for(j=0;j