简介
三种常见的快速的排序方法介绍。
快速排序
取一个元素p(可以直接指定为第一个元素),使元素p归位置,左边的数都比p小,右边的数都比p大。通过递归完成排序。(最常用的排序,效率高)
时间复杂度:O(nlogn)
快排问题:
最坏情况:O(n2) 例如(9,8,7,6,5,4,3,2,1) 加入随机化初始p(随机数选取index,和最左边的数交换位置,人为重组一下数组,尽量避免最坏情况)
递归最大深度问题(默认为9999)
import sys
sys.setrecursionlimit(1000000)
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):
p = data[left]
while left < right:
while left < right and data[right] >= p: # 找小于p的值
right -= 1 # 往左走一步
data[left] = data[right] # 把右边的值放到左边位置上
print(data)
while left < right and data[left] <= p: # 找大于p的值
left += 1 # 往右走一步
data[right] = data[left] # 把左边的值放到右边位置上
print(data)
data[left] = p # 把p归位
print(data)
return left
堆排序
堆是一种特殊的完全二叉树结构。
大根堆:一个完全二叉树,满足任一节点都比其子节点都大。
小根堆:一个完全二叉树,满足任一节点都比其子节点都小。
堆的向下调整性质:假设根节点的左右子树都是堆,但自身不是堆,可以通过一次向下的调整来将其编程一个堆。
堆排序过程(大根堆)
(1) 堆建立。(从最后一个子树开始调整)
(2) 堆顶是最大元素。
(3) 去掉堆顶,将堆最后一个元素放到堆顶,此时通过一侧调整重新使堆有序。
(4) 堆顶元素为第二大元素。
(5) 重复步骤3,直到堆变空。
时间复杂度:O(nlogn), 实际表现略慢于堆排序。
python 内置堆排序模块(小根堆)
def sift(li, low, high):
# 堆最后一个元素位置high, low堆顶位置
i = low # 根节点位置
j = 2 * i + 1 # 左节点
temp = li[i] # 堆顶存起来
while j <= high: # 只要j位置有节点就一直循环
if j+1 <= hight and li[j+1] > li[j]: # 如果右节点存在并且比左节点大
j = j + 1 # j指向右节点
if li[j] > temp:
li[i] = li[j]
i = j # i j 位置替换
j = 2 * i + 1 # j网下继续调整
else:
li[i] = temp # 把temp放到父节点的位置
break
else:
li[i] = temp # 把temp放到叶子节点
def heap_sort(li):
n = len(li)
for i in range((n-1-1)//2, -1, -1): # 从最后一个节点的父节点的位置网前遍历
# i 表示建堆的时候调整的部分的根的下标
sift(li, i, n-1)
# 建堆完成
for i in range(n-1, -1, -1):
# i 指当前堆的最后一个元素的位置
li[0], li[i] = li[i], li[0]
sift(li, 0, i-1) # i-1是新的high
# python内置模块使用
import heapq
import random
li = list(range(100))
random.shuffle(li)
print(li)
heapq.heapify(li) # 建小根堆
print(li)
n = len(li)
new_li = list()
for i in range(n):
value = heapq.heappop(li) # 弹出最小的元素
new_li.append(value)
print(li)
归并排序
归并: 假设一个列表两段分别有序,将其合成一个有序列表的操作。
归并排序过程:
(1) 分解: 将列表越分越小,直至分成一个元素。
(2) 终止条件: 一个元素是有序的。
(3) 合并: 将两个有序列表归并,列表越来越大。
关键点:递归
时间复杂度:O(nlogn) 空间复杂度: O(n)
# 归并
def merge(li, low, mid, high):
i = low
j = mid + 1
temp_list = list()
while i <= mid and j <= high: # 只要左右两边都有数就循环
if li[i] < li[j]:
temp_list.append(li[i])
i += 1
else:
temp_list.append(li[j])
j += 1
# while执行完,肯定有一部分没有数了
while i <= mid: # 如果左边还有数
temp_list.append(li[i])
i += 1
while j <= high: # 如果右边还有数
temp_list.append(li[j])
j += 1
li[low:high+1] = temp_list
def merge_sort(li, low, high):
if low < high: # 列表至少有两个元素
mid = (low + high) // 2
merge_sort(li, low, mid) # 左边部分排序
merge_sort(li, mid+1, high) # 右边部分排序
merge(li, low, mid, high)
总结
- 三种排序时间复杂度都是O(nlogn)
- 一般情况下,就运行时间而言: 快排 < 归并 < 堆
- 缺点:(1)快排:极端情况下 效率低。(2)归并:需要二外的内存开销。 (3)堆排序: 在三种排序中速度相对慢一些。