常见排序算法
时间复杂度与空间复杂度
递归
汉诺塔问题
def hanoi(n, a, b, c):
if n > 0:
hanoi(n-1, a, c, b)
print(f'moving from {a} to {c}')
hanoi(n-1, b, a, c)
顺序查找与二分查找
二分查找
def binary_search(li, val):
left = 0
right = len(li) - 1
while left <= right:
mid = (left + right) // 2
if li[mid] == val:
return mid
elif li[mid] > val:
right = mid - 1
else:
left = mid + 1
else:
return None
列表排序
即将无序列表调整为有序列表
常见排序算法
低级排序算法
- 冒泡排序
- 选择排序
- 插入排序
进阶排序算法
- 快速排序
- 堆排序
- 归并排序
其他排序算法
- 希尔排序
- 计数排序
- 基数排序
冒泡排序
实现思路
从列表首出发,判断相邻的两个数,较大者置后,直到列表尾或者有序区
每趟无序区数量减一,有序区数量加一,直至完全排序
代码
def bubble_sort(li):
for i in range(len(li)-1):
exchange = False
for j in range(len(li)-i-1):
if li[j] > li[j+1]:
li[j], li[j+1] = li[j+1], li[j]
exchange = True
if not exchange:
return
时间复杂度O(n²)
选择排序
实现思路
每一趟排序都将无序区最小数放在有序区末尾
代码
def select_sort(li):
for i in range(len(li) - 1):
min_loc = i
for j in range(i+1, len(li)):
if li[j] < li[min_loc]:
min_loc = j
if min_loc != i:
li[i], li[min_loc] = li[min_loc], li[i]
插入排序
实现思路
将除首个元素外的其他元素依次与有序区比较(边比较边右移直至)插入合适位置
代码
def insert_sort(li):
for i in range(1, len(li)):
tmp = li[i]
j = i - 1
while j >= 0 and li[j] > tmp:
li[j+1] = li[j]
j -= 1
li[j+1] = tmp
时间复杂度O(n²)
快速排序
实现思路
取元素P,使P归位(列表被P分成两部分,左边都比P小,右边都比P大),递归完成排序
代码
def partition(li, left, right):
tmp = li[left]
while left < right:
while left < right and li[right] >= tmp:
right -= 1
li[left] = li[right]
while left < right and li[left] <= tmp:
left += 1
li[right] = li[left]
li[left] = tmp
return left
def quick_sort(li, left, right):
if left < right:
mid = partition(li, left, right)
quick_sort(li, left, mid-1)
quick_sort(li, mid+1, right)
时间复杂度O(nlogn)
堆排序
树
即一种可以递归定义的数据结构
二叉树
即度不超过二的树,每一层的节点数都达到最大值的二叉树就叫做满二叉树,叶子节点只出现在最下层和次下层,并且最下面一层的节点都集中在该层最左边的连续位置的二叉树就叫完全二叉树。
二叉树的存储方式:顺序存储、链式存储
堆是一种特殊的完全二叉树
父节点下标:i
子节点下标:2i+1, 2i+2
堆
- 大根堆:即任一节点都比其孩子节点大的完全二叉树
- 小根堆:即任一节点都比其孩子节点小的完全二叉树
堆的向下调整
假设,节点的左右子树都是堆,但由节点和子树组成的不是堆,这样可以通过一次向下调整使其变成一个堆
堆排序实现思路
- 建立堆
- 堆顶元素即为最大元素,移存至末尾叶子节点(作为有序区)
- 将原末尾叶子节点值移至堆顶,做向下调整,重新移存堆顶
- 遍历取数
代码
def sift(li, root, leaf):
i = low
j = 2 * i + 1
tmp = li[low]
while j <= leaf:
if j + 1 <= leaf and li[j+1] > li[j]:
j += 1
if li[j] > tmp:
li[i] = li[j]
i = j
j = 2 * i + 1
else:
li[i] = tmp
break
else:
li[i] = tmp
def heap_sort(li):
n = len(li)
for i in range((n-2)//2, -1, -1):
sift(li, i, n-1)
# 建堆完毕
for i in range(n-1, -1, -1):
li[0], li[i] = li[i], li[0]
sift(li, 0, i-1)
时间复杂度O(nlogn)
堆排序模块
heapq
Topk问题
def sift(li, low, high):
i = low
j = 2 *i + 1
tmp = li[low]
while j <= high:
if j + 1 <= high and li[j+1] < li[j]:
j = j + 1
if li[j] < tmp:
li[i] = li[j]
i = j
j = 2 * i + 1
else:
break
li[i] = tmp
def topk(li, k):
heap = li[0:k]
for i in range((k-2)//2, -1, -1):
sift(heap, i, k-1)
for i in range(k, len(li)-1):
if li[i] > heap[0]:
heap[0] = li[i]
sift(heap, 0, k-1)
for i in range(k-1, -1, -1):
heap[0], heap[i] = heap[i], heap[0]
sift(heap, 0, i-1)
'''
def heap_sort(li):
n = len(li)
for i in range((n-2)//2, -1, -1):
sift(li, i, n-1)
for i in range(n-1, -1, -1):
li[0], li[i] = li[i], li[0]
sift(li, 0, i-1)
'''
归并排序
归并
将两个有序列表合并为一个有序列表
归并实现
def merge(li, low, mid, high):
i = low
j = mid + 1
list_tmp = []
while i <= mid and j <= high:
if li[i] < li[j]:
list_tmp.append(li[i])
i += 1
else:
list_tmp.append(li[j])
j += 1
while i <= mid:
list_tmp.append(li[i])
i += 1
while j <= high:
list_tmp.append(li[j])
j += 1
li[low:high+1] = list_tmp
实现
def merge(li, low, mid, high):
i = low
j = mid + 1
list_tmp = []
while i <= mid and j <= high:
if li[i] < li[j]:
list_tmp.append(li[i])
i += 1
else:
list_tmp.append(li[j])
j += 1
while i <= mid:
list_tmp.append(li[i])
i += 1
while j <= high:
list_tmp.append(li[j])
j += 1
li[low:high+1] = list_tmp
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)
空间复杂度(非原地排序):O(n)
进阶排序算法总结
时间复杂度都是O(nlogn)
一般情况下,就运行时间而言:
快速排序<归并排序<堆排序
缺点:快速排序存在极端情况;归并排序不是原地排序,需要额外内存;堆排序在这三种算法中相对较慢
希尔排序
即一种分组插入排序算法
实现思路
取整数d1 = n/2,将元素分为d1个组,每组相邻元素距离为d1,在各组内直接插入排序
取整数d2 = d1/2,重复步骤一,直到di = 1,所有元素同一组内直接插入排序
希尔排序每趟并不使某些元素有序,而是使整体越来越接近有序,最后一趟使整体数据有序
代码
def insert_sort_gap(li):
for i in range(gap, len(li)):
tmp = li[i]
j = i - gap
while j >= 0 and li[j] > tmp:
li[j+gap] = li[j]
j -= gap
li[j+gap] = tmp
def shell_sort(li):
d = len(li) // 2
while d >= 1:
insert_sort_gap(li, d)
d //= 2
时间复杂度:与gap有关
计数排序
实现思路
前提是一直所有元素的共同范围,通过遍历所有元素计数重复元素的值和对应的数量,然后根据计数结果覆盖原列表
代码
def count_sort(li, max_count=100):
count = [0 for _ in range(max_count + 1)]
for val in li:
count[val] += 1
li.clear()
for index, val in enumerate(count):
if val > 0:
li += [index] * val
时间复杂度O(n)
桶排序
实现思路
将元素分桶,保持桶间、桶内有序,返回连接后的所有桶
代码
def bucket_sort(li, n=100, max_num=10000):
buckets = [[] for _ in range(n)]
for var in li:
i = min(var // (max_num // n), n-1)
buckets[i].append(var)
for j in range(len(buckets[i]-1), 0, -1):
if buckets[i][j] < buckets[i][j-1]:
buckets[i][j], buckets[i][j-1] = buckets[i][j-1], buckets[i][j]
else:
break
sorted_li = []
for bucket in buckets:
sorted_li += bucket
return sorted_li
时间复杂度
与分桶策略有关
空间复杂度O(nk)
基数排序
实现思路
将元素值按位分为多个关键字分桶排序
代码
def radix_sort(li):
max_num = max(li)
it = 0
while 10 ** it <= max_num:
buckets = [[] for _ in range(10)]
for var in li:
digit = (var // 10 ** it) % 10
buckets[digit].append(var)
li.clear()
for bucket in buckets:
li += bucket
it += 1