⭐ 欢迎来到虫虫下载站! | 📦 资源下载 📁 资源专辑 ℹ️ 关于我们
⭐ 虫虫下载站

📄 huarongdaojavashixian.txt

📁 华容道的java实现
💻 TXT
📖 第 1 页 / 共 3 页
字号:

//---------------------------------------------------------------------------

//===============================================

//===============================================

 

 

 

七、利用哈希技术

用棋盘折叠方法计算哈希值

棋盘可看做5个int32,分别对它移位并求和,取得哈希值,移位应适当,充分利用各子,增强散列

让hash冲突变为零的要点: 

1.利用盘面生成一个足够乱的数,你可能考虑各种各样的杂凑算法(类似MD5的功能) 

折叠计算hash时,注意各子的值尽量少被直相加(异或等),折叠计算时通过适当移位后再相加,移位的作用是各子的数值部分尽量少重叠在一起,充分利用各子数值,产生足够的散列度. 

2.利用随机函数(rand)来产生,当然随机数应与盘面产生关联,每一盘面对应的随机数(一个或一组)应是唯一的。 

3.哈希表的大小应是表中记录的节点总数的4倍以上. 

4.设哈希表为int hsb[128K+10],总节点数为m=30K 

某一盘面节点的哈希值为n,n是17位的,那么n在hash表中位置为hsb[n], 

hsb[n]里存放的是这个节点的另一个32位的哈希值,用于判断哈希冲突. 

当出现冲突时,n在表中的位置改为n+1,这样可充分利用哈希表,节约空间 

经过这样处理后,哈希冲突次数约为: 

第一次哈希突次数:(1+2+...+30)/128=(n^2)/2/128=3.5k 

第二次哈希突次数:(1*1+2*2+...+30*30)/(128*128)=(n^3)/3/128/128=0.55K 

接下来我们再建第二个15位哈希表hsb2[32k] 

同样上述处理 

第三次哈希突次数:(1+2+...+0.55k)/32=(n^2)/2/32k=0.005k=5次 

第四次哈希突次数:(1*1+2*2+...+0.55*0.55k)/(32k*32k)=0次 

以上分析是在哈希值n的散列度理想情况下的结果,如果你的n的散列度不是很理想,冲突次数可乘上2,即: 

第一次:3.5k*2=7k 

第二次:0.55k*2=1.1 

第三次:[(1+2+...+1.1k)/32]*2=40次 

第四次:[(1*1+2*2+...+1.1*1.1k)/(32*32k)]*2=0.88次(约1次)

//===============================================

//===============================================

//===============================================

//下文是利用哈希技术优化后的代码

//===============================================

 

//---------------------------------------------------------------------------

//----本程序在C++Builder6.0及VC++6.0中调试通过----

//----程序名称:"华容道之哈希算法"搜索----

//----程序设计:许剑伟----

//----最后修改时间:2006.10.22----

//----速度:横刀立马12毫秒(P2.4G机器)

//---------------------------------------------------------------------------

#include <time.h>

#include <stdio.h>

#include <conio.h>

#include <stdlib.h>

//---------------------------------------------------------------------------

//--棋盘定义说明及搜索过程使用的核心数据结构--

//---------------------------------------------------------------------------

//棋盘表示使用char一维数组,例:char q[20];

//大王是5(大王只能1个),横将是4,竖将是3,兵是2,空位用0表示

//大王与横将前两个须相同,余占位填充1,竖将第二个占位同样填充1

//棋盘上只能为2个空格,不能多也不能少

//---------------------------------------------------------------------------

const COL[20]={0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3,0,1,2,3};//列号表

struct PMZB{       //盘面走步集结构

  char s[10],d[10];//原位置,目标位置,最多只会有10步

  int n;           //总步数

};

typedef char QP[20];

//---------------------------------------------------------------------------

//--以下定义几函数--

//---------------------------------------------------------------------------

//以下define用于棋盘复制,不要用环循,实地址直接引用要快8倍

#define qpcpy(q,p) {int *a=(int*)q,*b=(int*)p;a[0]=b[0],a[1]=b[1],a[2]=b[2],a[3]=b[3],a[4]=b[4];}/*复制棋盘*/

void qkmem(void *ps,int n){ //内存块置0,同memset(mem,0,memsize);

  register int *p=(int*)ps,*p2=p+n/4;

  while(p<p2) *p++=0;

  char *p3=(char *)p,*p4=(char *)ps+n;

  while(p3<p4) *p3++=0;

}

//---------------------------------------------------------------------------

//--以下是搜索算法之一(解决哈希表问题)--

//---------------------------------------------------------------------------

#define hsize 128*1024//使用128k(17位)哈希表,如果改用更大的表,相应的哈希计算位数也要改

//以下这两个哈希计算是对棋盘折叠计算,注意异或与加法相似,不会提高散列度,适当的移位则会提高散列度

#define hs17(h1,h) h=(h1&0x0001FFFF)^(h1>>17) //17位哈希值计算(折叠式计算)

#define hs15(h1,h) h=(h1&0x00007FFF)^(h1>>19) //15位哈希值计算(折叠式计算)

#define phs(h1,h,b){if(!b[h]){b[h]=h1;return 1;} if(b[h]==h1)return 0;h++;} //哈希值测试,返回1是新节点

class PmHx{ //盘面哈希计算

 public:

 unsigned int *hsb,*hsb2; //哈希表

 int cht; //哈希冲突次数

 PmHx(){//初始化编码表

   int i;

   hsb=new unsigned int[hsize+hsize/4+64];hsb2=hsb+hsize+32; //第二哈希表大小为第一哈希表的1/4

   reset();

 }

 ~PmHx(){ delete[] hsb; }

 void reset(){ cht=0; qkmem(hsb,(hsize+hsize/4+64)*sizeof(unsigned int));}

 int check(char *q){ //盘面编码

   //生成散列参数n1,n2,m0

   //以下参数生成算法不保证参数与棋盘的唯一对应关系,因此理论上存在哈希表冲突判断错误的可能

   //只不过产生错误的可能性几乎可能完全忽略

   unsigned int i,n1,n2,m0,h,h1,*p=(unsigned int*)q;

   n1=(p[1]<<3)+(p[2]<<5)+p[0]; //每次折叠时都应充分发挥各子作用,增强散列

   n2=(p[3]<<1)+(p[4]<<4);

   m0=(n2<<6)^(n1<<3); //增强散列参数

   int a=1;

   //第一哈希处理

   h1=n1+n2+m0; hs17(h1,h);//h1为散列和,h为第一哈希索引

   for(i=0;i<2;i++) phs(h1,h,hsb); //多次查表,最多32次

   //第二哈希处理

   h1=n1-n2+m0; hs15(h1,h);//h1为散列差,h为第二哈希值

   for(i=0;i<10;i++) phs(h1,h,hsb2); //首次查表

   cht++; //哈希冲突计数(通过5次哈希,一般情况下冲突次数为0)

   return 1;

 }

 void check2(char *q){ //按左右对称规则考查棋盘,并记录到哈希表

   char i,q2[20];

   for(i=0;i<20;i+=4) q2[i]=q[i+3],q2[i+1]=q[i+2],q2[i+2]=q[i+1],q2[i+3]=q[i];

   check(q2);

   //check2()执行次数较少,是实节点的次数,约为check()执行次数的1/3,所以不必过份求其速度

 }

}; //建立哈希表索引器

//---------------------------------------------------------------------------

//以下设计走法生成器

//---------------------------------------------------------------------------

#define zin0(des) z->s[z->n]=i,z->d[z->n++]=des //保存步法宏

#define zin1(des) z->s[z->n]=i-1,z->d[z->n++]=des-1 //保存步法宏(左移1列)

#define zin4(des) z->s[z->n]=i-4,z->d[z->n++]=des-4 //保存步法宏(上移1行)

#define zinb(des,fx) i=des+(fx); if(q[i]==2) {if(h){zin0(k1);zin0(k2);}else zin0(des);}

void zbFX(char *q,PMZB *z){ //分析当前可能的步法,并将所有可能的步法保存在z中

  int i,k1=-1,k2,h=0;z->n=0;  //k1空格1位置,k2空格2位置,h为两空格的联合类型,计步复位

  for(i=0;i<20;i++){ if(!q[i]){if(k1==-1) k1=i; else { k2=i; break; }} } //查空格的位置

  int col1=COL[k1],col2=COL[k2];

  if(k1+4==k2) h=1;         //空格竖联合

  if(k1+1==k2&&col1<3) h=2; //空格横联合

  if(col1>0){zinb(k1,-1);

    if(q[i]==3) {if(h==1) zin0(k1);}

    if(q[i]==5) {if(h==1) zin1(k1);}

    if(q[i]==4) {if(h==2) zin1(k2); zin1(k1);}

  }

  if(col1<3){zinb(k1,1);

    if(q[i]==3) {if(h==1) zin0(k1);}

    if(q[i]==5) {if(h==1) zin0(k1);}

    if(q[i]==4) {zin0(k1);} //如果横联合,k1不是第一空,所以不用判断h

  }

  if(k1>3){zinb(k1,-4);

    if(q[i]==4&&q[i+1]==4&&(col1!=1||q[i-1]!=4)){ if(h==2) zin0(k1); }

    if(q[i]==1){

      if(q[i-4]==3) {if(h==1) zin4(k2); zin4(k1);}

      if(q[i-4]==5&&q[i-3]==5){if(h==2) zin4(k1);}

    }

  }

  if(k1<16){zinb(k1,4);

    if(q[i]==3) zin0(k1);

    if(q[i]==4&&q[i+1]==4&&(col1!=1||q[i-1]!=4)){ if(h==2) zin0(k1); }

    if(q[i]==5&&q[i+1]==5){ if(h==2) zin0(k1); }

  }

  if(col2>0){zinb(k2,-1); if(q[i]==4) zin1(k2); }

  if(k2>3)  {zinb(k2,-4); if(q[i]==1&&q[i-4]==3)zin4(k2);}

  if(col2<3){zinb(k2,1);  if(q[i]==4) {if(h==2) zin0(k1); zin0(k2);}}

  if(k2<16) {zinb(k2,4);  if(q[i]==3) {if(h==1) zin0(k1); zin0(k2);}}

}

//---------------------------------------------------------------------------

//--以下是搜索过程(广度优先)--

//---------------------------------------------------------------------------

class ZBD{ //走步队(搜索器)

 public:

 QP *z;       //棋盘队列

 int dn,dm,cur;//队(队尾),队头及队头内走步集游标

 PMZB zbj;    //队头走步集

 int *hs;     //回溯用的指针,指向父亲(对应的父亲)

 char*hss;    //对应的源步

 int max;     //最大队长

 int *res,ren;//结果

 int js,js2;  //搜索情况计数

 PmHx Hx;     //希希处理类

 ZBD(int k){ z=new QP[k]; hs=new int[k*2+500]; res=hs+k; hss=new char[k]; max=k; reset(); }

 ~ZBD(){ delete[] z; delete[] hs; delete[] hss; }

 void reset() { dn=0; dm=0,cur=-1; ren=0; js=js2=0; hss[dn]=-1;}

 void zb(char *q,char s,char d){ //走一步函数

   char c=q[s];q[s]=0;

   switch(c){

    case 3: q[s+4]=0; q[d+4]=1; break; //竖,余位填充1

    case 4: q[s+1]=0; q[d+1]=c; break; //横

    case 5: q[s+1]=q[s+4]=q[s+5]=0; q[d+1]=c,q[d+4]=q[d+5]=1; break;

   }q[d]=c;

 }

 int zbcd(char *q){ //走步出队

   if(cur==-1) zbFX(z[dm],&zbj);

   cur++; if(cur>=zbj.n) {dm++,cur=-1; return 1;} //步集游标控制

   if(hss[dm]==zbj.s[cur]) return 1;//和上次移动同一个棋子时不搜索,可提速20%左右

   qpcpy(q,z[dm]); zb(q,zbj.s[cur],zbj.d[cur]); //走步后产生新节点,结果放在q中(出队)

   return 0;

 }

 void zbrd(char *q){ //走步入队

   if(dn>=max) { printf("队溢出.\r\n"); return; }

   qpcpy(z[dn],q); //出队

   if(cur>=0) hss[dn]=zbj.d[cur]; //记录下移动的目标位置(用于剪枝)

   hs[dn++]=dm; //记录回溯点

 }

 char* getre(int n){ return z[res[n]];} //取第n步盘面

 int bfs(char *qp,int dep,int all=0){ //广度优先搜索,参数为棋盘及搜索最大深度,all为穷举节点总数

  if(dep>500||dep<=0) dep=200;

  reset(); Hx.reset(); //哈希表及队复位

  char q[20]; qpcpy(q,qp);

  int i,k;

  for(zbrd(q),i=1;i<=dep;i++){ //一层一层的搜索

    k=dn; if(dm==k) return -1;

    if(all)printf("第%d层共%d节点\r\n",i-1,k-dm);

    while(dm<k){ //广度优先

      if(zbcd(q)) continue;     //返回1说明是步集出队,不是步出队

      js++; //遍历总次数计数

      if(q[13]==5&&q[14]==5&&!all) { //大王出来了

        for(ren=i,k=ren-2,res[ren-1]=dm;k>=0;k--) res[k]=hs[res[k+1]]; //回溯

        return 1;

      }

      if(i<dep&&Hx.check(q)){ //到了最后一层可以不再入队了

        js2++;       //js2遍历的实结点个数,js2不包括起始点,出队时阻止了回头步,始节点不可能再遍历

        Hx.check2(q);//对称节点做哈希处理

        zbrd(q);

      }

    }

  }

  return 0;

 }

}S(40*1024); //建立搜索引擎

//---------------------------------------------------------------------------

//----输入输出部分----

//---------------------------------------------------------------------------

void prt(char *q){ //打印棋盘

  int i,j,k;

  char y[20],x[20],xy[20],p[20],c1,c2;

  for(i=0;i<20;i++){

    y[i]='|',x[i]='-',xy[i]='+';

    if(q[i]) p[i]=q[i]+48; else p[i]=' ';

    if(q[i]==1) p[i]=p[i-4];

  }

  for(i=0;i<20;i++){

    if(q[i]==0) {if(COL[i]<3&&q[i+1]==0) y[i]=' '; if(i<16&&q[i+4]==0) x[i]=' ';}

    if(q[i]==3) { x[i]='.'; }

    if(q[i]==4) { y[i]='.'; i++; }

    if(q[i]==5) { y[i]=' '; y[i+4]=' '; x[i]=' '; x[i+1]=' '; xy[i]='.'; i++; }

  }

  printf("+-+-+-+-+\r\n");

  for(i=0;i<5;i++){

    k=i*4;

    printf("|");

    for(j=0;j<4;j++){ printf("%c%c",p[k+j],y[k+j]); }

    printf("\r\n|");

    for(j=0;j<4;j++){ printf("%c%c",x[k+j],xy[k+j]); }

⌨️ 快捷键说明

复制代码 Ctrl + C
搜索代码 Ctrl + F
全屏模式 F11
切换主题 Ctrl + Shift + D
显示快捷键 ?
增大字号 Ctrl + =
减小字号 Ctrl + -