标签:
对于全排列问题,比如说 对于一个集合{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个单元!!!)
以上便是哈希映射空间压缩的神器----康托展开式 ^。。^
?
标签:
原文地址:http://www.cnblogs.com/gankai-7/p/4216813.html