康托展开原理

康托展开(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)×(ni)!

其中:

  • ( n − i ) ! (n - i)! (ni)! 表示剩余元素的全排列个数。
  • 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),计算其康托展开值:

  1. 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
  2. 1 1 1 前面没有比它小的数,贡献 0 × 2 ! = 0 0 \times 2! = 0 0×2!=0
  3. 4 4 4 前面有 1 1 1 个比它小的数(即 2 2 2),贡献 1 × 1 ! = 1 1 \times 1! = 1 1×1!=1
  4. 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. 初始化一个可用元素列表 [ 1 , 2 , 3 , … , n ] [1,2,3,\dots,n] [1,2,3,,n]
  2. 逐步确定排列的每一位
    • 计算当前位属于哪个桶: rank / / ( n − 1 ) ! \text{rank} // (n-1)! rank//(n1)!
    • 确定该桶对应的元素,并移除该元素。
    • 计算 rank % ( n − 1 ) ! \text{rank} \% (n-1)! rank%(n1)!,继续下一位。

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. 应用场景

  1. 排列的唯一编码(用于哈希、压缩、存储)。
  2. 字典序排名计算(如求一个排列是第几小的全排列)。
  3. 求解全排列的第 k k k 个排列(如 LeetCode 60 题)。
  4. A 搜索中的状态压缩存储*(如 8 数码问题)。

6. 总结

  • 康托展开:将一个排列映射为一个整数,计算该排列在所有排列中的排名。
  • 逆康托展开:给定排名,恢复原排列。
  • 时间复杂度 O ( n 2 ) O(n^2) O(n2)(可优化至 O ( n log ⁡ n ) O(n \log n) O(nlogn))。
  • 主要用途:唯一编码、字典序排名、全排列查询。

这是一种非常实用的排列索引方法,在组合数学、搜索算法和数论中都有应用。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

SweetCode

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值