七种经典排序算法python实现

匿名 (未验证) 提交于 2019-12-02 22:51:30

最近要考算法设计,所以把排序算法总结一下。经典的排序算法包括:冒泡排序,选择排序,插入排序,快速排序,归并排序,堆排序和希尔排序。全部程序都用python3实现,默认从小到大排序。
参考文章:https://blog.csdn.net/ls5718/article/details/51809866,博主的文章里面有演示动图,不懂的时候可以看下动图。

介绍:

让两数比较大的值一直滚动到最右侧,类似泡泡一直往上飘,每次滚动都要进行比较

思路:
临近的数字两两进行比较,按照从小到大的顺序进行交换,这样一趟过去,最大的数字就被交换到了最后一位,
然后再从头开始两两比较交换,直到导数第二位时结束

步骤:
1、比较相邻的元素,如果前一个比后一个大,就交换它们两个
2、对第0个到第n-1个数据做同样的工作,这时,最小的数就会‘浮’到数组的最左边位置
3、对所以的元素重复上面的步骤,除了单一个
4、持续每次对越来越少的元素重复上面的步骤,知道没有任何一个数字需要比较

时间复杂度:
O(n**2) 如果未优化的冒泡排序,最好的情况也是O(n**2),优化后的最好情况是O(n)

具体代码:

介绍:
保持最小元素在最左侧,用最左侧的元素依次和右边的元素比较,谁小谁放在左边。
思路:每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子序列前面,直到全部记录排序完毕
步骤:1、在未排序序列中找到最小元素,存放在排序序列的起始位置2、再从剩余未排序元素中继续寻找最小元素,然后放到已排序序列的末尾3、以此类推,直到所有元素均排序完毕。
时间复杂度:最坏情况:O(n**2),最好情况O(n**2)
具体代码:

介绍:
由于插入排序其内层循环非常紧凑,对于小规模输入,插入排序是一种非常快的排序算法

思路:
对每个未排序数据,在已排序序列中从后向前扫描,找到相应位置并插入。

步骤:
1、从第一个元素开始,该元素可以认为已经被排序
2、取出下一个元素,在已经排序的元素序列中从后向前扫描
3、如果被扫描的元素(已排序)大于新元素,将该元素后移一位
4、重复步骤3,直到找到已排序的元素小于或者等于新元素的位置
5、将新元素插入到该位置后面
6、重复步骤2~5

时间复杂度:
最坏的情况:数组反向排序 O(n**2);最好的情况:数组已经排序O(n),平均情况:O(n**2)
具体代码:

介绍:
快速排序通常比同为O(NlogN)的其他算法更快

思路:
快速排序采用的思想是分治思想。快速排序是找出一个元素(理论上可以随便找一个元素)作为基准(pivot),
然后对数组进行分区操作,是基准左边的元素的值都不大于基准值,基准右边的元素都不小于基准值,如此作为
基准的元素调整到排序后的正确位置。递归快速排序,将其他n-1个元素也调整到排序后的正确位置。最后每个
元素都是在排序后的正确的位置,排序完成。
所以快速排序算法的核心算法是分区操作,即如何调整基准的位置以及调整返回基准的最终位置以便分治递归。

步骤:
1、从数列中挑出一个元素作为基准数
2、分区过程,将比基准数大的放到右边,小于或等于它的数放在左边
3、再对左右分区递归执行第二步,直到各区间只有一个数

算法时间复杂度:O(nlogn)

排序演示:

具体代码:

介绍:

归并排序是采用分治法的一个非常典型的应用。

思路:
归并排序的思想就是先递归分解数组,再合并数组。

先考虑合并两个有序数组,基本思路是比较两个数组的最前面的数,谁小就先取谁,取了以后相应的指针
就往后移动一位。然后再比较,直至一个数组为空,最后再把另一个数组的剩余部分复制过来即可。

再考虑递归分解,基本思路是将数组分解成left和right,如果这两个数组内部数据是有序的,那么就可以
用上面合并数组的方法将这两个数组合并排序。如何让这两个数组内部有序呢?可以再二分,直至分解出的
小组只有一个元素时为止,此时认为该小组内部已经有序。然后合并排序相邻两个小组即可。

步骤:
1、将待排序数组R[0...n-1]二分分解,分解成N个长度为1的有序数组
2、将相邻的有序数组成对合并,得到N/2个长度为2的有序数组
3、将这些有序数组再次成对合并,得到N/4个长度为4的有序数组
4、重复上面的步骤,最后得到一个长度为N的有序数组。
综上可知,归并排序其实只要做两件事:(1)“分解”--将序列每次折半拆分。(2)“合并”--将划分
后的数组两两合并和排序

时间复杂度: O(NlogN)

具体代码:有两种,上面的是传统的做法,下面是利用python数组的特性

介绍:
堆排序在top K 问题中使用比较频繁。堆排序是采用二叉堆的数据结构来实现的,虽然实质上还是
一维数组。二叉堆是一个近似完全二叉树。

二叉堆具有以下性质:
1、父节点的键值总是大于或等于任何一个子节点的键值
2、每个节点的左右子树都是一个二叉堆(都是最大堆或最小堆)

根结点(亦称为堆顶)的关键字是堆里所有结点关键字中最大者,称为大根堆,又称最大堆(大顶堆)。
大根堆要求根节点的关键字既大于或等于左子树的关键字值,又大于或等于右子树的关键字值。

步骤:
1、构造最大堆(Build_Max_Heap):若数组下标范围为0~N,考虑到单独一个元素是大根堆,则
从下标N/2开始的元素均为大根堆。于是只要从N/2-1开始,向前一次构造大根堆,这样就能保证,
构造到某个节点时,它的左右子树都已经是大根堆。
2、堆排序(HeapSort):由于堆是用数组模拟的。得到一个大根堆后,数组内部并不是有序的。因此需要将
堆化数组有序化。思想是移除根节点,并做最大堆调整的递归运算。第一次将heap[0]与heap[n-1]交换,
再对heap[0...n-2]做最大堆调整。第二次将heap[0]与heap[n-2]交换,再对heap[n-3]做最大堆调整。
重复该操作直至heap[0]和heap[1]交换。由于每次都是将最大的数并入到后面的有序区间,故操作完后整个
数组就是有序的了。
3、最大堆调整(Max_Heapify):该方法是提供给上述两个过程调用的。目的是将堆的末端子节点做调整,
使得子节点永远小于父节点。

时间复杂度:O(NlogN)
具体代码:

介绍:
希尔排序,也称递减增量排序算法,实质是分组插入排序。由 Donald Shell 于1959年提出。
希尔排序是非稳定排序算法。虽然思想可以理解,不过编码过程很绕,以后需要的时候再回来多看看。

思路:
将数组列在一个表中并对列分别进行插入排序,重复这过程,不过每次用更长的列(步长更长了,列数更少了)
来进行。最后整个表就只有一列了。将数组转换至表是为了更好的理解这算法,算法本身还是使用数组进行排序
假如,假设有这样一组数[ 13 ,14, 94, 33, 82, 25, 59, 94, 65, 23, 45, 27, 73, 25, 39, 10 ]
如果我们以步长5开始进行排序,我们可以通过将这列表放在有5列的表中来更好的描述算法,
这样它们看起来应该是这样的:
13 14 94 33 82
25 59 94 65 23
45 27 73 25 39
10
然后我们对每列进行排序
10 14 73 25 23
13 27 94 33 39
25 59 94 65 82
45
将上述四行数字,依序接在一起时我们得到:[ 10 14 73 25 23 13 27 94 33 39 25 59 94 65 82 45 ]。
这是10 已经移动到正确的位置了,然后再以步长3进行排序
10 14 73
25 23 13
27 94 33
39 25 59
94 65 82
45
排序之后是
10 14 13
25 23 33
27 25 59
39 65 73
45 94 82
94
最后以步长1进行排序(此时就是简单的插入排序了)。

时间复杂度:O(n**2) 使用Hibbard增量的最坏情况:O(n^3/2)
具体代码:

总结

排序算法稳定性表示两个值相同的元素在排序前后是否有位置变化。如果前后位置变化,则排序算法是不稳定的,否则是稳定的。稳定性的定义符合常理,两个值相同的元素无需再次交换位置,交换位置是做了一次无用功。

下面是七种经典排序算法指标对比情况:

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!