将这两种排序一同讲解是因为这两个算法的实现过程中都使用了 递归。
快速排序(必须掌握)
算法说明
快速排序,也叫划分交换排序。通过一趟排序将要排序的数据分割成独立的两部分,其中一部分的数据比另一个部分的都要小。之后再针对这两个部分的数据进行快速排序,同样可以分别将这两个部分的数据分成两部分。
整个过程递归进行,最终使得整个数据达到有序的状态。递归的终止条件是,数列的大小是0或1,代表着数列已被排序好。没有这个条件的话,递归会无限执行下去。
以下面的序列作为演示,
54 26 93 17 77 31 44 55 20
^ ^
| |
low high
# 寻找一个位置,使得54左边的元素都比他小,54右边的都比他大
54 26 93 17 77 31 44 55 20
^ ^
| |
low high
# low指到93的位置不动了,因为93不满足小于54;high指到20不动,因为20不满足大于54.
# 于是将这两个值交换,则指针可以继续移动
--------------------------------------------------------------------
# 交换后得到
54 26 20 17 77 31 44 55 93
^ ^
| |
low high
# 继续移动指针
54 26 20 17 77 31 44 55 93
^ ^
| |
low high
# low指到77停止,high指到44停止,因为这两个数不满足小于和大于54,对他们进行交换
--------------------------------------------------------------------
# 交换后得到
54 26 20 17 44 31 77 55 93
^ ^
| |
low high
#继续移动指针
54 26 20 17 44 31 77 55 93
^
|
low/high
# 在31的位置上两个指针相遇,此时停止遍历,此处即是54在序列中最终的排序位置
# 将54移动至此
--------------------------------------------------------------------
# 由此将序列分成了两部分,对这两部分分别进行上方的操作
54
26 20 17 44 31 77 55 93
算法实现
序列的升序排列,
def quick_sort(alist, first, last):
if first >= last:
return
low = first
high = last
# 把序列第一个元素取出
mid_value = alist[first]
while low < high:
# high指针左移
while alist[high] >= mid_value and low < high:
high -= 1
alist[low], alist[high] = alist[high], alist[low]
# low指针右移
while alist[low] < mid_value and low < high:
low += 1
alist[low], alist[high] = alist[high], alist[low]
# 退出循环时, low == high,将原本序列的第一个元素放到low的位置
alist[low] = mid_value
# 对low左边的序列进行快速排序
quick_sort(alist, first, low-1)
# 对low右边的序列进行快速排序
quick_sort(alist, low+1, last)
时间复杂度与稳定性
复杂度 | 说明 |
---|---|
最优时间复杂度 | O(nlogn)对应的是原本序列已有序的情况下将列表遍历一遍的时间为O(n),每次遍历后都对序列进行二分,时间复杂度为O(logn),故最终得到最优时间复杂度 O(nlogn)。 |
最坏时间复杂度 | O(n2) |
快速排序不具备排序稳定性,因为存在high指针和low指针元素交换位置的情况,此时不能保证交换后键值相同的元素保留原始顺序。
归并排序
算法说明
归并排序的思想就是先递归分组,之后再合并数组。
具体流程如下,
- 将数组分解最小(每个子序列仅有一个元素)
- 然后合并两个有序数组,基本思路是比较两个数组的最前面的数,哪个小就先取出。取出后相应的指针往后移动一位。然后再进行比较,直至一个数组为空。最后把另一个数组的剩余部分复制过来即可。
以下面的例子进行演示,
54, 26, 93, 17, 77, 31, 44, 55
# 第一次拆分
54, 26, 93, 17 77, 31, 44, 55
# 第二次拆分
54, 26 93, 17 77, 31 44, 55
# 第三次拆分
54 26 93 17 77 31 44 55
# 第一次合并
26, 54 17, 93 31, 77 44, 55
# 第二次合并
17, 26, 54, 93 31, 44, 55, 77
# 第三次合并
17, 26, 31, 44, 54, 55, 77, 93
算法实现
实现升序排列,
def merge_sort(alist):
n = len(alist)
if n <= 1:
return alist
mid = n // 2
# 左边使用归并排序后得到的新的序列
ledt_li = merge_sort(alist[: mid])
# 右侧使用归并排序后得到的新的序列
right_li = merge_sort(alist[mid:])
# 将左右两个序列合并为一个整体,首先创建指针,指向两个子序列开头的元素
left_pointer, right_pointer = 0, 0
result = []
while left_pointer < len(left_li) and right_pointer < len(right_li):
if left_li[left_pointer] <= right_li[right_pointer]: # 可使其具有排序稳定性
result.append(left_li[left_pointer])
left_pointer += 1
else:
result.append(right_li[right_pointer])
right_pointer += 1
# 退出循环时,说明两个子序列其中一个的元素已被全部添加至result中
result.extend(left_li[left_pointer: ])
result.extenf(right_li[right_pointer: ])
return result
需要注意的是,归并排序每次返回的都是一个新的序列对象。
时间复杂度与稳定性
归并排序最优时间复杂度和最坏时间复杂度都是 O(nlogn),因为都要进行二分和对子序列遍历排序。
归并排序在设置合理的情况下具备排序稳定性。