十大排序算法练习
使用Python实现常用的十大排序算法:
1.插入排序insert sort
2.希尔排序shell sort
3.简单选择排序select sort
4.冒泡排序bubble sort
5.快速排序quick sort
6.堆排序HeapSort
7.归并排序mergesort
8.计数排序countingSort
9.桶排序bucketSort
10.基数排序radixSort
参考实现:
博客中代码对应的仓库—sort_algorithm.ipynb,以及用于排序程序测试的代码—sort.py.
1.插入排序
将后续元素一个一个插入到前面已经排好序的有序序列中。
算法复杂度:
1 | def insertSort(arr): |
insertSort [-1, 0, 0, 3, 9, 10]
2.希尔排序shell sort
是简单插入排序的高效改进版,选择增量step序列,根据增量先从宏观上对数组进行排序
算法复杂度:
1 | def shellSort(arr): |
shellSort: [-1, 0, 0, 3, 9, 10]
3.简单选择排序
遍历数组,选择最小或最大值从头开始交换
算法复杂度:
1 | def selectSort(arr): |
selectSort: [-1, 0, 0, 3, 9, 10]
4.冒泡排序
算法复杂度:
1 | def bubbSort(arr): |
bubbSort: [1, 2, 2, 2, 3, 4, 4, 5, 7, 9, 10, 12]
5.快速排序quick sort
分治法思想,时间复杂度为:
1 | def quickSort(arr, left, right): |
[-2, 0, 6, 9, 10]
1 | # # 更加简洁清晰的实现,这种实现对含有重复出现元素的数组排序将出错 |
[1, 2, 2, 2, 3, 4, 4, 5, 7, 9, 10, 12]
6.堆排序HeapSort
堆是具有以下性质的完全二叉树:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆;或者每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。
基本思路:
a.将无需序列构建成一个堆,根据升序降序需求选择大顶堆或小顶堆;
b.将堆顶元素与末尾元素交换,将最大元素”沉”到数组末端;(升序方法)
c.重新调整结构,使其满足堆定义,然后继续交换堆顶元素与当前末尾元素,反复执行调整+交换步骤,直到整个序列有序。
算法复杂度为:
堆的定义就是:
大顶堆:arr[i] >= arr[2i+1] && arr[i] >= arr[2i+2]
小顶堆:arr[i] <= arr[2i+1] && arr[i] <= arr[2i+2]
堆的调整:
最后一个非叶子结点为 arr.length/2-1,从最后一个非叶子节点开始,从右至左,从下至上进行调整
使用双向队列进行优化:https://www.jianshu.com/p/d174f1862601
1 | class HeapSort: |
[1, 2, 2, 2, 3, 4, 4, 5, 7, 9, 10, 12]
7.归并排序mergesort
该算法是采用分治法(Divide and Conquer)的一个非常典型的应用。
分治法:
- 分: 将问题划分为很多小问题,拆分子序列,递归深度为log2n
- 治: 将每个小问题的解“修补”在一起
作为一种典型的分而治之思想的算法应用,归并排序的实现由两种方法:
- 自上而下的递归(所有递归的方法都可以用迭代重写,所以就有了第 2 种方法);
- 自下而上的迭代;
需要额外的空间消耗,时间复杂度:
算法步骤:
1) 申请空间,使其大小为两个已经排序序列之和,该空间用来存放合并后的序列;
2) 设定两个指针,最初位置分别为两个已经排序序列的起始位置;
3) 比较两个指针所指向的元素,选择相对小的元素放入到合并空间,并移动指针到下一位置;
4) 重复步骤 3 直到某一指针达到序列尾;
5) 将另一序列剩下的所有元素直接复制到合并序列尾。
很好的图解算法过程参考:https://www.cnblogs.com/chengxiao/p/6194356.html
1 | ######################################### |
mergeSort: [-2, -1, 0, 0, 0, 2, 3, 7, 9, 10, 32, 40]
8.计数排序countingSort
计数排序要求输入的数据必须是有确定范围的整数。
放输入数据是n个0到k之间的整数时,时间复杂度为:
算法的步骤如下:
1)找出待排序的数组中最大和最小的元素
2)统计数组中每个值为i的元素出现的次数,存入数组C的第i项
3)对所有的计数累加(从C中的第一个元素开始,每一项和前一项相加)
4)反向填充目标数组:将每个元素i放在新数组的第C(i)项,每放一个元素就将C(i)减去1
第3) 4)步的目的是使得到的排序是稳定排序,即相等的元素相对位置不变。
1 | def countingSort(arr): |
countingSort: [-2, -1, 0, 0, 0, 2, 3, 7, 9, 10, 32, 40]
9.桶排序bucketSort
桶排序是计数排序的升级版。它利用了函数的映射关系,高效与否的关键就在于这个映射函数的确定。为了使桶排序更加高效,我们需要做到这两点:
1) 在额外空间充足的情况下,尽量增大桶的数量
2) 使用的映射函数能够将输入的 N 个数据均匀的分配到 K 个桶中
同时,对于桶中元素的排序,选择何种比较排序算法对于性能的影响至关重要。
最快的情况:输入的数据可以均匀的分配到每一个桶中。
最慢的情况:输入的数据被分配到了同一个桶中。
1 | # 和计数排序排序很像,下面是一个pythonic实现 |
[8, 5, 5, 3, 2]
[0.12, 0.2, 0.3, 0.46, 0.5]
10.基数排序radixSort
典型整数排序算法,基数排序有两种方法:
这三种排序算法都利用了桶的概念,但对桶的使用方法上有明显差异:
- 基数排序:根据键值的每位数字来分配桶;
- 计数排序:每个桶只存储单一键值;
- 桶排序:每个桶存储一定范围的数值;
基数排序重要的两个过程:取每个数的每一位,然后按顺序放回。重复上述过程,直到所有数的各个位数均被取过。
1 | def radixSort(s): |
[[], [1], [], [23, 3453], [334, 4, 23424], [5, 345, 345345, 45], [], [67, 7], [78], [99]]
[[1, 4, 5, 7], [], [23, 23424], [334], [345, 345345, 45], [3453], [67], [78], [], [99]]
[[1, 4, 5, 7, 23, 45, 67, 78, 99], [], [], [334, 345, 345345], [23424, 3453], [], [], [], [], []]
[[1, 4, 5, 7, 23, 45, 67, 78, 99, 334, 345], [], [], [23424, 3453], [], [345345], [], [], [], []]
[[1, 4, 5, 7, 23, 45, 67, 78, 99, 334, 345, 3453], [], [23424], [], [345345], [], [], [], [], []]
[[1, 4, 5, 7, 23, 45, 67, 78, 99, 334, 345, 3453, 23424], [], [], [345345], [], [], [], [], [], []]
[1, 4, 5, 7, 23, 45, 67, 78, 99, 334, 345, 3453, 23424, 345345]
leetcode
最大乘积子串
1 | # 暴力解法---超时警告:( |
6
0
动态规划法:1
2
3
4max = max(curr*last-max,curr*last-min,curr)
min = min(curr*last-max,curr*last-min,curr)
dp(i) = max(dp(i-1)[0]*arr[i],dp(i-1)[1]*arr[i],arr[i]))
1 | # 动态规划法 |
20
0
0
1 | # 运行时间优于99% |