『Python』写代码?程序猿?你不能不懂的八大排序算法的Python实现
『Python』写代码?程序猿?你不能不懂的八大排序算法的 Python 实现
信息获取后通常需要进行处理,处理后的信息其目的是便于人们的应用。信息处理方法有多种,通常由数据的排序,查找,插入,删除等操作。本章介绍几种简单的数据排序算法和高效的排序算法.
本章主要涉及到的知识点有:
简单排序算法: 学会选择排序、冒泡排序、桶排序、插入排序的原理以及代码编写
高效排序算法: 理解希尔排序,基数排序,快速排序和归并排序的原理
1. 简单排序算法
简单排序算法包括选择排序、冒泡排序、桶排序和插入排序,本节重点介绍以上四种简单排序算法。
1.1 选择排序
基本思想:
每一趟从待排序的数据元素中选出最小(或最大)的一个元素,顺序放在待排序的数列的最前,知道全部待排序的数据元素排完。
排序过程:
例如:
1 |
|
对应代码:
1 |
|
1.2 冒泡排序
基本思想:
所谓冒泡排序就是依次将两个相邻的数进行比较,大的在前面,小的在后面。即先比较第一个数和第二个数,大数在前,小数在后,然后比较第 2 个数和第 3 个数,直到比较最后两个数。第一趟结束后,最小数的数一定在最后。第二趟在第一趟的基础上重复上述操作。
由于排序过程中总是大数在前,小数在后, 相当于气泡上升,所以叫冒泡排序。
大数在前,小数在后排序后得到的是降序,小数在前,大数在后排序后得到的是升序结果。
排序过程(降序)
1 |
|
可以发现,第二趟结束已经排好序了,实际上对于一组数据排 n-1 趟一定能排好序。因为第 i 趟都会有前 i 小的数排序好,n-1 趟前 n-1 小的数已排好序,最后一个数自然也排好序了。
对应代码:
1 |
|
1.3 桶排序
基本思想:
桶排序的思想是若待排序的记录的关键字在一个明显有限范围内(整型)时,可设计有限个有序桶,每个桶只能装与之对应的值,顺序输出各桶的值,将得到有序的序列。
例如,输入 n 个 09 之间的整数,由小到大排序输出9。输入的数 0 入 0 号桶,1 入 1 号桶,依次类推。
我们可以准备 10 个桶依次编号为 0
如图所示:
先准备好 10 个空桶并编号。
依次输入 8 个整数:2,5,6,8,5,2,9,6
每输入一次就将数放入对应的桶。
输入完毕后桶内数据如图所示:
桶排序过程
1 |
|
实现代码:
1 |
|
1.4 插入排序
基本思想:
插入排序是一种简单的排序方法,其算法的基本思想是:
假设待排序的数据存放在数组 a[1…n]中,增加一个哨兵节点 x.
a[1]自成一个有序区,无序区为 a[2…n];
从 i=2 起直至 i=n 为止,将 a[i]放在恰当的位置,使 a[1…i]数据序列有序;
x=a[i]
将 x 与前 i-1 个数比较,j=i-1;while(x<a[j]) j-=1
将 a 数组的元素从 j 位置开始向后移动:for k in range(j,i+1,-1): a[k]=a[k-1]
a[j]=x
生成包含 n 个数据的有序区。
例如 a=[3 2 4 1 6 5 2 7]
排序过程:
1 |
|
插入排序的时间复杂度为 O(n*n)。插入排序适用于数据已经排好序,插入一个新数据的情况
实现代码:
1 |
|
注意:此数组有效数据从 i=1 开始,a[0]=0,是为了运算方便补上的。
2. 高效排序算法
第一节介绍了简单的排序算法,但在实际应用中,简单的排序算法很难达到效率的要求,所以本节介绍了两种高效的排序算法,使排序时间复杂度大大减少。
2.1 快速排序
基本思想:
快速排序是一种采用分治法解决问题的一个典型应用,也是冒泡排序的一种改进。它的基本思想是,通过一趟排序将待排记录分割成独立的两部分,其中一部分均比另一部分小,则可分别对这两部分继续进行排序,已达到整个序列有序。排序的时间复杂度为 O(nlogn),相比于简单排序算法,运算效率大大提高。
算法步骤:
① 从序列中取出一个数作为中轴数
② 将比这个数大的数放到它的右边,小于或等于他的数放到它的左边。
③ 再对左右区间重复第二步,知道各区间只有一个数
例如:对以下 10 个数进行快速排序:
6 1 2 7 9 3 4 5 10 8
以第一个数为基准数
在初始状态下,数字 6 在序列的第 1 位。我们的目标是将 6 挪到序列中间的某个位置,假设这个位置是 k。现在就需要寻找这个 k,并且以第 k 位为分界点,左边的数都 ≤6,右边的数都 ≥6。那么如何找到这个位置 k 呢?
我们要知道,快速排序其实是冒泡排序的一种改进,冒泡排序每次对相邻的两个数进行比较,这显然是一种比较浪费时间的。
而快速排序是分别从两端开始”探测”的,先从右往左找一个小于 6 的数,再从左往右找一个大于 6 的数,然后交换他们。这里可以用两个变量 i 和 j,分别指向序列最左边和最右边。我们为这两个变量起个好听的名字“哨兵 i”和“哨兵 j”。刚开始的时候让哨兵 i 指向序列的最左边,指向数字 6。让哨兵 j 指向序列的最右边,指向数字 8。
首先哨兵 j 开始出动。因为此处设置的基准数是最左边的数,所以需要让哨兵 j 先出动,这一点非常重要。哨兵 j 一步一步地向左挪动,直到找到一个小于 6 的数停下来。接下来哨兵 i 再一步一步向右挪动),直到找到一个数大于 6 的数停下来。最后哨兵 j 停在了数字 5 面前,哨兵 i 停在了数字 7 面前
现在交换哨兵 i 和哨兵 j 所指向元素的值。交换之后的序列如下。
到此,第一次交换结束。接下来开始哨兵 j 继续向左挪动(再友情提醒,每次必须是哨兵 j 先出发)。他发现了 4<6,停下来。哨兵 i 也继续向右挪动的,他发现了 9>6,停下来。此时再次进行交换,交换之后的序列如下
第二次交换结束。哨兵 j 继续向左挪动,他发现了 3<6,又停下来。哨兵 i 继续向右移动,此时哨兵 i 和哨兵 j 相遇了,哨兵 i 和哨兵 j 都走到 3 面前。说明此时“探测”结束。我们将基准数 6 和 3 进行交换。交换之后的序列如下。
到此第一轮“探测”真正结束。现在基准数 6 已经归为,此时以基准数 6 为分界点,6 左边的数都小于等于 6,6 右边的数都大于等于 6。
现在我们将第一轮“探测”结束后的序列,以 6 为分界点拆分成两个序列,左边的序列是“3 1 2 5 4”,右边的序列是“9 7 10 8”。接下来还需要分别处理这两个序列,因为 6 左边和右边的序列目前都还是混乱的。不过不要紧 ,我们已经掌握了方法,接下来只要模拟刚才的方法分别处理 6 左边和右边的序列即可。
实际上快速排序的每一轮处理其实就是将这一轮的基准数归为,直到所有的数都归为为止,排序就结束了。
实现代码:
1 |
|
2.2 归并排序
基本思想:
归并排序是由递归实现的,主要是分而治之的思想,也就是通过将问题分解成多个容易求解的局部性小问题来解开原本的问题的技巧。
另外,归并排序在合并两个已排序数组时,如果遇到了相同的元素,只要保证前半部分数组优先于后半部分数组, 相同元素的顺序就不会颠倒。所以归并排序属于稳定的排序算法。
每次分别排左半边和右半边,不断递归调用自己,直到只有一个元素递归结束,开始回溯,调用 merge 函数,合并两个有序序列,再合并的时候每次给末尾追上一个最大 int 这样就不怕最后一位的数字不会被排序。
排序过程:
代码实现:
1 |
|
2.3 基数排序:
基本思想:
基数排序(radix sort)属于“分配式排序”(distribution sort),又称“桶子法”(bucket sort)或 bin sort,顾名思义,它是透过键值的部份资讯,将要排序的元素分配至某些“桶”中,藉以达到排序的作用。参考桶排序。
基数排序算法不依靠直接比较元素排序。而是采用分配式排序,单独处理元素的每一位。从最高位向最低位处理称为:最高位优先(MSD)反之称为:最低位优先(LSD)。
下面以最低位优先为例:
(1)准备 10 个容器,编号 0-9,对应数字 0-9。 容器是有序的(按添加顺序)
(2)然后按待排序元素的某一位上的数字(比如:个位/十位/百位)将其存放到对应容器中(数字相同,如: 个位是数字 1 时, 就把这个元素放在 1 号桶),所有元素这样处理完后,再从 0 号容器开始依次到 9 号容器, 将其中的元素顺序取出放回原数组,然后再从下一位开始…(比如个位处理完后, 再处理十位/百位….最高位)
基数排序,可以称之为,进阶版的桶排序。
排序过程:
这个图片的来源
代码实现:
1 |
|
2.4 希尔排序
基本思想:
希尔排序,也称递减增量排序算法,是插入排序的一种更高效的改进版本。希尔排序是非稳定排序算法。同时也突破了之前内排序算法复杂度为 O(n2)的限制。
希尔排序是基于插入排序的以下两点性质而提出改进方法的:
插入排序在对几乎已经排好序的数据操作时,效率高,即可以达到线性排序的效率
插入排序一般来说是低效的,因为插入排序每次只能将数据移动一位
该方法的基本思想是:先将整个待排元素序列分割成若干个子序列(由相隔某个“增量”的元素组成的)分别进行直接插入排序,然后依次缩减增量再进行排序,待整个序列中的元素基本有序(增量足够小)时,再对全体元素进行一次直接插入排序。因为直接插入排序在元素基本有序的情况下(接近最好情况),效率是很高的,因此希尔排序在时间效率上比前两种方法有较大提高。
其中增量序列的选择是非常关键的,但通常我们取步长为 n/2(数组长度的一般)然后一直取半直到 1。
算法过程:
图片是之前看到的,如果有人知道出处,希望可以在评论区中指出,感谢。
实现代码:
1 |
|