目录
待补充其他排序算法, 如快速排序, 希尔排序等.
其他平衡树, 如红黑树, B-树等
插入排序(insertion sort)
对于一个数组A, 指针key用来遍历数组A.
指针key每次移动后都要保证A[0
插入操作
时间复杂度移动需要O(n^2) 比较操作的时间复杂度取决于key处的数字前移的实现方式:
- 如果使用比较并交换相邻数字来实现, 那么比较操作的时间复杂度为.
- 如果使用二分查找来实现, 那么比较的时间复杂度为
如果比较操作的开销远大于移动的开销, 那么使用二分查找的时间复杂度将会更优. 如果比较操作开销与移动操作开销相等, 那么时间复杂度均为O(n^2).
代码实现
def insertion_sort_swap(A): """ 对数组A进行插入排序, 使用交换实现. 一种O(n^2)的原地稳定排序. """ for key in range(len(A)): val = A[key] # 将A[key]处的数插入至A[0:key - 1]中正确的位置 i = key - 1 while i > -1 and A[i] > val: A[i + 1] = A[i] i = i - 1 A[i + 1] = val return A
def insertion_sort_binary(A): """ 对数组A进行插入排序, 使用二分实现. 一种O(n^2)的原地稳定排序. 在比较开销显著大于移动开销时, 具有更好的时间复杂度. """ # 共n次 for key in range(len(A)): val = A[key] del A[key] # 将A[key]处的数插入至A[0:key - 1]中正确的位置 l, r = -1, key mid = (l + r) // 2 # 二分查找, 单次O(log n) while not r == l + 1: if A[mid] <= val: l = mid else: r = mid mid = (l + r) // 2 # 插入, 单次O(n) A.insert(mid + 1, val) return A
归并排序(merge sort)
分治思想.
基础情况:
若数组只有1个元素, 返回原数组.
递归情况:
- 将数组划分成左数组L与右数组R
- 对左数组L和右数组R分别排序
- 合并排序后的L和R.
合并操作:
复杂度分析:
- 划分需要
- 递归需要
- 合并需要:
- 第一层需要
- 第二层需要, 即
- 第三层需要, 即
- … 故合并需要的时间复杂度为n*层数, 即
代码实现
def merge(A, B): """ 归并排序对应的合并操作. 开销O(n). """ L = [] key_a = key_b = 0 while True: # 空列表判定 if key_a >= len(A): L.extend(B[key_b:]) return L elif key_b >= len(B): L.extend(A[key_a:]) return L
# 合并 if A[key_a] > B[key_b]: L.append(B[key_b]) key_b += 1 else: # 保证排序稳定性 L.append(A[key_a]) key_a += 1
def merge_sort(A): """ 对数组A进行归并排序. 一个时间复杂度O(nlog n)的稳定非原地排序. 空间开销为O(n) """ # base case if len(A) <= 1: return A
# recursion mid = len(A) // 2 L = A[0:mid] R = A[mid:len(A)] L = merge_sort(L) R = merge_sort(R) return merge(L, R)
树形递归的时间复杂度
- 每次递归需要:
解得
(归并排序的时间复杂度)
- 每次递归需要:
解得
- 每次递归需要:
解得
优先级队列(priority queue)
实现某个集合, 中的每个元素都有对应的优先级(不同元素的优先级可能相同). 应当支持如下操作:
- 插入新元素
- (移除并)获取最大值.
- 改变某元素的优先级
堆是优先级队列的一种实现
堆(Heap)
是一个数组, 也可以看作一个完全二叉树, 堆h的根节点为h[1]. 对于节点x来说:
- 其父节点: x = 1时无父节点, x > 1时父节点为x // 2, 或x >> 1.
- 左子节点: 2 * x, 或x << 1.
- 右子节点: 2 * x + 1, 或x << 1 + 1. 大根堆: 每个节点都大于它的子节点(如果存在). 小根堆类似.
二叉堆的性质
- 叶子节点永远占一半(ceil(n/2)), 非叶子结点占(floor(n/2)). 这同时也说明最后一个叶子节点的编号除以2即是最后一个非叶子结点.
堆的操作
- 建大根堆: 从无序数组中建立一个大根堆.
- 大根堆化(max_heapify)
- 插入
- (移除并)获取最大值
- 堆排序.
大根堆化(max_heapify)
对于单个违反堆序的节点, 将其移动至正确的位置, 设以其为根节点的子堆高度为n, 则大根堆化所需时间复杂度为.
概括地说, 对于某个需要大根堆化的节点x, 持续将x与左右子节点比较, 交换, 直到:
- x大于左节点, 且x大于右节点
- x已无左右子节点
建大根堆
从n/2位置开始逆序遍历到1, 每个节点都进行大根堆化. (从n/2位置开始是因为大于n/2的节点均是叶子节点, 无需大根堆化, 逆序是为了保证建堆过程的无后效性)
时间复杂度: 求和后可以知道时间复杂度为.
堆排序(heap sort)
- 建大根堆A
- 弹出A[1]
- 交换A[n]与A[1], A的size减少1.
- 对A[1]大根堆化.
- 重复步骤2到5, 直到A的size为0.
时间复杂度:
代码实现
def max_heapify(A, x): """ 将堆A (A[0]储存堆元素个数n, 实际索引从1开始) 的某个违反堆序的节点x大根堆化. """ lt = 2 * x rt = 2 * x + 1 # 确定x, lt, rt中最大的, 并与x交换 if lt <= A[0] and A[x] < A[lt]: largest = lt else: largest = x
if rt <= A[0] and A[rt] > A[largest]: largest = rt
# 如果并未交换, 说明x在正确位置上 if x == largest: return
# 否则则说明x在largest位置上, 递归 A[x], A[largest] = A[largest], A[x] max_heapify(A, largest)
def build_max_heap(A): """ 从数组A建堆(索引从0开始) """ # 保证堆的索引从1开始, A[0]储存堆的元素个数n. A.insert(0, len(A))
# 从n / 2开始倒序遍历. i = A[0] // 2 while i > 0: max_heapify(A, i) i -= 1
def insert(A, val): """ 向堆A(A[0]储存元素个数n, 索引从1开始)中插入值val """ # 若数组大小不足, append, 反之则赋值 idx = A[0] + 1 if idx == len(A): A.append(val) else: A[idx] = val A[0] += 1
# 上滤, 每次比较节点与其父节点 while idx != 1 and A[idx] > A[idx // 2]: A[idx], A[idx // 2] = A[idx // 2], A[idx] idx = idx // 2
def top(A): """ 查询堆顶 """ return A[1]
def pop(A): """ 移除(塞至堆尾并size-1)并返回堆顶 """ last = A[0] A[1], A[last] = A[last], A[1] A[0] -= 1 max_heapify(A, 1) return A[last]
def heap_sort(A): """ 对某数组A进行升序堆排序, 索引从0开始 一种O(nlog n)的原地非稳定排序. """ build_max_heap(A) while A[0] > 0: pop(A) # 每次将最大元素塞至队尾 del A[0] # 将A恢复为数组
二叉搜索树(BST)
数组的二分查找可以做到, 但插入操作为.
链表可以做到插入, 但无法二分查找, 查找操作为.
链式存储并非绝对不适合二分查找. 回顾数组的二分查找过程:
- 我们找到数组的中点, 并以此为基准将其分成两半
- 在左半边或右半边的数组进行查找, 数组的元素数量减半.
- 继续下去, 直至数组的元素个数为1.
我们可以发现, 这个过程中访问的节点可以形成一个有向图. 原数组中点指向左数组中点和右数组中点, 而左数组中点又指向1/4数组中点和2/4数组中点… 每个节点都指向对应的左半边中点和右半边中点, 形成一个二叉树.
这个二叉树就是二叉搜索树(Binary Search Tree, BST), 它是一种能够进行二分查找的链式数据结构. 在理想状态下具有的查找效率. 相应地, 插入操作会变慢, 为.
二叉搜索树的递归定义如下:
- 空树是一个二叉搜索树.
- 二叉搜索树的子树是二叉搜索树.
- 对于任意节点, 大于其左子树的所有节点, 小于其右子树的所有节点.
BST的操作
- 插入:
insert(root, val)
- 查找:
search(root, val)
- 查最大/最小值:
find_max(root)
,find_min(root)
- 删除:
delete(root, val)
- 减少:
reduce(root, val)
, 使某值的数量减少1
BST排序
容易注意到, BST做中序遍历(按照左子树-根-右子树的顺序)即为排序. 复杂度
class TreeNode: """ :key: 该节点的键(或值) :num: 该值的数量 :left: 指向左子树的指针 :right: 指向右子树的指针 """ def __init__(self, key):
self.key, self.num = key, 1 self.left = None self.right = None
def __repr__(self): return str_of_tree(self)
def str_of_tree(root: TreeNode, h = 0) -> str: """ 接收一个二叉树的根节点,返回整个树的可视化结构。 :param root: 树的根节点 :return: 字符串, 作为树的整个可视化结构 """ if root is None: return '' s = '' s += f" Key:{root.key}({root.num})\n" s += h * ' ' + ' Left:' s += str_of_tree(root.left, h + 1) s += '\n' s += h * ' ' + 'Right:' s += str_of_tree(root.right, h + 1) return s
def insert(root: TreeNode, val: int) -> TreeNode: """ 通过myroot = insert(myroot, val)的方式向树中插入元素, 始终返回根节点 :param root: 根节点 :param val: 需要插入的值 :return: 根节点 """ if root is None: return TreeNode(val)
current = root while True: if current.key == val: current.num += 1 return root elif val < current.key: if current.left is None: current.left = TreeNode(val) return root else: current = current.left elif val > current.key: if current.right is None: current.right = TreeNode(val) return root else: current = current.right
def search(root: TreeNode, val: int) -> TreeNode | None: """ 在树中搜索元素, 若找到返回该节点, 未找到返回None :param root: 根节点 :param val: 搜索的值 :return: 节点或None """ if root is None: return None if val == root.key: return root
current = root while True: if current is None: return None elif val == current.key: return current elif val > current.key: current = current.right elif val < current.key: current = current.left
def find_max(root: TreeNode) -> TreeNode | None: """ 找到树中的最大值 :param root: 根节点 :return: 空树返回None, 否则返回最大值节点 """ if root is None: return None
current = root while True: if current.right is None: return current else: current = current.right
def find_min(root: TreeNode) -> TreeNode | None: """ 找到树中的最小值 :param root: 根节点 :return: 空树返回None, 否则返回最小值节点 """ if root is None: return None
current = root while True: if current.left is None: return current else: current = current.left
def delete(root: TreeNode, val: int) -> TreeNode | None: """ 删除BST树中所有的某值 :param root: BST树的根 :param val: 需要删除的值 :return: 删除后的根节点 """ if root is None: return None
if val > root.key: root.right = delete(root.right, val) elif val < root.key: root.left = delete(root.left, val) elif val == root.key: if root.left is None: root = root.right elif root.right is None: root = root.left else: new_root = find_min(root.right) root.key, new_root.key = new_root.key, root.key root.num, new_root.num = new_root.num, root.num root.right = delete(root.right, val) return root
def reduce(root: TreeNode, val: int) -> TreeNode | None: """ 使BST树中某值的数量减少1 :param root: BST树的根 :param val: 某值 :return: 减少后的根节点 """ if root is None: return None
if val > root.key: root.right = reduce(root.right, val) elif val < root.key: root.left = reduce(root.left, val) elif val == root.key: if root.num >= 2: root.num -= 1 else: if root.left is None: root = root.right elif root.right is None: root = root.left else: new_root = find_min(root.right) root.key, new_root.key = new_root.key, root.key root.num, new_root.num = new_root.num, root.num root.right = delete(root.right, val) return root
def build_BST(arr: list) -> TreeNode: """ 从数组建立BST, 理想情况下复杂度为O(nlog n), 最差复杂度为O(n^2) :param arr: 数组 :return: BST树的根节点 """ bst = None for i in arr: bst = insert(bst, i) return bst
def BST_sort(arr: list) -> list: """ 利用BST排序, 理想情况下复杂度为O(nlog n), 最差复杂度为O(n^2) :param arr: 数组 :return: 排序后的数组 """ ans = []
# 递归辅助函数 def helper(root: TreeNode) -> None: if root is None: return helper(root.left) t = root.num while t: ans.append(root.key) t -= 1 helper(root.right)
helper(build_BST(arr)) return ans
AVL树
发明rotate操作的人真是天才! —ykindred
BST在数据分布不佳的情况下, 操作的时间复杂度会由O(log n)退化为O(n). 这主要是子树之间高度不平衡导致的.
最早的自平衡二叉搜索树(或高度平衡树, 也可简称为平衡树)是AVL树. 得名于它的发明者G. M. Adelson-Velsky和E. M. Landis.
AVL树满足如下性质:
- AVL树本身是一棵二叉搜索树
- 其每个节点的左子树高度与右子树高度差(平衡因子, BF)的绝对值不超过1.
由于需要检查子树高度差, 所以AVL的每个节点额外储存高度数据, 每次插入或删除时自底向上地更新.
AVL可以将插入, 删除, 查找等操作保证在O(log n)量级.
旋转操作(Rotate)
- 左旋(Left Rotate): BF = -2时, 进行如下操作:
- 右孩子变根.
- 右孩子的左孩子变根的右孩子.
- 根变右孩子的左孩子.
- 右旋(Right Rotate)则相反. BF = 2时, 进行:
- 左孩子变根.
- 左孩子的右孩子变根的左孩子.
- 根变左孩子的右孩子.
根据失衡路径, 可以分为如下四种情况:
- LL型: 新节点在某节点z的左子树的左子树中, 此时对z进行一次右旋
- RR型: 新节点在某节点z的右子树的右子树中, 此时对z进行一次左旋
- LR型: 新节点在某节点z的左子树的右子树中, 此时先对z左子树做左旋(这可以将LR型转换为LL型), 再对z做右旋.
- RL型: 新节点在某节点z的右子树的左子树中, 此时先对z右子树做右旋(这可以将RL型转换为RR型), 再对z做左旋.
class TreeNode: """ :key: 该节点的键(或值) :num: 该值的数量 :left: 指向左子树的指针 :right: 指向右子树的指针 :height: 某节点的高度 """ def __init__(self, key): self.key = key self.num = 1 self.left = None self.right = None self.height = 1
def __repr__(self): return str_of_tree(self)
def str_of_tree(root: TreeNode, h = 0) -> str: """ 接收一个二叉树的根节点,返回整个树的可视化结构。 :param root: 树的根节点 :return: 字符串, 作为树的整个可视化结构 """ if root is None: return '' s = '' s += f" Key:{root.key}({root.num})\n" s += h * ' ' + ' Left:' s += str_of_tree(root.left, h + 1) s += '\n' s += h * ' ' + 'Right:' s += str_of_tree(root.right, h + 1) return s
def find_max(root: TreeNode) -> TreeNode | None: """ 找到树中的最大值 :param root: 根节点 :return: 空树返回None, 否则返回最大值节点 """ if root is None: return None
current = root while True: if current.right is None: return current else: current = current.right
def find_min(root: TreeNode) -> TreeNode | None: """ 找到树中的最小值 :param root: 根节点 :return: 空树返回None, 否则返回最小值节点 """ if root is None: return None
current = root while True: if current.left is None: return current else: current = current.left
def get_height(node: TreeNode) -> int: """ 给出某一节点的高度 :param node: 某节点 """ if node is None: return 0 return node.height
def get_BF(node: TreeNode) -> int: """ 给出某节点的平衡因子 :param node: 某节点 :return: 平衡因子, 整数 """ if node is None: return 0 return get_height(node.left) - get_height(node.right)
def right_rotate(node: TreeNode) -> TreeNode: """ 对某子树做右旋, 返回右旋后的根节点 :param node: 某节点 :return: 右旋后的根节点 """ # 右旋时左子树不应为空 assert not (node.left is None), "left child shouldn't be None"
# 旋转 new_root, new_right, new_right_left = node.left, node, node.left.right node, node.right, node.right.left = new_root, new_right, new_right_left
# 更新高度 new_root.right.height = 1 + max(get_height(new_root.right.left), get_height(new_root.right.right)) new_root.height = 1 + max(get_height(new_root.left), get_height(new_root.right))
return new_root
def left_rotate(node: TreeNode) -> TreeNode: """ 对某子树做左旋, 返回左旋后的根节点 :param node: 某节点 :return: 左旋后的根节点 """ # 左旋时右子树不应为空 assert not (node.right is None), "right child shouldn't be None"
# 旋转 new_root, new_left, new_left_right = node.right, node, node.right.left node, node.left, node.left.right = new_root, new_left, new_left_right
# 更新高度 new_root.left.height = 1 + max(get_height(new_root.left.right), get_height(new_root.left.left)) new_root.height = 1 + max(get_height(new_root.right), get_height(new_root.left)) return new_root
def rotate(root: TreeNode) -> TreeNode: """ 对某节点做平衡检查, 若失衡则旋转 :param root: 某节点 :return: 检查后的根节点 """ BF = get_BF(root) left_BF = get_BF(root.left) right_BF = get_BF(root.right)
if BF >= 2: # LL if left_BF == 1: root = right_rotate(root) # LR elif left_BF == -1: root.left = left_rotate(root.left) root = right_rotate(root)
elif BF <= -2: # RR if right_BF == -1: root = left_rotate(root) # RL elif right_BF == 1: root.right = right_rotate(root.right) root = left_rotate(root) return root
def insert(root: TreeNode, val: int) -> TreeNode: """ 向AVL树中插入某值 :param root: AVL树的根 :param val: 某值 :return: 插入后的根节点 """ # 标准BST插入 if root is None: return TreeNode(val)
if val == root.key: root.num += 1 elif val > root.key: root.right = insert(root.right, val) elif val < root.key: root.left = insert(root.left, val)
# 更新高度 root.height = 1 + max(get_height(root.left), get_height(root.right))
# 检查旋转 root = rotate(root)
return root
def delete(root: TreeNode, val: int) -> TreeNode | None: """ 删除AVL树中所有的某值 :param root: AVL树的根 :param val: 需要删除的值 :return: 删除后的根节点 """ # 标准BST删除 if root is None: return None
if val > root.key: root.right = delete(root.right, val) elif val < root.key: root.left = delete(root.left, val) elif val == root.key: if root.left is None: root = root.right elif root.right is None: root = root.left else: new_root = find_min(root.right) root.key, new_root.key = new_root.key, root.key root.num, new_root.num = new_root.num, root.num root.right = delete(root.right, val)
if not(root is None): # 更新高度 root.height = 1 + max(get_height(root.left), get_height(root.right))
# 检查旋转 root = rotate(root)
return root
def search(root: TreeNode, val: int) -> TreeNode | None: """ 在树中搜索元素, 若找到返回该节点, 未找到返回None :param root: 根节点 :param val: 搜索的值 :return: 节点或None """ if root is None: return None if val == root.key: return root
current = root while True: if current is None: return None elif val == current.key: return current elif val > current.key: current = current.right elif val < current.key: current = current.left
def reduce(root: TreeNode, val: int) -> TreeNode | None: """ 使AVL树中某值的数量减少1 :param root: AVL树的根 :param val: 某值 :return: 减少后的根节点 """ # 标准BST减少 if root is None: return None
if val > root.key: root.right = reduce(root.right, val) elif val < root.key: root.left = reduce(root.left, val) elif val == root.key: if root.num >= 2: root.num -= 1 return root else: if root.left is None: root = root.right elif root.right is None: root = root.left else: new_root = find_min(root.right) root.key, new_root.key = new_root.key, root.key root.num, new_root.num = new_root.num, root.num root.right = delete(root.right, val)
if not (root is None): # 更新高度 root.height = 1 + max(get_height(root.left), get_height(root.right))
# 检查旋转 root = rotate(root)
return root
def build_AVL(arr: list) -> TreeNode: """ 从数组建立AVL, 复杂度为O(nlog n) :param arr: 数组 :return: AVL树的根节点 """ avl = None for i in arr: avl = insert(avl, i) return avl
def AVL_sort(arr: list) -> list: """ 利用AVL排序, 复杂度为O(nlog n) :param arr: 数组 :return: 排序后的数组 """ ans = []
# 递归辅助函数 def helper(root: TreeNode) -> None: if root is None: return helper(root.left) t = root.num while t: ans.append(root.key) t -= 1 helper(root.right)
helper(build_AVL(arr)) return ans
比较排序的时间复杂度下界
是, 可以分3步证明:
- 比较排序的过程可以用决策树来表示
- 每种排列对应每个独特的路径(结果), 所以共有种路径(结果), 也就是决策树有个叶子节点
- 树高至少为, 数学上可以证明.
故任何基于比较的排序算法, 其最坏情况的时间复杂度至少为. 而同样可以证明平均情况下的时间复杂度下界依然是.
此处只介绍思想, 详细证明略.
计数排序
我们已经证明, 比较排序的时间复杂度下界为, 这是排序界的”名作之壁”, 能达到这个复杂度的排序算法我们都可以认为其在时间上是”不劣”的. 那么有没有不基于比较的更快的排序算法呢?
有的兄弟有的, 这就是非比较排序, 我们主要介绍计数排序和基数排序.
计数排序(counting sort): 对于小的非负整数的排序, 具有线性时间复杂度.
对每个整数都使用一个变量来记录其数量, 最后从小到大输出即可.
容易注意到, 这是一种非原地排序, 可以实现稳定. 并且对排序的数据有要求(必须是小的非负整数或者能够映射到小的非负整数).
def counting_sort(arr: list, maxx: int = int(300)) -> list: """ 一种O(n)的不稳定非原地排序, 只针对小的非负整数 :param arr: 需要排序的数组 :param maxx: 数据的上界 :return: 排序后的数组 >>> a = [12,5,6,8,9,1,2,0,2,6,5,8,7,4,5,6,4,6] >>> counting_sort(a, 200) [0, 1, 2, 2, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 8, 8, 9, 12] >>> counting_sort(a) [0, 1, 2, 2, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 8, 8, 9, 12] """ cnt = [0 for _ in range(maxx)] ans = [] for i in arr: cnt[i] += 1 for i in range(len(cnt)): while cnt[i]: ans.append(i) cnt[i] -= 1 return ans
基数排序
计数排序有很多问题:
- 只能排序整数
- 在数据范围较大的时候, 排序的空间复杂度过高
**基数排序(radix sort)**牺牲了常数(普遍认为)复杂度, 解决了这两个问题.
基数排序的基本思想: 将整数分成若干个位数, 对每个位数进行计数排序.
基数排序可以对字符串进行排序. 基数排序分为LSD(从低位到高位)和MSD(从高位到低位). 这里仅以LSD作为演示.
from math import log10, ceildef radix_sort_LSD(arr: list, maxx: int = 10000): """ 一种线性时间复杂度的稳定非原地排序. 从低位到高位排序 :param arr: 数组 :param maxx: 数据的上界 :return: 排序后的数组 >>> a = [12,5,6,8,9,1,2,0,2,6,5,8,7,4,5,6,4,6] >>> radix_sort_LSD(a, 12) [0, 1, 2, 2, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 8, 8, 9, 12] >>> radix_sort_LSD(a) [0, 1, 2, 2, 4, 4, 5, 5, 5, 6, 6, 6, 6, 7, 8, 8, 9, 12] """ k = ceil(log10(maxx)) ans = [] base = 1 modu = 10 while k: bucket = [[] for _ in range(10)] ans.clear() for i in arr: idx = i % modu // base bucket[idx].append(i) for i in range(10): ans = ans + bucket[i] arr = ans.copy() base *= 10 modu *= 10 k -= 1 return arr
更多排序
桶排序, 快速排序, 希尔排序…有兴致的时候再更新.