八数码中的hash问题

转载改变自 


http://qfviolethill.blog.163.com/blog/static/114112168201002972344768/


八数码里对于我们这种用bfs的新手最头疼的就死活判重,现在我就见过两种方法,

1、和之前的数组比较。

2、化成字符串比较。

也许第二种确实聪明了那么一点,但是9!在时间上的开销可不是闹着玩的。


于是终于不费苦心在网上找到了一个极好的hash函数的思想;正文如下


-----------------------------------------------------------------------------------------------

看到8数码问题,首先想到的是搜索,由于问题是要求最优解的,所以我们选择搜索的方向应该是广度搜索。但随之而来的难题就出现了,广搜判重怎么处理呢?


一种很简单的思想是用for一遍以前搜过的状态。但是,八数码问题的状态共9!,显然for在时间上无法承受。


另外一种思想是把这9位数排成一列,转化成一个10进制的数进行hash,但空间上的问题有无法解决了。


 


显然,我们需要的是一个能够全排列的hash,而下面得这篇文章也就介绍了对于一个n<12的全排列状态,我们应该怎样完成hash判重


 


我们经常使用的数的进制为“常数进制”,即始终逢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进制数的最大值为(n+1)!-1。


(2)当所有位ai均为0时,此时K有最小值0。


因此,n位变进制数能够表示0到(n+1)!-1的范围内的所有自然数,共(n+1)!个。


在一些状态空间搜索算法中,我们需要快速判断某个状态是否已经出现,此时常常使用Hash函数来实现。其中,有一类特殊的状态空间,它们是由全排列产生的,比如N数码问题。对于n个元素的全排列,共产生n!个不同的排列或状态。下面将讨论如何使用这里的变进制数来实现一个针对全排列的Hash函数。


从数的角度来看,全排列和变进制数都用到了阶乘。如果我们能够用0到n!-1这n!个连续的变进制数来表示n个元素的所有排列,那么就能够把全排列完全地数化,建立起全排列和自然数之间一一对应的关系,也就实现了一个完美的Hash函数。那么,我们的想法能否实现呢?答案是肯定的,下面将进行讨论。


假设我们有b0,b1,b2,b3,...,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位不相同,即全排列的任意两个不同的排列具有不同的变进制数。至此,定理1得证。


 


 


 


 


我把上面的定理总结简化了一下,具体的内容是这样的


1、  计算出阶乘(factorial)1——n-1的数值,存放在数组factorial中


2、  对于一个随机得到的全排列a。从第二位开始(设为ai),计算在i之前有多少个比ai大的数(也就是a[i]和a[j]构成逆序对(j<i)),设为count个


3、  让hash值+count*factorial[i-1];


 


以下的伪代码给出了hash函数对一个全排列的过程


function gethash(全排列a):longint;


    begin


      gethash:=0;


      for i:=2 to n do   //注意,一定是第二位,因为从第二位开始才可能出现逆序对


        begin


          count:=0;


          for j:=1 to i-1 do  //计算逆序对的数目


            begin


              if a[i]<a[j] then inc(count);


            end;


          gethash:=gethash+count*factorial[i-1];


        end;


end;


算法的复杂度是n^2,但是由于这里的n非常小,一般只适用于(n<12),可视为在常数时间内完成hash。更何况,这个hash实现了空间上的最大节约,既不会出现冲突,同时一点也不浪费一点空间,因为这里n!个状态完完全全的能够表示n全排列的所有状态,达到了空间上的最大利用。


 


回到八数码问题,有了这样的一个强hash,我们要做的事情就只剩下简单的广度搜索而已了,为了使搜索起来更方便,可以把没有数的地方看成是0。这样,在移动的时候,只需要交换一下相邻的数就可以了。


   同时,我们可以把9个数字连成一排,这样也就方便了后面的hash。而上下移动也就变成了-3和+3的问题。值得注意的是:当我们把矩阵变成一维存储的时候,左右移动的时候需要防止第一行第三个移动到第二行第一个。


------------------------------------------------------------------------------------


mark自

2015/3/10

争取周3更以上

 


  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值