5561 字
28 分钟
算法导论(2) 排序与树
目录
  1. 插入排序
  2. 归并排序
  3. 优先级队列
  4. 二叉搜索树
  5. AVL树
  6. 比较排序的时间复杂度下界
  7. 计数排序
  8. 基数排序
待补充

其他排序算法, 如快速排序, 希尔排序等.

其他平衡树, 如红黑树, B-树等

插入排序(insertion sort)#

对于一个数组A, 指针key用来遍历数组A. 指针key每次移动后都要保证A[0 + 1]已排序. 通过将key处的数字前移到正确位置来实现. 当key到达数组末尾时, A[0]排序完毕, 即整个数组排序完毕.

插入操作#

插入排序

时间复杂度

移动需要O(n^2) 比较操作的时间复杂度取决于key处的数字前移的实现方式:

  1. 如果使用比较并交换相邻数字来实现, 那么比较操作的时间复杂度为O(n2)O(n^2).
  2. 如果使用二分查找来实现, 那么比较的时间复杂度为O(nlogn)O(n\log n)

如果比较操作的开销远大于移动的开销, 那么使用二分查找的时间复杂度将会更优. 如果比较操作开销与移动操作开销相等, 那么时间复杂度均为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个元素, 返回原数组.

递归情况:#

  1. 将数组划分成左数组L与右数组R
  2. 对左数组L和右数组R分别排序
  3. 合并排序后的L和R.

合并操作:#

合并操作

复杂度分析:#

  1. 划分需要O(logn)O(\log n)
  2. 递归需要O(n)O(n)
  3. 合并需要:
    1. 第一层需要O(n)O(n)
    2. 第二层需要2O(n/2)2O(n/2), 即O(n)O(n)
    3. 第三层需要4O(n/2)4O(n/2), 即O(n)O(n)
    4. … 故合并需要的时间复杂度为n*层数, 即O(nlogn)O(n\log 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)
树形递归的时间复杂度
  1. 每次递归需要O(n)O(n):
T(n)=2T(n/2)+cnT(n) = 2T(n/2) + cn

解得

T(n)=O(nlogn)T(n) = O(n\log n)

(归并排序的时间复杂度)

  1. 每次递归需要O(1)O(1):
T(n)=2T(n/2)+cT(n) = 2T(n/2) + c

解得

T(n)=O(n)T(n) = O(n)
  1. 每次递归需要O(n2)O(n^2):
T(n)=2T(n/2)+cn2T(n) = 2T(n/2) + cn^2

解得

T(n)=O(n2)T(n) = O(n^2)

优先级队列(priority queue)#

实现某个集合SS, SS中的每个元素都有对应的优先级(不同元素的优先级可能相同). SS应当支持如下操作:

  1. 插入新元素
  2. (移除并)获取最大值.
  3. 改变某元素的优先级

是优先级队列的一种实现

堆(Heap)#

是一个数组, 也可以看作一个完全二叉树, 堆h的根节点为h[1]. 对于节点x来说:

  1. 其父节点: x = 1时无父节点, x > 1时父节点为x // 2, 或x >> 1.
  2. 左子节点: 2 * x, 或x << 1.
  3. 右子节点: 2 * x + 1, 或x << 1 + 1. 大根堆: 每个节点都大于它的子节点(如果存在). 小根堆类似.
二叉堆的性质
  1. 叶子节点永远占一半(ceil(n/2)), 非叶子结点占(floor(n/2)). 这同时也说明最后一个叶子节点的编号除以2即是最后一个非叶子结点.

堆的操作#

  1. 建大根堆: 从无序数组中建立一个大根堆.
  2. 大根堆化(max_heapify)
  3. 插入
  4. (移除并)获取最大值
  5. 堆排序.

大根堆化(max_heapify)#

对于单个违反堆序的节点, 将其移动至正确的位置, 设以其为根节点的子堆高度为n, 则大根堆化所需时间复杂度为O(logn)O(log n).

概括地说, 对于某个需要大根堆化的节点x, 持续将x与左右子节点比较, 交换, 直到:

  1. x大于左节点, 且x大于右节点
  2. x已无左右子节点

大根堆化 大根堆化 大根堆化

建大根堆#

从n/2位置开始逆序遍历到1, 每个节点都进行大根堆化. (从n/2位置开始是因为大于n/2的节点均是叶子节点, 无需大根堆化, 逆序是为了保证建堆过程的无后效性)

时间复杂度: T=c(n4+2n8+3n16+4n32+...+logn)T = c(\frac{n}{4} + \frac{2n}{8} + \frac{3n}{16} + \frac{4n}{32} + ... + \log n) 求和后可以知道时间复杂度为O(n)O(n).

建大根堆 建大根堆 建大根堆

堆排序(heap sort)#

  1. 建大根堆A
  2. 弹出A[1]
  3. 交换A[n]与A[1], A的size减少1.
  4. 对A[1]大根堆化.
  5. 重复步骤2到5, 直到A的size为0.

时间复杂度: O(nlogn)O(nlog n)

代码实现#

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)#

数组的二分查找可以做到O(logn)O(\log n), 但插入操作为O(n)O(n).

链表可以做到O(1)O(1)插入, 但无法二分查找, 查找操作为O(1)O(1).

链式存储并非绝对不适合二分查找. 回顾数组的二分查找过程:

  1. 我们找到数组的中点, 并以此为基准将其分成两半
  2. 在左半边或右半边的数组进行查找, 数组的元素数量减半.
  3. 继续下去, 直至数组的元素个数为1.

我们可以发现, 这个过程中访问的节点可以形成一个有向图. 原数组中点指向左数组中点和右数组中点, 而左数组中点又指向1/4数组中点和2/4数组中点… 每个节点都指向对应的左半边中点和右半边中点, 形成一个二叉树.

这个二叉树就是二叉搜索树(Binary Search Tree, BST), 它是一种能够进行二分查找的链式数据结构. 在理想状态下具有O(logn)O(\log n)的查找效率. 相应地, 插入操作会变慢, 为O(logn)O(\log n).

二叉搜索树的递归定义如下:

  1. 空树是一个二叉搜索树.
  2. 二叉搜索树的子树是二叉搜索树.
  3. 对于任意节点xx, xx大于其左子树的所有节点, 小于其右子树的所有节点.

BST的操作#

  1. 插入: insert(root, val)
  2. 查找: search(root, val)
  3. 查最大/最小值: find_max(root), find_min(root)
  4. 删除: delete(root, val)
  5. 减少: reduce(root, val), 使某值的数量减少1

BST排序#

容易注意到, BST做中序遍历(按照左子树-根-右子树的顺序)即为排序. 复杂度O(n)O(n)

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树满足如下性质:

  1. AVL树本身是一棵二叉搜索树
  2. 其每个节点的左子树高度与右子树高度差(平衡因子, BF)的绝对值不超过1.

由于需要检查子树高度差, 所以AVL的每个节点额外储存高度数据, 每次插入或删除时自底向上地更新.

AVL可以将插入, 删除, 查找等操作保证在O(log n)量级.

旋转操作(Rotate)#

  1. 左旋(Left Rotate): BF = -2时, 进行如下操作:
    1. 右孩子变根.
    2. 右孩子的左孩子变根的右孩子.
    3. 根变右孩子的左孩子.
  2. 右旋(Right Rotate)则相反. BF = 2时, 进行:
    1. 左孩子变根.
    2. 左孩子的右孩子变根的左孩子.
    3. 根变左孩子的右孩子.

根据失衡路径, 可以分为如下四种情况:

  1. LL型: 新节点在某节点z的左子树的左子树中, 此时对z进行一次右旋
  2. RR型: 新节点在某节点z的右子树的右子树中, 此时对z进行一次左旋
  3. LR型: 新节点在某节点z的左子树的右子树中, 此时先对z左子树做左旋(这可以将LR型转换为LL型), 再对z做右旋.
  4. 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

比较排序的时间复杂度下界#

O(nlogn)O(nlog n), 可以分3步证明:

  1. 比较排序的过程可以用决策树来表示
  2. 每种排列对应每个独特的路径(结果), 所以共有n!n!种路径(结果), 也就是决策树有n!n!个叶子节点
  3. 树高至少为log(n!)\log (n!), 数学上可以证明log(n!)=O(nlogn)\log(n!) = O(n\log n).

故任何基于比较的排序算法, 其最坏情况的时间复杂度至少为O(nlogn)O(n\log n). 而同样可以证明平均情况下的时间复杂度下界依然是Ω(nlogn)\Omega(n\log n).

此处只介绍思想, 详细证明略.

计数排序#

我们已经证明, 比较排序的时间复杂度下界为O(nlogn)O(n\log n), 这是排序界的”名作之壁”, 能达到这个复杂度的排序算法我们都可以认为其在时间上是”不劣”的. 那么有没有不基于比较的更快的排序算法呢?

有的兄弟有的, 这就是非比较排序, 我们主要介绍计数排序基数排序.

计数排序(counting sort): 对于小的非负整数的排序, 具有线性时间复杂度O(n)O(n).

对每个整数都使用一个变量来记录其数量, 最后从小到大输出即可.

容易注意到, 这是一种非原地排序, 可以实现稳定. 并且对排序的数据有要求(必须是小的非负整数或者能够映射到小的非负整数).

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

基数排序#

计数排序有很多问题:

  1. 只能排序整数
  2. 在数据范围较大的时候, 排序的空间复杂度过高

**基数排序(radix sort)**牺牲了常数(普遍认为)复杂度, 解决了这两个问题.

基数排序的基本思想: 将整数分成若干个位数, 对每个位数进行计数排序.

基数排序可以对字符串进行排序. 基数排序分为LSD(从低位到高位)和MSD(从高位到低位). 这里仅以LSD作为演示.

from math import log10, ceil
def 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

更多排序#

桶排序, 快速排序, 希尔排序…有兴致的时候再更新.

算法导论(2) 排序与树
https://fuwari.vercel.app/posts/算法导论2-排序与树/
作者
ykindred
发布于
2025-08-30
许可协议
CC BY-NC-SA 4.0