康托展开式[美味的全排列]

对于全排列问题,比如说 对于一个集合{a,b,c,d,e,f,g},所有可能的排列方式是{a,b,c,d,e,f,g},{a,b,c,d,e,g,f},{a,b,c,d,f,e,g},....,{g,f,e,d,c,b,a}

好吧,如果现在要写一个哈希映射关系表来实现对于每一种排列可能的映射,那么少不了需要设计一个哈希函数(以什么作为key,怎么尽可能减小空间浪费。。。。都是需要好好思考的问题)。

有趣的是,对于这样的全排列问题,康托展开法带来了一种几乎完美的函数映射关系(一一映射&&双射)表示模型,如下:

X=a[n]*(n-1)!+a[n-1]*(n-2)!+...+a[i]*(i-1)!+...+a[1]*0!

其中,a[i]为整数,并且0<=a[i]<i,1<=i<=n。

?

下面举个栗子来细细说明。

首先有一个标准原始集合,里面的每一个元素已按照约定好的大小比较关系排好序,比如{a,b,c,d,e,f,g}

现在有一个集合{a,b,f,e,d,g,c},需要算出它对应的key值X,步骤如下:(初始 X = 0)

(1)对于a,在除开a的未标记集合{b,f,e,d,g,c}中有0个小于a,故有X += 0*6!,然后将a设为已标记;

(2)对于b,在除开b的未标记集合{f,e,d,g,c}中有0个小于b,故有X += 0*5!,然后将b设为已标记;

(3)对于f,在除开f的未标记集合{e,d,g,c}中有3个小于f,故有X += 3*4!,然后将f设为已标记;

(4)对于e,在除开e的未标记集合{d,g,c}中有2个小于e,故有X += 2*3!,然后将e设为已标记;

(5)对于d,在除开d的未标记集合{g,c}中有1个小于d,故有X += 1*2!,然后将d设为已标记;

(6)对于g,在除开g的未标记集合{c}中有1个小于g,故有X += 1*1!,然后将g设为已标记;

(7)对于c,在除开c的未标记集合{}中有0个小于c,故有X += 0*0!,然后将c设为已标记;

(8)未标记集合为空,得X =?0*6!+?0*5!+?3*4!+?2*3!+?1*2!+?1*1!+?0*0!= 87,则集合序列{a,b,f,e,d,g,c}对应的key为87。

[可以敏感地注意到阶乘的分级作用,就好像一个两位的编码,十位与个位隔绝开来,未标记集合内有k个元素则相应阶乘为k!(0*k! ~ k*k!),这显然与接下来的k-1和(k-1)!(0*(k-1)! ~ (k-1)*(k-1)!)隔绝开, 隔绝作用保证每一种状态都是独一无二的]

?

注意,这里的全排列哈希映射是双射,因此对于一种特定的序列有且仅有一个key,因此通过这唯一的key可以逆推得到唯一的序列情况,下面是逆推举例:

已知原始集合{a,b,c,d,e,f,g}和key = 87,逐一求出各个阶乘系数:

(1) 87 / 6! = 0 ...... 87 对于当前未标记集合{a,b,c,d,e,f,g},有0个比自身小的元素的元素是a,因此目标序列集合{a}, a设为已标记;

(2) 87 / 5! = 0 ...... 87?对于当前未标记集合{b,c,d,e,f,g},有0个比自身小的元素的元素是b,因此目标序列集合{a,b}, b设为已标记;

(3) 87 / 4! = 3 ...... 15?对于当前未标记集合{c,d,e,f,g},有3个比自身小的元素的元素是f,因此目标序列集合{a,b,f}, f设为已标记;

(4) 15 / 3! = 2 ...... 3?对于当前未标记集合{c,d,e,g},有2个比自身小的元素的元素是e,因此目标序列集合{a,b,f,e}, e设为已标记;

(5) 3 / 2! = 1 ...... 1?对于当前未标记集合{c,d,g},有1个比自身小的元素的元素是d,因此目标序列集合{a,b,f,e,d}, d设为已标记;

(6) 1 / 1! = 1 ...... 0?对于当前未标记集合{c,g},有1个比自身小的元素的元素是g,因此目标序列集合{a,b,f,e,d,g}, g设为已标记;

(7) 0 / 0! = 0 ...... 0?对于当前未标记集合{c},有0个比自身小的元素的元素是c,因此目标序列集合{a,b,f,e,d,g,c}, c设为已标记;

(8) 未标记集合为空,结束,获得 key = 87 所对应的目标序列{a,b,f,e,d,g,c}。

可以得知表达式为?X =?0*6!+?0*5!+?3*4!+?2*3!+?1*2!+?1*1!+?0*0!= 87 与?{a,b,f,e,d,g,c} 相互一一对应的

?

总结一下:

  总是从高阶的阶乘开始计算(例子:6! -> 5! ->...->0!),才能达成“隔绝”条件,因为在最开始的未标记元素最多因此能与目标数构成某逻辑关系的元素数目也可能最多(0~6)*6!,如果反过来会出现(0~6)*0!+(0~5)*1!+.....+(0~0)*6!,存在像3*0! == 3*1!这样的越界情形

  在已获知全部元素和排序规则的前提下,运用康托展开式可以构建一一映射的哈希函数,并且得到的 key-value 对拥有很好的连续性(比如说将一种序列作为key来求得相应的value,那么刚才的例子的{a,b,f,e,d,g,c}对应的value是87,原始集合{a,b,c,d,e,f,g}所有的序列{a,b,c,d,e,f,g}....{g,f,e,d,c,b,a}所对应的值为别为0....5039,其中7!= 5040,{a,b,c,d,e,f,g}的size为7)

  即使考虑最糟糕的一种状态:6*6!+5*5!+4*4!+3*3!+2*2!+1*1!+0*0! = 5039,哈哈是不是正好保障了下限呢

  紧凑的双射集将可能的浪费降到了最低,对于上面的例子,开一个array[5040]的数组就刚好够了,不必担心存在大于5039的value,自然不必将数组开更大,导致内部可能的空间浪费(对于不那么好的哈希函数,可能将5040个点射到0~10000的范围内,如果开一个array[10000]的数组虽然可以满足要求,但是必然浪费其中的4960个单元!!!)

  以上便是哈希映射空间压缩的神器----康托展开式 ^。。^

?

时间: 2024-08-06 19:59:42

康托展开式[美味的全排列]的相关文章

全排列编码于解码-康托展开式

在一些问题中需要对全排列状态进行编码,如果使用完美hash来实现的话需要编码数量在计算机承受范围内,康托展开式符合条件 公式:X=An*(n-1)!+An-1*(n-2)!+......A2*1!+A1*0! 其中An等于在剩余元素中当前元素第几大(从0开始).比如:1 4 2 6 5 3 7 8 9 1在剩下的1 4 2 6 5 3 7 8 9中为第0大 4在剩下的4 2 6 5 3 7 8 9中为第2大 2在剩下的2 6 5 3 7 8 9中为第0大 .......... 8在8 9中为第0

NYOJ 139 我排第几个 康托展开式

题目链接:http://acm.nyist.net/JudgeOnline/problem.php?pid=139 思路:康托展开式的典型应用,康托展开式是什么呢,举个例子.1,2,3这三个数的全排列共有六种,那么按照字典的顺序,『3,2,1』 这个序列是在第几个呢.康托是这样想的:   首先从第一位3开始看,比3小的有2,1,那么也就是说如果第一位是1或者2都在这个序列的前面,那么以1或者2开头的序列有多少呢,答案为2*2!.为什么是这个答案呢?因为符合 要求的第一位共有1和2两个数,然后第二

康托展开:对全排列的HASH和还原,判断搜索中的某个排列是否出现过

题目:http://acm.hrbust.edu.cn/index.php?m=ProblemSet&a=showProblem&problem_id=2297 前置技能:(千万注意是从0开始数的 康托展开表示的是当前排列在n个不同元素的全排列中的名次.比如213在这3个数所有排列中排第3. 那么,对于n个数的排列,康托展开为: 其中表示第i个元素在未出现的元素中排列第几.举个简单的例子: 对于排列4213来说,4在4213中排第3,注意从0开始,2在213中排第1,1在13中排第0,3在

康托展开-全排列应用

我排第几个 时间限制:1000 ms  |  内存限制:65535 KB 难度:3 描述 现在有"abcdefghijkl"12个字符,将其所有的排列中按字典序排列,给出任意一种排列,说出这个排列在所有的排列中是第几小的? 输入 第一行有一个整数n(0<n<=10000);随后有n行,每行是一个排列: 输出 输出一个整数m,占一行,m表示排列是第几位: 样例输入 3 abcdefghijkl hgebkflacdji gfkedhjblcia 样例输出 1 30271524

Aizu 0121 Seven Puzzle (康托展开+bfs)

Seven Puzzle Time Limit : 1 sec, Memory Limit : 65536 KB 7パズルは8つの正方形のカードとこれらのカードがぴたりと収まる枠を使って行います.それぞれのカードは互いに区別できるように.0,1,2....7と番号がつけられています.枠には.縦に2個.横に4個のカードを並べることができます. 7パズルを始めるときには.まず枠にすべてのカードを入れます.枠のなかで0のカードだけは.上下左右に隣接するカードと位置を交換することができます.たとえば.枠

康托展开 / 逆康托展开

先搬一下(戳)维基百科的康托展开(戳): 康托展开是一个全排列到一个自然数的双射,常用于构建哈希表时的空间压缩. 康托展开的实质是计算当前排列在所有由小到大全排列中的顺序,因此是可逆的. 由于是双射    所以可以求n的全排列里第k大的排列(逆康托展开) (伪)计算原理: 从某个元素找后面比这个元素小的数的个数,再乘以这个位置每一个数字能有的组合方法数(排列 / 阶乘),得出只考虑从这一位开始到末尾比当前小的排列数,然后加起来就是康托展开求的数(追求难懂的巅峰...........看不懂就看看维

nyoj 139 我排第几个(康托展开)

康托展开 康托展开的公式是 X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0! 其中,ai为当前未出现的元素中是排在第几个(从0开始). 这个公式可能看着让人头大,最好举个例子来说明一下.例如,有一个数组 s = ["A", "B", "C", "D"],它的一个排列 s1 = ["D", "B", "A", &

康托展开、康托逆展开原理

康托展开 康托展开的公式是 X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0! 其中,ai为当前未出现的元素中是排在第几个(从0开始). 这个公式可能看着让人头大,最好举个例子来说明一下.例如,有一个数组 s = ["A", "B", "C", "D"],它的一个排列 s1 = ["D", "B", "A", &

康托展开及其逆运算 详解

前文: 这个东东是我准备进攻一道A*算法的八数码题目时,遇到的. 决定先搞懂这个,再进攻八数码(传说中  不做人生不完整的 题目). 康托展开是什么? 定义: X=an*(n-1)!+an-1*(n-2)!+...+ai*(i-1)!+...+a2*1!+a1*0! ai为整数,并且0<=ai<i(1<=i<=n) 简单点说就是,判断这个数在其各个数字全排列中从小到大排第几位. 比如 132,在1.2.3的全排列中排第2位. 康托展开有啥用呢? 维基:n位(0~n-1)全排列后,其