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

📄 基本搜索方法——置换表.htm

📁 象棋程序设计全资料集(介绍编写象棋程序的方法思路)
💻 HTM
📖 第 1 页 / 共 2 页
字号:
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0 Transitional//EN">
<!-- saved from url=(0055)http://www.elephantbase.net/computer/search_hashing.htm -->
<HTML><HEAD><TITLE>基本搜索方法——置换表</TITLE>
<META http-equiv=Content-Type content="text/html; charset=gb_2312-80">
<META content="MSHTML 6.00.3790.2817" name=GENERATOR></HEAD>
<BODY background=基本搜索方法——置换表_files/background.gif>
<DL dir=ltr>
  <DIV align=center>
  <CENTER>
  <DT>《对弈程序基本技术》专题 </CENTER></DT></DIV>
  <DIV align=center>
  <CENTER>
  <DT>  </CENTER></DT></DIV>
  <DIV align=center>
  <CENTER>
  <DT><FONT face=隶书 size=6>置换表</FONT> </CENTER></DT></DIV>
  <DIV align=center>
  <CENTER>
  <DT>  </CENTER></DT></DIV>
  <DIV align=center>
  <CENTER>
  <DT><FONT face="Times New Roman">Bruce Moreland (</FONT><A 
  href="mailto:brucemo@seanet.com"><FONT 
  face="Times New Roman">brucemo@seanet.com</FONT></A><FONT 
  face="Times New Roman">) / </FONT>文 </CENTER></DT></DIV>
  <DT>  
  <DT><FONT face=楷体_GB2312 size=5><STRONG>一个多功能的数据结构</STRONG></FONT> 
  <DT>  
  <DT>  国际象棋的搜索树可以用图来表示,而置换结点可以引向以前搜索过的子树上。置换表可以用来检测这种情况,从而避免重复劳动。如果“<FONT 
  face="Times New Roman">1. e4 d6 2. d4</FONT>”以后的局面已经搜索过了,那就没有必要再搜索“<FONT 
  face="Times New Roman">1. d4 d6 2. e4</FONT>”以后的局面了。 
  <DT>  这个原因可能鼓舞着早期的电脑国际象棋程序的设计师们,而现在事实上这还是置换表的次要用途。在某些局面,例如在没有通路兵的王兵残局中,检查到的置换的数量是惊人的,以至于搜索可以在短达时间内达到很深的深度。 

  <DT>  省去重复的工作,这是置换表的一大特色,但是在一般的中局局面里,置换表的另一个作用更为重要。每个散列项里都有局面中最好的着法,我在“<A 
  href="http://www.elephantbase.net/computer/search_iterative.htm" 
  target=_blank>迭代加深</A>”这一章里解释过,首先搜索好的着法可以大幅度提高搜索效率。因此如果你在散列项里找到最好的着法,那么你首先搜索这个着法,这样会改进你的着法顺序,减少分枝因子,从而在短的时间内搜索得更深。 

  <DT>  
  <DT><FONT face=楷体_GB2312 size=5><STRONG>实现</STRONG></FONT> 
  <DT>  
  <DT>  主置换表是一个散列数组,每个散列项看上去像这样: 
  <DD>  
  <DD>#define hashfEXACT 0 
  <DD>#define hashfALPHA 1 
  <DD>#define hashfBETA 2 
  <DD>typedef struct tagHASHE { 
  <DD> U64 key; 
  <DD> int depth; 
  <DD> int flags; 
  <DD> int value; 
  <DD> MOVE best; 
  <DD>} HASHE; 
  <DD>  
  <DT>  这个散列数组是以“<A 
  href="http://www.elephantbase.net/computer/struct_zobrist.htm" 
  target=_blank><FONT 
  face="Times New Roman">Zobrist</FONT>键值</A>”为指标的。你求得局面的键值,除以散列表的项数得到余数,这个散列项就代表该局面。由于很多局面都有可能跟散列表中同一项作用,因此散列项需要包含一个校验值,它可以用来确认该项就是你要找的。通常校验值是一个<FONT 
  face="Times New Roman">64</FONT>位的数,也就是上面那个例子的第一个域。 
  <DT>  你从搜索中得到结果后,要保存到散列表中。如果你打算用散列表来避免重复工作,那么重要的是记住搜索有多深。如果你在一个结点上搜索了<FONT 
  face="Times New Roman">3</FONT>层,后来又打算做<FONT 
  face="Times New Roman">10</FONT>层搜索,你就不能认为散列项里的信息是准确的。因此子树的搜索深度也要记录。 
  <DT>  在<FONT face="Times New Roman">Alpha-Beta</FONT>搜索中,你很少能得到搜索结点的准确值。<FONT 
  face="Times New Roman">Alpha</FONT>和<FONT 
  face="Times New Roman">Beta</FONT>的存在有助你裁剪掉没有用的子树,但是用<FONT 
  face="Times New Roman">Alpha-Beta</FONT>有个小的缺点,你通常不会知道一个结点到底有多坏或者有多好,你只是知道它足够坏或足够好,从而不需要浪费更多的时间。 

  <DT>  当然,这就引发了一个问题,散列项里到底要保存什么值,并且当你要获取它时怎样来做。答案是储存一个值,另加一个标志来说明这个值是什么含义。在我上面的例子中,比方说你在评价域中保存了<FONT 
  face="Times New Roman">16</FONT>,并且在标志域保存了“<FONT 
  face="Times New Roman">hashfEXACT</FONT>”,这就意味着该结点的评价是准确值<FONT 
  face="Times New Roman">16</FONT>;如果你在标志域中保存了“<FONT 
  face="Times New Roman">hashfALPHA</FONT>”,那么结点的值最多是<FONT 
  face="Times New Roman">16</FONT>;如果保存了“<FONT 
  face="Times New Roman">hashfBETA</FONT>”,这个值就至少是<FONT 
  face="Times New Roman">16</FONT>。 
  <DT>  当你在搜索中遇到特定情况时,很容易决定评价和标志应该保存哪些内容。然而避免错误是非常重要的,散列表是非常容易犯错误的,而且一旦犯下错误就很难捕捉出来。 

  <DT>  我的散列项的最后一个域,保存着上次搜索到这个局面时的最佳着法。有时我没有得到最佳着法,比如任何低出边界的情况<FONT 
  face="Times New Roman">(</FONT>返回一个小于或等于<FONT 
  face="Times New Roman">Alpha</FONT>的值<FONT 
  face="Times New Roman">)</FONT>,而其他情况必定有最佳着法,比如高出边界的情况<FONT 
  face="Times New Roman">(</FONT>返回一个大于或等于<FONT 
  face="Times New Roman">Beta</FONT>的值<FONT 
  face="Times New Roman">)</FONT>。<FONT 
  color=#0000ff>【译注:只有叶子结点才没有最佳着法,即便是</FONT><FONT face="Times New Roman" 
  color=#0000ff>Alpha</FONT><FONT 
  color=#0000ff>结点,所有的着法都是差的,也应该从中找一个最好的着法,它对更深一层的搜索会带来很大的好处。】</FONT> 
  <DT>  如果找到最佳着法,那么它应该首先被搜索。 
  <DT>  下面是示范程序,是根据<FONT 
  face="Times New Roman">Alpha-Beta</FONT>函数修改的,改动的地方用醒目的字标出: 
  <DT>  
  <DD>int AlphaBeta(int depth, int alpha, int beta) { 
  <DD><FONT color=#ff0000> int hashf = hashfALPHA;</FONT> 
  <DD><FONT color=#ff0000> if ((val = ProbeHash(depth, alpha, beta)) != 
  valUNKNOWN) {</FONT> 
  <DD><FONT color=#ff0000>  // </FONT><FONT 
  color=#0000ff>【valUNKNOWN必须小于-INFINITY或大于INFINITY,否则会跟评价值混淆。】</FONT> 
  <DD>  return val; 
  <DD> } 
  <DD> if (depth == 0) { 
  <DD>  val = Evaluate(); 
  <DD><FONT color=#ff0000>  RecordHash(depth, val, hashfEXACT);</FONT> 
  <DD>  return val; 
  <DD> } 
  <DD> GenerateLegalMoves(); 
  <DD> while (MovesLeft()) { 
  <DD>  MakeNextMove(); 
  <DD>  val = -AlphaBeta(depth - 1, -beta, -alpha); 
  <DD>  UnmakeMove(); 
  <DD>  if (val &gt;= beta) { 
  <DD><FONT color=#ff0000>   RecordHash(depth, beta, hashfBETA);</FONT> 
  <DD>   return beta; 
  <DD>  } 
  <DD>  if (val &gt; alpha) { 
  <DD><FONT color=#ff0000>   hashf = hashfEXACT;</FONT> 
  <DD>   alpha = val; 
  <DD>  } 
  <DD> } 
  <DD><FONT color=#ff0000> RecordHash(depth, alpha, hashf);</FONT> 
  <DD> return alpha; 
  <DD>} 
  <DT>  
  <DT>  以下就是两个新的函数的代码: 
  <DD>  
  <DD>int ProbeHash(int depth, int alpha, int beta) { 
  <DD> HASHE *phashe = &amp;hash_table[ZobristKey() % TableSize()]; 
  <DD> if (phashe-&gt;key == ZobristKey()) { 
  <DD>  if (phashe-&gt;depth &gt;= depth) { 
  <DD>   if (phashe-&gt;flags == hashfEXACT) { 
  <DD>    return phashe-&gt;val; 
  <DD>   } 
  <DD>   if ((phashe-&gt;flags == hashfALPHA) &amp;&amp; (phashe-&gt;val &lt;= 
  alpha)) { 
  <DD>    return alpha; 
  <DD>   } 
  <DD>   if ((phashe-&gt;flags == hashfBETA) &amp;&amp; (phashe-&gt;val &gt;= 
  beta)) { 
  <DD>    return beta; 
  <DD>   } 
  <DD>  } 
  <DD>  RememberBestMove(); 
  <DD> } 
  <DD> return valUNKNOWN; 
  <DD>} 
  <DD>  
  <DD>void RecordHash(int depth, int val, int hashf) { 
  <DD> HASHE *phashe = &amp;hash_table[ZobristKey() % TableSize()]; 
  <DD> phashe-&gt;key = ZobristKey(); 

⌨️ 快捷键说明

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