第四章 排序
1、排序的来源
排序算法是一种基本并且常用的算法。排序就是将杂乱无章的数据元素,通过一定的方法按关键字顺序排列的过程。排序是信息学中贯穿始末的一项技能,又是各种算法的结晶。让我们来体会一下各种算法设计思想在排序中的体现。
由于实际工作中处理的数量巨大,所以排序算法对算法本身的速度要求很高。所以衡量一个排序算法的好坏,一个重要方面就是衡量其运算速度的快慢。
2、排序的概念
排序就是对数据元素序列进行建立某种有序排列的过程。更确切的说,排序就是把一个数据元素序列(或称为“一组数据元素”)按照关键字整理成递增(或递减)排列的过程。
例如,下表就是一个学生成绩表,其中每个学生都记录有姓名、数学、语文、物理、英语。这些元素构成了5个域:姓名,数学,语文,物理,英语。在排序时,要注意找到根据哪个域来排序,这样才能进行接下来的排序(写程序时,思考问题时,都要注意思维的精确性)。比如,如果使用数学来排序,那么我们会得到一个排序结果,而如果使用物理来排序,那么会得到另外一种结果。
学生成绩表:
姓名 数学 语文 物理 英语
Frank 94.0 76.5 90.0 77.0
Tom 85.0 88.0 72.5 79.5
Jack 77.0 90.0 90.5 89.5
Nancy 85.0 86.5 88.0 90.5
所谓的“关键字”,是指要排序的数据元素集合中的一个域,排序就是以这个关键字为基准进行的。例如,对上表,我们既可以对数学成绩来排序,也可以对语文成绩来排序。那么,对数学成绩排序时,数学域就是排序的“关键字”;同样,对语文成绩排序时,语文这个域,就成为了相应的排序域。
更进一步来说,关键字还有主关键字、次关键字之分。现举例说明:请按照数学成绩降序、物理成绩降序来排序。那么,排序结果是:Frank、Nancy、Tom、Jack。
3、排序算法的分类
3.1 直接插入排序
顾名思义,插入排序的方法是:从初始有序的子集合开始,不断的把新的数据元素插入到已排列有序子集合的合适位置上,使子集合中数据元素的个数不断增多,直至将全部元素插入完毕。
算法:顺序地把待排序的数据元素按其关键字值的大小,插入到已排序数据元素集合的适当位置。子集合的数据元素个数从只有一个元素开始,逐渐依次增大,当其集合大小和最终数据大小相同时,排序完毕。
例如:(假设按照递增排列)
初始关键字序列:[64] 5 7 89 6 24
第一次排序结果:[5 64] 7 89 6 24
第二次排序结果:[5 7 64] 89 6 24
第三次排序结果:[5 7 64 89] 6 24
第四次排序结果:[5 6 7 64 89 ] 24
第五次排序结果:[5 6 7 24 64 89]
见教材P278。
习题:请将序列 55 78 98 12 54 02 13 按照递减顺序排序,并写出具体过程。
3.2 直接选择排序
直接选择排序的方法是:从待排序的数据元素集合中选取关键字最小的数据元素并将其与原始数据元素集合中第一个数据元素交换位置;然后从不包第一个位置上数据元素的集合中选取关键字最小的数据元素并将它与原始的数据元素集合中的第二个数据元素交换位置;重复上述操作,直到最后剩下一个数据元素为止。
算法:
从待排序的数据元素集合中选择关键字最小的数据元素
将其与原始数据元素中的第一个数据元素交换位置
从不包括第一个位置上的数据元素集合中选择关键字最小的数据元素
将其与原始数据元素中的第二个数据元素交换位置
如此重复上述过程,直到最后剩下一个元素为止
例如:
初始关键字序列:64 5 7 89 6 24
第一次排序结果:[5] 64 7 89 6 24
第二次排序结果:[5 6 ] 7 89 64 24
第三次排序结果:[5 6 7 ] 89 64 24
第四次排序结果:[5 6 7 24 ] 64 89
第五次排序结果:[5 6 7 24 64] 89
最终结果:[5 6 7 24 64 89]
见教材P282。
习题:请将序列 55 78 98 12 54 02 13 按照递减顺序排序,并写出具体过程。
3.3 交换排序
利用交换数据元素的位置进行排序的方法称为交换排序。常用的交换排序方法有冒泡排序法和快速排序法。
(1)冒泡排序法
冒泡排序法是最简单的排序方法。
这种方法的基本思想是,将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮。在冒泡排序算法中我们要对这个“气泡”序列处理若干遍。所谓一遍处理,就是自底向上检查一遍这个序列,并时刻注意两个相邻的元素的顺序是否正确。如果发现两个相邻元素的顺序不对,即“轻”的元素在下面,就交换它们的位置。显然,处理一遍之后,“最轻”的元素就浮到了最高位置;处理二遍之后,“次轻”的元素就浮到了次高位置。在作第二遍处理时,由于最高位置上的元素已是“最轻”元素,所以不必检查。一般地,第i遍处理时,不必检查第i高位置以上的元素,因为经过前面i-1遍的处理,它们已正确地排好序。
算法简单描述:
在要排序的一组数中,对当前还未排好序的范围内的全部数,自上而下对相邻的两个数依次进行比较和调整,让较大的数往下沉,较小的往上冒。即:每当两相邻的数比较后发现它们的排序与排序要求相反时,就将它们互换。
所以被形象地称为“冒泡”。
编程要点和难点:双重循环。
使用冒泡排序法时,要使用双重循环的方法。所谓双重循环,亦即在一级循环中再嵌套一层循环,达到对数组中的每个数据进行次遍历的效果。
例如:
例一、初始序列: 38 5 19 26 49 97 1 66
第一阶段:
第一次比较: 5 38 19 26 49 97 1 66
第二次比较: 5 19 38 26 49 97 1 66
第三次比较: 5 19 26 38 49 97 1 66
第四次比较: 5 19 26 38 49 97 1 66
第五次比较: 5 19 26 38 49 97 1 66
第六次比较: 5 19 26 38 49 1 97 66
第七次比较: 5 19 26 38 49 1 66 97
第二阶段结果:
5 19 26 38 1 49 66 97
第三阶段结果:
5 19 26 1 38 49 66 97
第四阶段结果:
5 19 1 26 38 49 66 97
第五阶段结果:
5 1 19 26 38 49 66 97
第六阶段结果:
1 5 19 26 38 49 66 97
第七阶段结果:
1 5 19 26 38 49 66 97
最终排序结果:
1 5 19 26 38 49 66 97
习题:请将序列 55 78 98 12 54 02 13 按照递减顺序排序,并写出具体过程。
(2)快速排序法
快速排序是对冒泡排序的一种改进,它是目前最快的排序算法。
它的基本思想是:通过一躺排序将要排序的数据分割成独立的两部分,其中一部分的所有数据都比另外一不部分的所有数据都要小,然后再按次方法对这两部分数据分别进行快速排序,整个排序过程可以递归进行,以此达到整个数据变成有序序列。
具体做法:任取待排序列的某个纪录,(通常选择第一个数),作为基准。按照该数值的大小,将整个序列分成两个序列——左侧的所有记录的数值都比基准小(或者相等),右侧的都比基准大,基准则放在两个子序列之间,显然这时该基准已经放在了最后应该放在的位置上。然后,使用递归的方法,分别对左右子序列重复使用上面的操作,直到最后所有的纪录都放在了相应的位置。
编程要点和难点:递归
快速排序法是一种使用递归的算法。关于递归,详见上文,这里不再重复。
例如: 见课本P289
3.4 归并排序
归并排序主要指二路归并排序。
快速排序的基本思想是基于分治策略的。对于输入的子序列L[p..r],如果规模足够小则直接进行排序,否则分三步处理:
分解(Divide):将输入的序列L[p..r]划分成两个非空子序列L[p..q]和L[q+1..r],使L[p..q]中任一元素的值不大于L[q+1..r]中任一元素的值。
递归求解(Conquer):通过递归调用快速排序算法分别对L[p..q]和L[q+1..r]进行排序。
合并(Merge):由于对分解出的两个子序列的排序是就地进行的,所以在L[p..q]和L[q+1..r]都排好序后不需要执行任何计算L[p..r]就已排好序。
这个解决流程是符合分治法的基本步骤的。因此,快速排序法是分治法的经典应用实例之一。
它的实现方法是:
设数组中存放了N个数据元素,初始时我们把它们看成是N个长度为1的有序子数组。
从第一个子数组开始两两合并,得到N/2 个长度为2的新的有序子数组(当N为奇数时,最后一个新的有序子数组的长度为1)
对这些新的有序子数组再次两两合并(此处可以用插入排序,也可以是其他任何排序方法,对结果影响不大)
重复上述过程,直到得到一个新的长度为N的有序数组为止。
例如:
初始关键字序列: 72 73 71 23 94 16 05 68 64
第一次归并结果: 72 73 23 71 16 94 05 68 64
第二次归并结果: 23 71 72 73 05 16 68 94 64
第三次归并结果: 05 16 23 68 71 72 73 94 64
第四次归并结果: 05 16 23 64 68 71 72 73 94
3.5 基数排序
基数排序也叫做桶排序(很形象),是一种当关键字为整数类型时非常高效的排序方法。
见教材P292。
4、排序算法性能比较
排序算法 最好时间 平均时间 最坏时间 辅助空间 稳定性
直接插入排序 O(n) O(n2) O(n2) O(1) 稳定
直接选择排序 O(n2) O(n2) O(n2) O(1) 不稳定
冒泡排序 O(n) O(n2) O(n2) O(1) 稳定
快速排序 O(n x lbn) O(n x lbn) O(n2) O(lbn) 不稳定
归并排序 O(n x lbn) O(n x lbn) O(n x lbn) O(n) 稳定
基数排序 O(m x n) O(m x n) O(m x n) O(n) 稳定