4、快速排序
- 从数列中挑出一个元素,称为基准;
- 重新排列数列,所有元素比基准小的摆放在基准前面,所有元素比基准大的摆在基准后面;
- 在这个分区结束之后,该基准就位于数列的中间位置;
- 递归地对基准左右两边的数列进行排序。
快速排序代码——第一步
def quick_sort(data, left, right):
if left < right:
mid = partition(data, left, right)
quick_sort(data, left, mid - 1)
quick_sort(data, mid + 1, right)
快速排序代码——第二步
def partition(data, left, right):
left =0;right = len(data)-1
tmp = data[left]
while left < right:
while left < right and data[right] >= tmp:
right -= 1
data[left] = data[right]
while left < right and data[left] <= tmp:
left += 1
data[right] = data[left]
data[left] = tmp ###很显然,当left ==right时,循环会终止,即实现了左边的数都比tmp小,右边的数都比tmp大
return left ##left==right,所以返回left和right是一样的结果
5、归并排序(Merge Sort)
分治法:很多有用的算法结构上是递归的,为了解决一个特定问题,算法一次或者多次递归调用其自身以解决若干子问题。 这些算法典型地遵循分治法的思想:将原问题分解为几个规模较小但是类似于原问题的子问题,递归求解这些子问题, 然后再合并这些问题的解来建立原问题的解。
现在我们就来看下归并排序是是如何利用分治法解决问题的。
- 分解:将待排序的 n 个元素分成各包含 n/2 个元素的子序列
- 解决:使用归并排序递归排序两个子序列
- 合并:合并两个已经排序的子序列以产生已排序的答案
当数组被完全分隔成只有单个元素的数组时,我们需要把它们合并回去,每次两两合并成一个有序的序列。
一次归并代码
def merge(li, low, mid, high):
i = low #这里的low是左半段的第一个元素的索引
j = mid + 1 #这里设置的mid是左半段的最后一个元素的索引,因此j就是右半段的第一个元素的索引
ltmp = [] #新增一个列表来存储
while i <= mid and j <= high:
if li[i] <= li[j]:
ltmp.append(li[i])
i += 1
else:
ltmp.append(li[j])
j += 1
while i <= mid:
ltmp.append(li[i])
i += 1
while j <= high:
ltmp.append(li[j])
j += 1
li[low:high + 1] = ltmp
递归的调用归并排序
def merge_sort(li, low, high):
if low < high:
mid = (low + high) // 2
mergesort(li, low, mid)
mergesort(li, mid + 1, high)
merge(li, low, mid, high)
算法分析:
时间复杂度:总的代价是 cnlg(n)+cn ,忽略常数项可以认为是 O(nlg(n))
空间复杂度:o(n)
6、堆排序
为什么我要将堆排序放在最后呢,相对来说,堆排序是高级排序算法中相对不好理解的一中排序算法,我个人的看法而已。
这涉及了二叉树的理解,以及堆的理解,利用堆数据结构所设计的一种排序算法,通过每次弹出堆顶元素实现排序。
动画演示:
堆排序的过程:
- 建立堆
- 得到堆顶元素
- 去掉堆顶,将堆的最后一个元素放到堆顶,此时可通过一次调整重新使堆有序
- 堆顶元素为第二大元素
- 重复步骤3,直到堆变空
## 调整的过程
def sift(data, low, high):
i = low #当前父节点
j = 2 * i + 1 #该父节点的左子节点
tmp = data[i]
while j <= high: #左节点在堆里
if j < high and data[j] < data[j + 1]: ## 有右节点,且大于左子节点
j += 1 ##由于是先判断了的,就是为了找出左右子节点中的较大者
if tmp < data[j]: ##判断父节点是否小于子节点中的较大者
data[i] = data[j] ##小于的话,就相互交换
i = j ##交换之后,交换的子节点成为父节点,此时原子节点处是空着的,让交换后的父节点成为新的子节点,在执行循环
j = 2 * i + 1 ##到这里,在判断其左子节点是否在堆里,在就继续执行循环,不在就退出
else:
break
data[i] = tmp ##循环结束后,将最开始的父节点交换到最后循环结束时的那个父节点上
def heap_sort(data):
n = len(data)
for i in range(n // 2 - 1, -1, -1): ##这里的n//2是最后一个元素的父节点,从后往前的建堆
sift(data, i, n - 1) ### 循环结束时,一个堆就建好了
for i in range(n - 1, -1, -1): ##是为了出数,也可以新开一个列表,来存储堆顶元素,出一个元素后,堆的个数同时减一
data[0], data[i] = data[i], data[0]
sift(data, 0, i - 1)
本来还想记录下希尔排序的,不过用的机会不多,这里也不做介绍了。我觉得掌握前面的几种排序算法,就已经够了。
堆排序、快速排序、归并排序小结:
1)三种排序算法的时间复杂度都是o(nlgn)
2)一般情况下,就运行时间而言:
快速<归并<堆
3)三种排序的缺点:
- 快速排序:极端情况下,排序效率低
- 归并排序:需要额外的内存开销
- 堆排序:在快的排序算法中相对较慢
这里截张图作为总结,注这里的稳定性主要是说比如我们遇到相同的数时,稳定性好的,能保持先前的状态
参考资料 :
https://python-data-structures-and-algorithms.readthedocs.io/zh/latest/13_高级排序算法/merge_sort/
https://www.cnblogs.com/onepixel/articles/7674659.html