重点:
- 有序区
 - 无序区
 
冒泡排序(BUB)
列表每两个相邻的数, 如果前边的比后边的大, 那么交换这两个数
冒泡排序算法的流程如下:
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个。
 - 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数。
 - 针对所有的元素重复以上的步骤,除了最后一个。
 - 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较。
 

关键点: 趟, 无序区
code
1  | # O(n²) 时间复杂度  | 
优化版
1  | def bubble_sort(li):  | 
| 空间时间复杂度 | O(1) | 
|---|---|
| 最坏时间复杂度 | O(n²) | 
| 最优时间复杂度 | O(n) | 
| 平均时间复杂度 | O(n²) | 
选择排序(SEL)
每一次从待排序的数据元素中选出最小(或最大)的一个元素,存放在序列的起始位置,直到全部待排序的数据元素排完。
1  | def select_sort(li):  | 
| 空间时间复杂度 | O(1) | 
|---|---|
| 最坏时间复杂度 | O(n²) | 
| 最优时间复杂度 | O(n²) | 
| 平均时间复杂度 | O(n²) | 
插入排序(INS)
插入排序每次取出数组后半部分的第一个元素,在排好序的前半部分中,为其找到最合适的位置并进行插入(扑克牌)
- 列表被分为有序区和无序区两个部分。最初有序区只有一个元素。
 - 每次从无序区选择一个元素,插入到有序区的位置,直到无序区变空。
 
插入排序算法的流程如下:
- 从第一个元素开始,该元素可以认为已经被排序
 - 取出下一个元素,在已经排序的元素序列中从后向前扫描
 - 如果该元素(已排序)大于新元素,将该元素移到下一位置
 - 重复步骤 3,直到找到已排序的元素小于或者等于新元素的位置
 - 将新元素插入到该位置后
 - 重复步骤 2~5
 
关键点:
- 摸到的牌
 - 手里的牌 (有序)
 
code
1  | def insert_sort(li):  | 
| 空间时间复杂度 | O(1) | 
|---|---|
| 最坏时间复杂度 | O(n²) | 
| 最优时间复杂度 | O(n²) | 
| 平均时间复杂度 | O(n²) | 
快速排序(QUI)
博主看动图不是很理解, 建议看 这里

快速排序算法的流程如下:
- 取一个元素p(第一个元素),使元素p归位;
 - 列表被p分成两部分,左边都比p小,右边都比p大;
 - 递归完成排序。
 
关键点:
- 整理(让元素归位)
 - 递归
 
1  | def partition(data, left, right):  | 
优化版
1  | # 来自知乎 @风满楼  | 
1  | # 快排精简版  | 
问题
某些极端的情况下复杂度非常高, 如:
1  | 9 8 7 6 5 4 3 2 1  | 
出现的概率不多, 属于极端情况, 解决方法: 选基准的时候随机选一个数与第一个数交换。
| 空间时间复杂度 | 根据实现的方式不同而不同 | 
|---|---|
| 最坏时间复杂度 | O(n²) | 
| 最优时间复杂度 | O(nlogn) | 
| 平均时间复杂度 | O(nlogn) | 
PS: 看到一个最狠的快排
1  | # https://github.com/qiwsir/algorithm/blob/master/quick_sort.md  | 
参考资料
- Ele - A面
 - http://bbs.ahalei.com/thread-4419-1-1.html
 - http://blog.csdn.net/v_july_v/article/details/6116297
 - https://www.zhihu.com/question/26786398
 - https://hellolynn.hpd.io/2017/08/03/%E5%BF%AB%E9%80%9F%E6%8E%92%E5%BA%8F-quick-sort/
 - https://github.com/qiwsir/algorithm/blob/master/quick_sort.md
 
堆排序(HEAP)
堆排序用的是树的结构
堆
- 大根堆:一棵完全二叉树,满足任一节点都比其孩子节点大
 - 小根堆:一棵完全二叉树,满足任一节点都比其孩子节点小
 

假设:节点的左右子树都是堆,但自身不是堆
当根节点的左右子树都是堆时,可以通过一次向下的调整来将其变换成一个堆。

堆排序过程:
- 建立堆
 - 得到堆顶元素,为最大元素
 - 去掉堆顶,将堆最后一个元素放到堆顶,此时可通过一次调整重新使堆有序。
 - 堆顶元素为第二大元素。
 - 重复步骤3,直到堆变空。
 
- 构建堆
 
先从最小的子树开始看, 最后一步看整个的堆; 从最后一个非叶子节点为根的子树开始做调整
- 挨个出数
 

code
1  | def sift(data, low, high):  | 
| 空间时间复杂度 | O(n),O(1) | 
|---|---|
| 最坏时间复杂度 | O(nlogn) | 
| 最优时间复杂度 | O(nlogn) | 
| 平均时间复杂度 | O(nlogn) | 
引用
- 数据结构:树
 - https://www.cnblogs.com/chengxiao/p/6129630.html
 - http://bubkoo.com/2014/01/14/sort-algorithm/heap-sort/
 - http://wuchong.me/blog/2014/02/09/algorithm-sort-summary/
 
归并排序(MER)


归并排序思路:
- 分解:将列表越分越小,直至分成一个元素。
 - 一个元素是有序的。
 - 合并:将两个有序列表归并,列表越来越大。
 

- 递归地将数组划分为两部分
 - 直到两个子数组元素都为1时,返回并将两个数组进行排序融合
 - 逐步返回,并递归融合,最终使得数组有序
 
code
1  | def merge(data, low, mid, high):  | 
加深理解
1  | def func(x):  | 
或者结合递归

| 空间时间复杂度 | O(n) | 
|---|---|
| 最坏时间复杂度 | O(nlogn) | 
| 最优时间复杂度 | O(n) | 
| 平均时间复杂度 | O(nlogn) | 
- 快速排序、堆排序、归并排序 - 小结
 
三种排序算法的时间复杂度都是O(nlogn)
- 运行时间:
 
快速排序 < 归并排序 < 堆排序
三种排序算法的缺点:
| 快速排序 | 极端情况下排序效率低 | 
|---|---|
| 归并排序 | 需要额外的内存开销 | 
| 堆排序 | 在快的排序算法中相对较慢 | 
计数排序(COU)

题: 现在有一个列表,列表中的数范围都在 0 到 100 之间,列表长度大约为 100 万。设计算法在 O(n) 时间复杂度内将列表进行排序。
1  | def count_sort(data, maxnum = 100):  | 
因为要开额外的内存空间,所以使用并不多。计数排序限定元素不会太大的时候,如:年龄可以使用计数排序
希尔排序(SHE)
希尔排序是一种分组插入排序算法。O(1.3n)

- 以数组元素长度的一半做为初始步长gap,将数组划分为gap个子数组
 - 循环切换遍历子数组,在子数组内分别进行插入排序
 - 将gap更新为gap/2,重复上述步骤1,2,直到gap为1
 

希尔排序思路:
- 先取一个正整数 d1(d1 < n),把全部记录分成 d1 个组,所有距离为 d1 的倍数的记录看成一组,然后在各组内进行插入排序
 - 然后取 d2(d2 < d1)
 - 重复上述分组和排序操作;直到取 di = 1(i >= 1) 位置,即所有记录成为一个组,最后对这个组进行插入排序。一般选 d1 约为 n/2,d2 为 d1 /2, d3 为 d2/2 ,…, di = 1。
 
希尔排序每趟并不使某些元素有序,而是使整体数据越来越接近有序;最后一趟排序使得所有数据有序。
1  | # 修改插入排序  | 
优化版
1  | def shell_sort(data):  | 
后记
排序算法指标

排序的稳定性
排序关键字相同的情况下,对象的相对位置不变
计时装饰器
1  | def cal_time(func):  |