康托展开(Cantor Expansion)
康托展开是一种用于排列唯一编码的方法,可以把一个排列转换成一个整数,并且能够从整数反向解析出原排列。主要用于排列的唯一表示、字典序排名、全排列生成等问题。
1. 康托展开公式
给定一个长度为 n n n 的排列 P = ( p 1 , p 2 , . . . , p n ) P = (p_1, p_2, ..., p_n) P=(p1,p2,...,pn),康托展开计算其在所有排列中的字典序排名(从 0 0 0 开始计数):
index = ∑ i = 1 n ( count of numbers smaller than p i in remaining elements ) × ( n − i ) ! \text{index} = \sum_{i=1}^{n} (\text{count of numbers smaller than } p_i \text{ in remaining elements}) \times (n - i)! index=∑i=1n(count of numbers smaller than pi in remaining elements)×(n−i)!
其中:
- ( n − i ) ! (n - i)! (n−i)! 表示剩余元素的全排列个数。
- count of numbers smaller than p i in remaining elements \text{count of numbers smaller than } p_i \text{ in remaining elements} count of numbers smaller than pi in remaining elements 表示 p i p_i pi 在未出现的数中有多少个比它小。
例子
假设有排列 P = ( 3 , 1 , 4 , 2 ) P = (3,1,4,2) P=(3,1,4,2),计算其康托展开值:
- 3 3 3 前面有 2 2 2 个比它小的数(即 1 1 1 和 2 2 2),贡献 2 × 3 ! = 2 × 6 = 12 2 \times 3! = 2 \times 6 = 12 2×3!=2×6=12。
- 1 1 1 前面没有比它小的数,贡献 0 × 2 ! = 0 0 \times 2! = 0 0×2!=0。
- 4 4 4 前面有 1 1 1 个比它小的数(即 2 2 2),贡献 1 × 1 ! = 1 1 \times 1! = 1 1×1!=1。
- 2 2 2 前面没有比它小的数,贡献 0 × 0 ! = 0 0 \times 0! = 0 0×0!=0。
最终计算:
12
+
0
+
1
+
0
=
13
12 + 0 + 1 + 0 = 13
12+0+1+0=13
所以,
(
3
,
1
,
4
,
2
)
(3,1,4,2)
(3,1,4,2) 在所有排列中的排名是
13
13
13(从
0
0
0 开始)。
2. 计算康托展开
Python 代码
from math import factorial
def cantor_expansion(permutation):
n = len(permutation)
index = 0
used = [0] * (n + 1) # 记录某个数是否出现过
for i in range(n):
count = sum(1 for x in range(1, permutation[i]) if not used[x]) # 计算未出现的比 p[i] 小的数
index += count * factorial(n - i - 1)
used[permutation[i]] = 1 # 标记已使用
return index
# 测试
perm = [3, 1, 4, 2]
print(cantor_expansion(perm)) # 输出 13
3. 逆康托展开(从排名反推排列)
康托展开的逆运算是根据排名计算排列。
逆康托展开步骤
- 初始化一个可用元素列表 [ 1 , 2 , 3 , … , n ] [1,2,3,\dots,n] [1,2,3,…,n]。
- 逐步确定排列的每一位:
- 计算当前位属于哪个桶: rank / / ( n − 1 ) ! \text{rank} // (n-1)! rank//(n−1)!。
- 确定该桶对应的元素,并移除该元素。
- 计算 rank % ( n − 1 ) ! \text{rank} \% (n-1)! rank%(n−1)!,继续下一位。
Python 代码
def inverse_cantor(rank, n):
elements = list(range(1, n + 1))
permutation = []
for i in range(n, 0, -1):
fact = factorial(i - 1)
index = rank // fact
rank %= fact
permutation.append(elements.pop(index))
return permutation
# 测试
print(inverse_cantor(13, 4)) # 输出 [3, 1, 4, 2]
4. 复杂度分析
- 康托展开计算:每一位都需要遍历 O ( n ) O(n) O(n) 个元素,时间复杂度 O ( n 2 ) O(n^2) O(n2)。
- 逆康托展开:每次寻找第
k
k
k 小的数,使用
pop()
需 O ( n ) O(n) O(n),总复杂度 O ( n 2 ) O(n^2) O(n2)。 - 优化:使用树状数组或平衡树可以优化到 O ( n log n ) O(n \log n) O(nlogn)。
5. 应用场景
- 排列的唯一编码(用于哈希、压缩、存储)。
- 字典序排名计算(如求一个排列是第几小的全排列)。
- 求解全排列的第 k k k 个排列(如 LeetCode 60 题)。
- A 搜索中的状态压缩存储*(如 8 数码问题)。
6. 总结
- 康托展开:将一个排列映射为一个整数,计算该排列在所有排列中的排名。
- 逆康托展开:给定排名,恢复原排列。
- 时间复杂度: O ( n 2 ) O(n^2) O(n2)(可优化至 O ( n log n ) O(n \log n) O(nlogn))。
- 主要用途:唯一编码、字典序排名、全排列查询。
这是一种非常实用的排列索引方法,在组合数学、搜索算法和数论中都有应用。