前言
原理就不在这里说了,好多大神肯定比我这个初学者讲的好很多,推荐去B站看视频讲解,跟着手敲代码
为什么选这三个排序呢?
- 首先快排是必须掌握的
- 看看快排在最坏的情况下(O(n²)),且不使用辅助空间,与冒泡(O(n²))的比较
- 由于快排是不稳定的排序算法,且平均时间复杂度为O(nlogn),那找一个时间复杂度为O(nlogn)且稳定排序算法,那么就非归并排序莫属了.
一、快速排序,归并排序,冒泡排序比较
算法 | 时间复杂度 | 空间复杂度 | 稳定性 | ||
平均 | 最好 | 最坏 | |||
冒泡排序 | O(n²) | O(n) | O(n²) | O(1) | 稳定 |
快速排序 | O(nlogn) | O(nlogn) | O(n²) | O(1)或O(n) | 不稳定 |
归并排序 | O(nlogn) | O(nlogn) | O(nlogn) | O(n) | 稳定 |
二、源码
import random
from copy import deepcopy
arr0 = [random.randint(1, 100) for _ in range(1000)]
# arr0 = [_ for _ in range(1000, 0, -1)]
# print(arr0)
for i in range(1, 11):
exec(f'arr{i} = deepcopy(arr0)')
1.冒泡
def bubble_sort(arr):
for i in range(len(arr) - 1):
for j in range(len(arr) - 1 - i):
if arr[j] >= arr[j + 1]:
arr[j], arr[j + 1] = arr[j + 1], arr[j]
return arr
2.归并
def merge_sort(arr):
length = len(arr)
if length <= 1:
return arr
mid = length // 2
# 以下标mid为分界点,将arr的[:mid]给left,[mid:]给right
left = merge_sort(arr[:mid])
right = merge_sort(arr[mid:])
lp = 0
rp = 0
res = []
# 切记这里不是<=,因为数组的index一定是小于数组长度的
while lp < len(left) and rp < len(right):
if left[lp] <= right[rp]:
res.append(left[lp])
lp += 1
else:
res.append(right[rp])
rp += 1
# 这里要用extend. left,right切片后的值为一个list
res.extend(left[lp:])
res.extend(right[rp:])
return res
3.快排
# 一句话快排
qs = lambda arr: arr if len(arr) <= 1 else qs([it for it in arr[1:] if it <= arr[0]]) + [arr[0]] + \
qs([it for it in arr[1:] if it > arr[0]])
# 空间复杂度O(1)的快排
def quick_sort_O1(arr, left, right):
if left >= right:
return
base = arr[left]
lp = left
rp = right
while lp < rp:
while lp < rp and arr[rp] >= base:
rp -= 1
arr[lp] = arr[rp]
while lp < rp and arr[lp] < base:
lp += 1
arr[rp] = arr[lp]
arr[lp] = base
quick_sort_O1(arr, left, lp - 1)
quick_sort_O1(arr, lp + 1, right)
return arr
# 空间复杂度O(n)的快排
def quick_sort_On(arr: list):
if len(arr) <= 1:
return arr
left = []
right = []
base = arr.pop(0)
for it in arr:
if it <= base:
left.append(it)
else:
right.append(it)
return quick_sort_On(left) + [base] + quick_sort_On(right)
# 空间复杂度O(n)的快排,引入随机处理,尝试规避快排的最坏风险
def quick_sort_On_random(arr: list):
if len(arr) <= 1:
return arr
left = []
right = []
base = arr.pop()
while arr:
tmp = arr.pop()
if tmp <= base:
left.append(tmp)
else:
right.append(tmp)
return quick_sort_On(left) + [base] + quick_sort_On(right)
三、创建长度为1000的list,在jupyter notebook上运行,观察结果
1.随机无序数组结果
- 空间换时间的做法明显让快排效率提高了一个数量级~
2.反序数组结果
将arr0重新赋值如下:
arr0 = [_ for _ in range(1000, 0, -1)]
3.正序数组结果
将arr0重新赋值如下:
arr0 = [_ for _ in range(1000)]
4.内置函数--遥遥领先
**内置函数那么快,学啥快排(捂脸)...
随机无序数组
反序数组
正序结果
总结
先不总结了,大致情况就如上吧.希望大家看完后给些意见和建议.
不知道有什么地方没考虑进去,本来只是为了规避快排最坏情况的风险而实现的quick_sort_On_random,意外发现每次都是最快的???
2020-12-16:
查找了相关资料,找到了quick_sort_On_random速度最快的原因,在这里记录一下
1.从列表的末尾(右端)弹出在CPython中需要恒定的时间,但是从左端弹出(.pop(0))需要的时间与列表的长度成正比
2.所有the_list [1:]中的元素在物理上向左移动一个位置.
3.如果需要经常删除索引位置0,那么使用collections.deque的实例要好得多. Deques支持从两端有效推送和弹出.