第3 单元 排 序 算 法 Gwszw四级1-7.indd 35 2023/9/13 9:52:46 跟我学 Python 四级 “我在整理老照片啊,本想按拍照日期排序, 可照片太多了!头疼啊……” “这个简单,可以用老师讲过的排序算法来实现。” “小萌,这两天没见,你都在忙什么呢?” 排序是使用计算机时经常进行的一种操作,其目的是将一组“无序”的数 据调整为“有序”。例如,做早操时同学们要按照身高排好队列,单词表按照 字母a~z 排序,一个班级里学生名册可以按照姓名、学号排序等。 抛开算法细节不说,排序本质上就是根据某种规则来比较元素,当元素排 序和规则冲突时,调整其顺序。在第2 单元中,我们学习了如何衡量算法的计 算成本,对于排序算法来讲,比较其优劣最直观的评价指标就是完成排序所需 的比较次数。 当然,排序算法的执行效率和数据规模有关。很显然,“将10 名同学按照 身高排序”和“将全世界所有的城市按照人口数量排序”两个问题的难度不可 同日而语。在数据量较小时,排序很简单。可随着数据量的增大,排序就需要 消耗大量的计算资源。排序算法的应用可以有效提升排序操作的执行效率,而 其中最常用的就是冒泡排序、选择排序和插入排序。 3.1 冒 泡 排 序 1.算法介绍 冒泡排序(Bubble Sort)是一种最简单的交换排序。它重复地走访过要排 序的序列,一次比较两个元素,如果它们的顺序错误,就把它们交换过来。重 复地进行以上操作直到没有元素再需要交换,此时序列已经排序完成。 Gwszw四级1-7.indd 36 2023/9/13 9:52:48 第3单元 排序算法 “我们在喝汽水时,瓶子中会有很多小气泡缓 缓往上飘,这是因为小气泡是由二氧化碳组成的, 它们比水要轻,所以气泡会上浮。冒泡排序的处 理过程和它类似,算法中的每个元素都像气泡一 样,根据规则(升序或降序)朝数列的尾部移动!” “老师,为什么叫冒泡排序?这名字还挺可 爱的!” 我们通过一个例子来演示下算法的排序过程。 假设有一个序列:6 5 7 3 4,现在要将它们按照升序排列。 冒泡排序的第一轮排序过程如图3-1 所示。 图3-1 冒泡排序-升序(第一轮) 经过第一轮的比较,最大数7 已经沉底,没有必要再参与后续处理过程。 因此,在第二轮排序时可以排除已沉底的元素7,对剩余元素(5,6,3,4) 重复以上排序过程。 想 想 一 请大家自己试试看,在练习本上完成剩余元素的排序吧。 Gwszw四级1-7.indd 37 2023/9/13 9:52:52 跟我学Python四级 “不错,所以我们可以知道,如果要对n 个元 素进行冒泡排序,那么完成最终排序就需要经过 n–1 轮运算。” “老师,我排好了!总共经过了4 轮排序,您看, 34567,整整齐齐!” 现在来总结下冒泡排序的算法过程(以升序为例)。 步骤1:比较相邻的元素。如果大的在前,就交换它们。 步骤2:对每一对相邻元素做同样的操作,从开始第一对到结尾的最后一 对,这样在最后的元素会是最大的数。 步骤3:针对所有的元素重复以上的步骤,除了最后一个元素。 步骤4:重复步骤1~3,直到排序完成。 “这个简单,只要将比较的规则改一下就可以 了。例如,将规则改为:比较两个数时,如果小的 在前,那么就交换位置,反之则不交换。” “老师,刚刚一直说的是升序,如果想降序排 列呢?” 2.代码实现 冒泡排序(升序)代码实现如下。 def bubbleSort(alist): #每次需遍历元素个数递减 for num in range(len(alist)-1,0,-1): for i in range(num): if alist[i] > alist[i+1]: alist[i],alist[i+1] = alist[i+1],alist[i] Gwszw四级1-7.indd 38 2023/9/13 9:52:53 第3单元 排序算法 练练一 【问题3-1】请使用冒泡排序实现如下列表中元素的降序排列。 alist = [55,26,83,17,88,34,47,59,22] 3.算法复杂度 利用第2 单元所学知识来分析下冒泡排序的算法复杂度。对含有n 个元素 的序列排序需要n–1 轮。每一轮的比较次数如表3-1 所示。 表3-1 冒泡排序每一轮比较次数 轮次比较次数 1 n-1 2 n-2 …… n-1 1 通过观察可以发现,总的比较次数是前n–1 个数的和,即 1 2 n2- 1 2 n,所以 该算法的时间复杂度为O(n2)。冒泡排序最好的情况是序列已经有序,这时不 需要任何交换操作,只需一轮比较即可完成,此时的时间复杂度为O(n)。相反, 最坏的情况就是每一次比较都需要交换。冒泡排序不需要借助额外存储空间, 其空间复杂度为O(1)。 3.2 选 择 排 序 1.算法介绍 选择排序与冒泡排序具有一定的相似性,二者的区别在于: Gwszw四级1-7.indd 39 2023/9/13 9:52:53 跟我学Python四级 选择排序是根据排序规则(升序或降序)找到其中最大或最小的数, 将其放到序列的末尾,而非冒泡排序中的“两两比较,随即交换”。 所以,选择排序在每轮排序中只做一次交换。为了便于理解,我们还是利 用3.1 节中的示例来演示选择排序的操作过程。 假设有一个序列:6 5 7 3 4,现在要将它们按照升序排列。 选择排序算法的排序过程如图3-2 所示。 图3-2 选择排序过程 “小萌说得对,和冒泡排序时一样,如果要对 n 个元素进行排序,那么完成最终排序就需要经过 n–1 轮运算。” “老师,我发现了,5 个数排序需要4 轮完成! 和冒泡排序时一样!” 选择排序的运算过程总结如下( 以升序为例)。 步骤1:从当前序列中找出最大的元素,将它与序列的队尾元素交换,如 其为队尾元素,则在原位置不动。 步骤2:针对所有的元素重复以上的步骤,除了最后一个。 步骤3:重复步骤1、步骤2,直至排序完成。 Gwszw四级1-7.indd 40 2023/9/13 9:52:56 第3单元 排序算法 想 想 一 请大家想一想,如果要使用选择排序实现降序排列,那么比较规则应该如 何修改? 2.代码实现 选择排序(升序)代码实现如下。 def selectionSort(alist): #每次遍历元素个数递减 for i in range(len(alist)-1,0,-1): #指定初始的最大值下标为0 max_idx = 0 #遍历剩余元素,依次与当前最大值比较 for j in range(1,i+1): if alist[j] > alist[max_idx]: #更新最大值下标 max_idx = j #将最大值与末尾元素交换 alist[i],alist[max_idx] = alist[max_idx],alist[i] 练练一 【问题3-2】请使用选择排序实现如下列表中元素的降序排列。 alist = [55,26,83,17,88,34,47,59,22] 3.算法复杂度 从算法原理可知,无论序列中元素是否有序,为了找出最大(最小)值, Gwszw四级1-7.indd 41 2023/9/13 9:52:57 跟我学Python四级 每一轮中元素间的相互比较都是必不可少的,且其比较次数和冒泡排序算法 的比较次数相同,所以不管在最好或最坏情况下,选择排序的时间复杂度都是 O(n2),但是选择排序每轮只交换一次,所以其运行速度比冒泡排序更快。选择 排序无须借助额外存储空间,其空间复杂度为O(1)。 3.3 直接插入排序 1.算法介绍 在这一节中,我们再来学习插入排序。插入排序的衍生算法有很多,如直 接插入排序、折半插入排序、希尔排序等,这里介绍较为基础的直接插入排序。 对于少量元素的排序,它是一个有效的算法。 直接插入排序的基本思想是通过构建有序序列,对于未排序数据,在已排 序序列中从后向前扫描,找到相应位置并插入。 “其实只用一个列表就可以了,下面我就用例 子来解释一下!” “老师,直接插入排序是需要准备两个列表 吗?一个有序,一个无序,这个算法描述有点复杂。” 还是通过之前的例子来讲解直接插入排序的操作过程。 假设有一个数列:6 5 7 3 4,现在要将它们按照升序排列。 插入排序过程如图3-3 所示。 直接插入排序的运算过程描述如下(以升序为例)。 步骤1:首先将队首位置的元素视为“有序列表”中的第一个元素,其余 元素视为“无序列表”。 步骤2:将“无序列表”中的第一个元素a 与“有序列表”中的元素从后 向前依次进行比较。当“有序列表”中某个元素b 比a 大时,将元素b 右移, Gwszw四级1-7.indd 42 2023/9/13 9:52:58 第3单元 排序算法 继续向前进行比较,直到“有序列表”中没有比a 大的元素时停止,将a 插入; 如果“有序列表”中没有比a 大的元素,则将a 插入到“有序列表”队尾;如 果待插入的元素a 与有序序列中的元素b 相等,则将元素a 插入到相等元素b 的后面。 步骤3:重复步骤1、步骤2,直至“无序列表”中元素为空,则排序完成。 想 想 一 如果要使用直接插入排序完成降序排列,那么算法要如何修改呢? 2.代码实现 直接插入排序(升序)代码实现如下。 def insertSort(alist): #将第一个元素视为有序,其余元素为无序,依次遍历无序元素 for idx in range(1,len(alist)): #记录当前待插入无序元素的值和下标 current = alist[idx] position = idx #从后向前依次与有序元素比较,如果该元素比待插入值大,将其后移, 图3-3 插入排序过程 Gwszw四级1-7.indd 43 2023/9/13 9:53:01 跟我学Python四级 #直到有序列表中没有比其大的元素时停止 while position > 0 and alist[position-1] > current: alist[position] = alist[position-1] position = position - 1 #将待插入元素插入到当前位置 alist[position] = current 练练一 【问题3-3】请使用直接插入排序实现如下列表中元素的降序排列。 alist = [55,26,83,17,88,34,47,59,22] 3.算法复杂度 在直接插入排序中,最好的情况是待排序序列本身是有序的,只需将当 前数与前一个数进行一次比较即可,此时共需要比较n–1 次,时间复杂度为 O(n)。 最坏的情况是待排序序列是逆序的,此时需要比较次数最多,总次数记为 1+2+3+…+n–1,所以,直接插入排序最坏情况下的时间复杂度为O(n2)。 综上分析可知,直接插入排序的算法时间复杂度为O(n2)。直接插入排序 不需要借助额外存储空间,其空间复杂度为O(1)。 本单元涉及三种排序算法,它们的时间复杂度和空间复杂度情况汇总如 表3-2 所示。 表3-2 三种排序算法的复杂度 排序方法 时间复杂度 (平均) 时间复杂度 (最好) 时间复杂度 (最坏) 空间复杂度 冒泡排序O(n2) O(n) O(n2) O(1) 选择排序O(n2) O(n2) O(n2) O(1) 直接插入排序O(n2) O(n) O(n2) O(1) Gwszw四级1-7.indd 44 2023/9/13 9:53:01 “本单元我们主要学习了冒泡排序、选择排序、直接插 入排序的算法思想和实现方法。希望大家能够根据应用场 景和数据存储结构选择合适的算法完成排序,掌握各排序 算法的时间复杂度和空间复杂度。本单元后面的习题,有 助于检验大家的学习效果,抓紧做一下吧。” 习 题 1. 从未排序序列中挑选元素,并将其依次放入已排序序列的一端,这种 排序方法称为( )。 A. 插入排序 B. 冒泡排序 C. 选择排序 D. 堆排序 2. 对n 个元素进行直接插入排序,完成排序所需要的轮数是( )。 A. n B. n+1 C. n–1 D. 2n 3. 对n 个元素进行冒泡排序,最好情况下的时间复杂度为( )。 A. O(1) B. O(log2n) C. O(n2) D. O(n) 4. 对n 个元素进行直接插入排序,算法的空间复杂度为( )。 A. O(1) B. O(log2n) C. O(n2) D. O(nlog2n) 5. 设一组初始记录关键字序列(5,2,6,3,8),利用冒泡排序进行升序排序, 且排序中从后向前进行比较,则第一趟冒泡排序的结果为( )。 A. 2,5,3,6,8 B. 2,5,6,3,8 C. 2,3,5,6,8 D. 2,3,6,5,8 6. 使用冒泡排序对包含50 个元素的序列进行排序,在最坏情况下,比较 3 3 次数是( )。 A. 150 B. 1225 C. 2450 D. 2500 7. 有台计算机使用选择排序对400 个数字排序花了400ms,如果花费 1600ms,大概能够完成排序的数字个数是( )。 A. 1200 B. 800 C. 1600 D. 3200 8. 有关选择排序的叙述中正确的是( )。 A. 每扫描一遍序列,只需要交换一次 Gwszw四级1-7.indd 45 2023/9/13 9:53:02 046 跟我学Python 四级 B. 每扫描一遍序列,需要交换多次 C. 时间复杂度是O(n) D. 空间复杂度为O(n) 9. 对序列(54,38,96,23,15,72,60,45,83)进行直接插入排序(升 序),插入时由后向前比较,当把第八个元素45 插入到有序表时,为找到插入 位置需比较的次数为( )。 A. 4 B. 6 C. 5 D. 3 10. 编写程序实现如下的功能。 (1)针对给定列表alist = [55,26,83,17,88,34,47,59,22],使 用冒泡排序算法编写函数bubbleSort 实现alist 中元素的降序排列。 (2)调用bubbleSort 函数,输出排序后的列表。 11. 编写程序实现如下的功能。 (1)针对给定列表alist = [55,26,83,17,88,34,47,59,22],使 用选择排序算法编写函数selectionSort 实现alist 中元素的升序排列。 (2)调用selectionSort 函数,输出排序后的列表。 Gwszw四级1-7.indd 46 2023/9/13 9:53:04