← permutation · 把全部 n!个排列玩通透 / 康托展开:给每个排列一个整数编号 待审核 4 / 4
康托展开 · rank / unrank · 阶乘进制

康托展开:给每个排列一个整数编号

全部 n! 个排列按字典序排好后,每个都有一个唯一的名次 0n!1{0 \dots n!-1}康托展开 (Cantor expansion) 把这个名次和排列直接互算: rank 把排列映成序号,unrank 反向解码——无需像枚举那样一个个走。秘诀是把排列看成阶乘进制 (factorial number system) 下的数:第 i 位的权重(n1i)!(n-1-i)!,该位的数位是「右边还没用、且比当前数小的个数」。

本页单步演示 unrank: 拖动序号 k,看它如何逐位被解码成排列。

为什么权重是阶乘? 排列里第一位每变大一档(换成更大的可用数),就跨过了后面 (n1)!(n-1)! 个排列(因为后面 n1n-1 个位置能自由排出这么多种);第二位每变一档跨过 (n2)!(n-2)! 个……依此类推。把「每位的档数 × 该位权重」加起来,正好是这个排列前面有多少个排列——也就是它的字典序名次。

这是一个双射 (bijection)。 rank 与 unrank 互为逆:对任意排列 rankunrank 回到自身,对任意序号反过来也成立。于是排列可以只用一个整数来存储 / 传输 / 比较——需要「字典序第 k 个排列」时,一步 unrank(k) 即得,不必从最小一路 next 过去。

数位的取值范围是变的。i 位的数位 d 只能落在 0n1i{0 \dots n-1-i}——最后一位永远是 0(只剩一个候选)。这正是阶乘进制的特征:各位「逢 2,3,4,{2,3,4,\dots} 进一」,而非十进制的逢十。rank 方向则相反:数位 = 当前数右边比它小的个数,乘以权重再求和。

1 · 反过来:单步看 rank(排列 → 序号)

上面的 unrank 把序号解码成排列;rank 是它的逆——把排列编码回序号。逐位扫描:第 i 位的阶乘进制位 d = 它右边比它小的个数,乘以权重 (n1i)!(n-1-i)! 再累加。改下面的排列单步看每位怎么数、怎么累加。