一、前言
- 今天介绍常见的几种排序算法使用 Python 实现和复杂度f分析:冒泡排序、选择排序、插入排序、谢尔排序、归并排序。
二、冒泡排序
-
排序思路:算法思路在于对无序表进行多趟比较交换,每趟包括了多次相邻元素的两两比较,并将逆序的数据互换位置,最终能将本趟最大项就位。每趟的过程像 “气泡” 在水中不断上浮到水面的过程,所以叫冒泡排序。
-
代码实现:
-
算法过程:
第一趟比较交换时,会进行 n-1 次相邻数据的比较,一旦经过最大项,则最大项会被一路交换到最后一项,依次类推。 -
算法分析:
无序表对初始数据项的排列状况对冒泡排序没有影响
算法总过程需要 n-1 趟,随着趟数的增加每趟数组中的最大项就位,比对次数逐步从 n-1 减少到 1 并包括可能发生的数据项的交换。比对次数是 1~n-1 的累加。对比的时间复杂度为O(n^2)
交换次数时间复杂度也为O(n^2)
最好
的情况是数组已经排好序,交换次数为 0最差
的情况每次对比都需要交换,交换的次数等于对比的次数,平均
情况就是最差情况的一半。 -
算法总结:冒泡排序是一个时间效率比较差的排序算法,原因是每个数据项在找到对应的位置之前,必须要经过
多次
对比和交换,其中大部分操作都是无效
的。优势的地方在于不需要其它额外的存储空间开销。
三、选择排序
- 排序思路:选择排序对冒泡排序进行了改进,保留了其基本的多趟对比的思路,每趟对比都使最大项就位。但选择排序对交换的过程进行了优化,相比冒泡排序的对次交换,选择排序每一趟对比只需要一次交换,只会记录最大项所在的位置,最后再跟本趟的最后一项交换。
- 代码实现:
- 算法分析:
相比于冒泡排序对比次数不变,时间复杂度依然为O(n^2)
,交换次数时间复杂度减少为O(n)
- 算法总结:
选择排序相比于冒泡排序对数据项交换的环节进行了优化,选择排序稍优于冒泡排序
。
四、插入排序
- 排序思路:插入排序维持一个已经有序的子列表,其位置始终在列表前部,然后逐步扩大这个子列表直到全表。像是在整理扑克牌。
- 代码实现:
- 算法过程:
第一趟排序,子列表仅包含第一数据项,将第二个数据项作为 “新项” 进行对比然后插入到子列表合适到位置中,此时已经排好序到列表就包含了两个数据项,第二趟排序,子列表包含两个数据项,将第三个数据和第二个数据进行插入对比,第三个数项也 “加入” 子列表。经过 n-1 次对比插入,子列表扩展到全表,排序完成。 - 算法分析:
插入排序的比较主要来寻找“新项”
的插入位置
最差
的情况是每趟都与子列表中所有项进行对比,总比对次数与冒泡排序相同,数量级为O(n^2)
最好
情况列表已经排好序的时候,每趟仅需 1 次对比,总次数为O(n)
- 算法总结:
无序表的初始数据项的排列状况对插入排序有一定影响
列表越接近有序,插入排序的次数越少,相比于冒泡和选择排序,插入排序的性能相对会好一些。
五、谢尔排序
- 排序思路:谢尔排序以插入排序为基础,对无序表进行
“间隔”
划分子列表,每个子列表都执行插入排序,随着子列表的数量越来越少,无序表的整体越来越接近有序,从而减少整体排序的对比次数。 - 代码实现:
- 算法过程:
按间隔划分子列表,当间隔为 1 时就是一个标准的插入排序,但由于前面几趟已经将列表处理为接近有序的状态,最后一趟所以只需要几次移动即可完成排序。 - 算法分析:
谢尔排序是以插入排序为基础,每趟都使得列表更加有序,过程会减少很多原先需要的“无效”
比对,谢尔排序的复杂度介于O(n)
和O(n^2)
之间,具体约为O(n^3/2)
- 算法总结:
谢尔排序是以插入排序为基础,可能并不会比插入排序好,但是可以减少许多无效
的对比。