n数码问题,全排列的hash(转载
2012-10-15 21:07
183 查看
我们经常使用的数的进制为“常数进制”,即始终逢p进1。例如,p进制数K可表示为
K = a0*p^0 +a1*p^1 + a2*p^2 + ... + an*p^n (其中0 <= ai<= p-1),
它可以表示任何一个自然数。
对于这种常数进制表示法,以及各种进制之间的转换大家应该是很熟悉的了,但大家可能很少听说变进制数。这里我要介绍一种特殊的变进制数,它能够被用来实现全排列的Hash函数,并且该Hash函数能够实现完美的防碰撞和空间利用(不会发生碰撞,且所有空间被完全使用)。这种全排列Hash函数也被称为全排列数化技术。
变进制数:
我们考查这样一种变进制数:第1位逢2进1,第2位逢3进1,……,第n位逢n+1进1。它的表示形式为
K = a1*1! +a2*2! + a3*3! + ... + an*n! (其中0 <= ai<= i),
也可以扩展为如下形式(因为按定义a0始终为0),以与p进制表示相对应:
K = a0*0! +a1*1! + a2*2! + a3*3! + ... + an*n! (其中0 <= ai<= i)。
(后面的变进制数均指这种变进制数,且采用前一种表示法)
先让我们来考查一下该变进制数的进位是否正确。假设变进制数K的第i位ai为i+1,需要进位,而ai*i!=(i+1)*i!=1*(i+1)!,即正确的向高位进1。这说明该变进制数能够正确进位,从而是一种合法的计数方式。
n位变进制数K的性质:
(1)当所有位ai均为i时,此时K有最大值
MAX[K] =1*1! + 2*2! + 3*3! + ... + n*n!
=
1! + 1*1! + 2*2! + 3*3! + ... + n*n! - 1
=
(1+1)*1! + 2*2! + 3*3! + ... + n*n! - 1
=
2! + 2*2! + 3*3! + ... + n*n! - 1
=
...
=
(n+1)!-1
因此,n位K进制数的(9php.com)最大值为(n+1)!-1。
(2)当所有位ai均为0时,此时K有最小值0。
因此,n位变进制数能够表示0到(n+1)!-1的范围内的所有自然数,共(n+1)!个。
ps:全排列的Hash函数完美利用空间防碰撞的前提。
全排列的Hash函数:
在一些状态空间搜索算法中,我们需要快速判断某个状态是否已经出现,此时常常使用Hash函数来实现。其中,有一类特殊的状态空间,它们是由全排列产生的,比如N数码问题。对于n个元素的全排列,共产生n!个不同的排列或状态。下面将讨论如何使用这里的变进制数来实现一个针对全排列的Hash函数。
从数的角度来看,全排列和变进制数都用到了阶乘。如果我们能够用0到n!-1这n!个连续的变进制数来表示n个元素的所有排列,那么就能够把全排列完全地数化,建立起全排列和自然数之间一一对应的关系,也就实现了一个完美的Hash函数。那么,我们的想法能否实现呢?答案是肯定的,下面将进行讨论。
假设我们有b0,b1,b2...bn 共 n+1 个不同的元素,假设各元素之间有一种次序关系b0< b1 < b2 ...<bn。对它们进行全排列,共产生(n+1)!种不同的排列。对于产生的任一排列 c0,c1,c2,..,cn,其中第i个元素ci(1<= i <= n)与它前面的i个元素构成的逆序对的个数为di(0<= di <= i),那么我们得到一个逆序数序列d1,d2,...,dn(0
<= di<=i)。这不就是前面的n位变进制数的各个位么?于是,我们用n位变进制数M来表示该排列:
M = d1*1! + d2*2! + ... +dn*n!
因此,每个排列都可以按这种方式表示成一个n位变进制数。下面,我们来考查n位变进制数能否与n+1个元素的全排列建立起一一对应的关系。
由于n位变进制数能表示(n+1)!个不同的数,而n+1个元素的全排列刚好有(n+1)!个不同的排列,且每一个排列都已经能表示成一个n位变进制数。如果我们能够证明任意两个不同的排列产生两个不同的变进制数,那么我们就可以得出结论:
★ 定理1 n+1个元素的全排列的每一个排列对应着一个不同的n位变进制数。
对于全排列的任意两个不同的排列p0,p1,p2,...,pn(排列P)和q0,q1,q2,...,qn(排列Q),从后往前查找第一个不相同的元素,分别记为pi和qi(0< i <= n)。
(1)如果qi > pi,那么,
如果在排列Q中qi之前的元素x与qi构成逆序对,即有x > qi,则在排列P中pi之前也有相同元素x> pi(因为x > qi且qi >pi),即在排列P中pi之前的元素x也与pi构成逆序对,所以pi的逆序数大于等于qi的逆序数。又qi与pi在排列P中构成pi的逆序对,所以pi的逆序数大于qi的逆序数。
(2)同理,如果pi > qi,那么qi的逆序数大于pi的逆序数。
因此,由(1)和(2)知,排列P和排列Q对应的变进制数至少有第i位不相同,即全排列的(9php.com)任意两个不同的(9php.com)排列具有不同的变进制数。至此,定理1得证。
计算n个元素的一个排列的变进制数的算法大致如下(时间复杂度为O(n^2)):
template <typename T>
size_t PermutationToNumber(const T permutation[], int n)
{
//n不能太大,否则会溢出(如果size_t为32位,则n <=12)
size_tresult = 0;
for (int j =1; j < n; ++j) {
int
count = 0;
for
(int k = 0; k < j; ++k) {
if
(permutation[k] > permutation[j])
++count;
}
//
factorials[j]保存着j!
result
+= count * factorials[j];
}
returnresult;
}
说明:
(1)由于n!是一个很大的数,因此一般只能用于较小的n。
(2)有了计算排列的变进制数的算法,我们就可以使用一个大小为n!的数组来保存每一个排列的状态,使用排列的变进制数作为数组下标,从而实现状态的快速检索。如果只是标记状态是否出现,则可以用一位来标记状态。
K = a0*p^0 +a1*p^1 + a2*p^2 + ... + an*p^n (其中0 <= ai<= p-1),
它可以表示任何一个自然数。
对于这种常数进制表示法,以及各种进制之间的转换大家应该是很熟悉的了,但大家可能很少听说变进制数。这里我要介绍一种特殊的变进制数,它能够被用来实现全排列的Hash函数,并且该Hash函数能够实现完美的防碰撞和空间利用(不会发生碰撞,且所有空间被完全使用)。这种全排列Hash函数也被称为全排列数化技术。
变进制数:
我们考查这样一种变进制数:第1位逢2进1,第2位逢3进1,……,第n位逢n+1进1。它的表示形式为
K = a1*1! +a2*2! + a3*3! + ... + an*n! (其中0 <= ai<= i),
也可以扩展为如下形式(因为按定义a0始终为0),以与p进制表示相对应:
K = a0*0! +a1*1! + a2*2! + a3*3! + ... + an*n! (其中0 <= ai<= i)。
(后面的变进制数均指这种变进制数,且采用前一种表示法)
先让我们来考查一下该变进制数的进位是否正确。假设变进制数K的第i位ai为i+1,需要进位,而ai*i!=(i+1)*i!=1*(i+1)!,即正确的向高位进1。这说明该变进制数能够正确进位,从而是一种合法的计数方式。
n位变进制数K的性质:
(1)当所有位ai均为i时,此时K有最大值
MAX[K] =1*1! + 2*2! + 3*3! + ... + n*n!
=
1! + 1*1! + 2*2! + 3*3! + ... + n*n! - 1
=
(1+1)*1! + 2*2! + 3*3! + ... + n*n! - 1
=
2! + 2*2! + 3*3! + ... + n*n! - 1
=
...
=
(n+1)!-1
因此,n位K进制数的(9php.com)最大值为(n+1)!-1。
(2)当所有位ai均为0时,此时K有最小值0。
因此,n位变进制数能够表示0到(n+1)!-1的范围内的所有自然数,共(n+1)!个。
ps:全排列的Hash函数完美利用空间防碰撞的前提。
全排列的Hash函数:
在一些状态空间搜索算法中,我们需要快速判断某个状态是否已经出现,此时常常使用Hash函数来实现。其中,有一类特殊的状态空间,它们是由全排列产生的,比如N数码问题。对于n个元素的全排列,共产生n!个不同的排列或状态。下面将讨论如何使用这里的变进制数来实现一个针对全排列的Hash函数。
从数的角度来看,全排列和变进制数都用到了阶乘。如果我们能够用0到n!-1这n!个连续的变进制数来表示n个元素的所有排列,那么就能够把全排列完全地数化,建立起全排列和自然数之间一一对应的关系,也就实现了一个完美的Hash函数。那么,我们的想法能否实现呢?答案是肯定的,下面将进行讨论。
假设我们有b0,b1,b2...bn 共 n+1 个不同的元素,假设各元素之间有一种次序关系b0< b1 < b2 ...<bn。对它们进行全排列,共产生(n+1)!种不同的排列。对于产生的任一排列 c0,c1,c2,..,cn,其中第i个元素ci(1<= i <= n)与它前面的i个元素构成的逆序对的个数为di(0<= di <= i),那么我们得到一个逆序数序列d1,d2,...,dn(0
<= di<=i)。这不就是前面的n位变进制数的各个位么?于是,我们用n位变进制数M来表示该排列:
M = d1*1! + d2*2! + ... +dn*n!
因此,每个排列都可以按这种方式表示成一个n位变进制数。下面,我们来考查n位变进制数能否与n+1个元素的全排列建立起一一对应的关系。
由于n位变进制数能表示(n+1)!个不同的数,而n+1个元素的全排列刚好有(n+1)!个不同的排列,且每一个排列都已经能表示成一个n位变进制数。如果我们能够证明任意两个不同的排列产生两个不同的变进制数,那么我们就可以得出结论:
★ 定理1 n+1个元素的全排列的每一个排列对应着一个不同的n位变进制数。
对于全排列的任意两个不同的排列p0,p1,p2,...,pn(排列P)和q0,q1,q2,...,qn(排列Q),从后往前查找第一个不相同的元素,分别记为pi和qi(0< i <= n)。
(1)如果qi > pi,那么,
如果在排列Q中qi之前的元素x与qi构成逆序对,即有x > qi,则在排列P中pi之前也有相同元素x> pi(因为x > qi且qi >pi),即在排列P中pi之前的元素x也与pi构成逆序对,所以pi的逆序数大于等于qi的逆序数。又qi与pi在排列P中构成pi的逆序对,所以pi的逆序数大于qi的逆序数。
(2)同理,如果pi > qi,那么qi的逆序数大于pi的逆序数。
因此,由(1)和(2)知,排列P和排列Q对应的变进制数至少有第i位不相同,即全排列的(9php.com)任意两个不同的(9php.com)排列具有不同的变进制数。至此,定理1得证。
计算n个元素的一个排列的变进制数的算法大致如下(时间复杂度为O(n^2)):
template <typename T>
size_t PermutationToNumber(const T permutation[], int n)
{
//n不能太大,否则会溢出(如果size_t为32位,则n <=12)
size_tresult = 0;
for (int j =1; j < n; ++j) {
int
count = 0;
for
(int k = 0; k < j; ++k) {
if
(permutation[k] > permutation[j])
++count;
}
//
factorials[j]保存着j!
result
+= count * factorials[j];
}
returnresult;
}
说明:
(1)由于n!是一个很大的数,因此一般只能用于较小的n。
(2)有了计算排列的变进制数的算法,我们就可以使用一个大小为n!的数组来保存每一个排列的状态,使用排列的变进制数作为数组下标,从而实现状态的快速检索。如果只是标记状态是否出现,则可以用一位来标记状态。
相关文章推荐
- n数码问题,全排列的hash(转载)
- hash(1)——n数码问题——全排列hash
- poj 1077--Eight(八数码问题,BFS,A*,全排列的哈希)
- bfs+hash poj 1077/hdu 1043 八数码问题
- 八数码问题 BFS+hash
- 八数码问题,bfs,hash,康托
- hdu1043(八数码问题,广搜 + hash(实现状态压缩) )
- Hash Collision DoS 问题(转载)
- 关于八数码问题中的状态判重的三种解决方法(编码、hash、<set>)
- 吴昊品游戏核心算法 Round 17 ——(转载)八数码问题的十重境界
- 八数码问题(hash+bfs)
- Poj 1077 eight(BFS+全序列Hash解八数码问题)
- Java hashCode() 和 equals()的若干问题解答<转载自skywang12345>
- Poj 1077 eight(BFS+全序列Hash解八数码问题)
- 八数码问题(hash+折半搜索)
- 【转载】八数码问题BFS与DFS的比较
- <转载>字符串 全排列生成问题
- HDU-1043 Eight八数码 搜索问题(bfs+hash 打表 IDA* 等)
- 关于八数码问题中的状态判重的三种解决方法(编码、hash、<set>)
- 【转载】java continue和break的几个问题