简约设计の艺术

讨论软件制造过程中的艺术与工程,软件哲学

用户操作
[即时聊天] [发私信] [加为好友]
丁亮ID:DL88250
1423991次访问,排名12好友173人,关注者246
Linux、Java、C/C++,OpenSource热爱者,擅长JavaSE/JavaEE开发,熟悉JSF、EJB、Spring、JPA、OSGi等框架应用的架构,目前正在深入学习算法、OOAD、TDD以及敏捷实践。
DL88250的文章
原创 857 篇
翻译 10 篇
转载 169 篇
评论 751 篇
88250的公告






最近评论
youjianbo_han_87:可惜了些资料啊,虽然说不一定每一本都会看,但是要用到的时候可以参考下,这才是书真正的价值,呵呵
DL88250:哎。。。。1年多以前,我格了我的硬盘。。。。
youjianbo_han_87:要不给你寄个大容量U盘,拷好寄回来? :)
youjianbo_han_87:终于浏览了一遍,感觉有点乱,有些事示例代码,一些是书。
分english和chinese是不是英文版和中文版的意思?如果想要的话,楼主是发到邮箱里面呢?怎么多,怎么发啊,哪里有得下,给个地址
DL88250:厄。。。。应该是有用的,只是间隔要调整好,不然很占CPU的。
文章分类
收藏
    相册
    Beyond
    壁纸收集
    动漫Kiss图图
    我的珍藏
    我的桌面
    CSDN专家Blog
    孟岩的专栏
    袁萌的专栏
    Ubuntu/Linux相关
    ChinaUnix
    Compiz Themes
    Compiz-Fusion
    deviantART Search
    GetDeb
    Gnome-Look
    KDE-Look
    LinuxToy
    Linux桌面中文网
    Ubuntu中文官方论坛
    Ubuntu桌面中文网
    博友
    老李的Blog
    代码示例
    C++代码示例
    HTML代码示例
    Java Code examples
    技术站点
    Apache Software
    CSDN
    Eclipse.org
    Extreme Programming
    Facelets DevDoc
    hibernate.org
    IBM软件技术
    JavaFX Home
    JavaFX Script Reference
    JavaWorld@TW
    Java开源大全
    JBoss.org
    LEX & YACC Page
    NetBeans中文社区
    Open Source Initiative
    PHP 官方
    Ruby on Rails
    Ruby中文社区论坛
    SOURCEFORGE.NET
    Springframework.org
    Struts Framework
    Sun中国技术社区
    UML官方
    图书下载
    CSDN下载频道
    e 书时空
    IT e Book
    中华电脑书库
    中国 E 书网
    中国 IT 认证实验室
    中文电子书网
    偶要雷锋 - 分享社区
    我爱 e 书
    网络中国 - E 书
    我的偶像 :-)
    Alan Turing
    Bjarne Stroustrup's Homepage
    Don Knuth's Home Page
    Martin Fowler
    Richard Stallman's Home Page
    Uncle Bob (Robert C. Martin)
    我的朋友
    Eleven的专栏
    Eric.Gao的空间
    Meteor的专栏
    mmchsusan的主页
    solonote的专栏
    Vanessa的小窝
    ZhiBaoDeng的专栏
    zyofprogrammer的学习历程
    先知罗庄的专栏
    光光的Blog~
    师傅dorainm的Blog
    皮皮的空间
    秋歌的专栏
    金秋风采
    阿明的专栏
    存档
    订阅我的博客
    XML聚合  FeedSky
    订阅到鲜果
    订阅到Google
    订阅到抓虾
    订阅到BlogLines
    订阅到Yahoo
    订阅到GouGou
    订阅到飞鸽
    订阅到Rojo
    订阅到newsgator
    订阅到netvibes

    原创 博弈基本技术——置换表收藏

    新一篇:  FEN文件格式 | 旧一篇: NetBeans 时事通讯(刊号 # 9 - May 26, 2008)




    《对弈程序基本技术》专题
     
    置换表
     
    Bruce Moreland /
     
    一个多功能的数据结构
     
      国际象棋的搜索树可以用图来表示,而置换结点可以引向以前搜索过的子树上。置换表可以用来检测这种情况,从而避免重复劳动。如果“1. e4 d6 2. d4”以后的局面已经搜索过了,那就没有必要再搜索“1. d4 d6 2. e4”以后的局面了。
      这个原因可能鼓舞着早期的电脑国际象棋程序的设计师们,而现在事实上这还是置换表的次要用途。在某些局面,例如在没有通路兵的王兵残局中,检查到的置换的数量是惊人的,以至于搜索可以在短达时间内达到很深的深度。
      省去重复的工作,这是置换表的一大特色,但是在一般的中局局面里,置换表的另一个作用更为重要。每个散列项里都有局面中最好的着法,我在“迭代加深”这一章里解释过,首先搜索好的着法可以大幅度提高搜索效率。因此如果你在散列项里找到最好的着法,那么你首先搜索这个着法,这样会改进你的着法顺序,减少分枝因子,从而在短的时间内搜索得更深。
     
    实现
     
      主置换表是一个散列数组,每个散列项看上去像这样:
     
    #define hashfEXACT 0
    #define hashfALPHA 1
    #define hashfBETA 2
    typedef struct tagHASHE {
     U64 key;
     int depth;
     int flags;
     int value;
     MOVE best;
    } HASHE;
     
      这个散列数组是以“Zobrist键值”为指标的。你求得局面的键值,除以散列表的项数得到余数,这个散列项就代表该局面。由于很多局面都有可能跟散列表中同一项作用,因此散列项需要包含一个校验值,它可以用来确认该项就是你要找的。通常校验值是一个64位的数,也就是上面那个例子的第一个域。
      你从搜索中得到结果后,要保存到散列表中。如果你打算用散列表来避免重复工作,那么重要的是记住搜索有多深。如果你在一个结点上搜索了3层,后来又打算做10层搜索,你就不能认为散列项里的信息是准确的。因此子树的搜索深度也要记录。
      在Alpha-Beta搜索中,你很少能得到搜索结点的准确值。AlphaBeta的存在有助你裁剪掉没有用的子树,但是用Alpha-Beta有个小的缺点,你通常不会知道一个结点到底有多坏或者有多好,你只是知道它足够坏或足够好,从而不需要浪费更多的时间。
      当然,这就引发了一个问题,散列项里到底要保存什么值,并且当你要获取它时怎样来做。答案是储存一个值,另加一个标志来说明这个值是什么含义。在我上面的例子中,比方说你在评价域中保存了16,并且在标志域保存了“hashfEXACT”,这就意味着该结点的评价是准确值16;如果你在标志域中保存了“hashfALPHA”,那么结点的值最多是16;如果保存了“hashfBETA”,这个值就至少是16
      当你在搜索中遇到特定情况时,很容易决定评价和标志应该保存哪些内容。然而避免错误是非常重要的,散列表是非常容易犯错误的,而且一旦犯下错误就很难捕捉出来。
      我的散列项的最后一个域,保存着上次搜索到这个局面时的最佳着法。有时我没有得到最佳着法,比如任何低出边界的情况(返回一个小于或等于Alpha的值),而其他情况必定有最佳着法,比如高出边界的情况(返回一个大于或等于Beta的值)【译注:只有叶子结点才没有最佳着法,即便是Alpha结点,所有的着法都是差的,也应该从中找一个最好的着法,它对更深一层的搜索会带来很大的好处。】
      如果找到最佳着法,那么它应该首先被搜索。
      下面是示范程序,是根据Alpha-Beta函数修改的,改动的地方用醒目的字标出:
     
    int AlphaBeta(int depth, int alpha, int beta) {
     int hashf = hashfALPHA;
     if ((val = ProbeHash(depth, alpha, beta)) != valUNKNOWN) {
      // 【valUNKNOWN必须小于-INFINITY或大于INFINITY,否则会跟评价值混淆。】
      return val;
     }
     if (depth == 0) {
      val = Evaluate();
      RecordHash(depth, val, hashfEXACT);
      return val;
     }
     GenerateLegalMoves();
     while (MovesLeft()) {
      MakeNextMove();
      val = -AlphaBeta(depth - 1, -beta, -alpha);
      UnmakeMove();
      if (val >= beta) {
       RecordHash(depth, beta, hashfBETA);
       return beta;
      }
      if (val > alpha) {
       hashf = hashfEXACT;
       alpha = val;
      }
     }
     RecordHash(depth, alpha, hashf);
     return alpha;
    }
     
      以下就是两个新的函数的代码:
     
    int ProbeHash(int depth, int alpha, int beta) {
     HASHE *phashe = &hash_table[ZobristKey() % TableSize()];
     if (phashe->key == ZobristKey()) {
      if (phashe->depth >= depth) {
       if (phashe->flags == hashfEXACT) {
        return phashe->val;
       }
       if ((phashe->flags == hashfALPHA) && (phashe->val <= alpha)) {
        return alpha;
       }
       if ((phashe->flags == hashfBETA) && (phashe->val >= beta)) {
        return beta;
       }
      }
      RememberBestMove();
     }
     return valUNKNOWN;
    }
     
    void RecordHash(int depth, int val, int hashf) {
     HASHE *phashe = &hash_table[ZobristKey() % TableSize()];
     phashe->key = ZobristKey();
     phashe->best = BestMove();
     phashe->val = val;
     phashe->hashf = hashf;
     phashe->depth = depth;
    }
     
      你所看到的代码,并不像航天科学一样准确,而是很可能有错误的,而且细节上的问题我还没有讨论。如果你的程序中有错误,或许就是很严重的错误。
      【以上代码有个速度上的瓶颈,即“ZobristKey() % TableSize()”这个表达式。由于“电脑一做除法就成了傻瓜”,所以“TableSize”最好是一个2n的常量,只有当除数是2n时除法才可以由右移指令取代。最好的方法是设一个“TableSizeMask”的变量:
     
    int TableSizeMask = TableSize() - 1;
    HASHE *phashe = &hash_table[ZobristKey() & TableSizeMask];
     
      而这里“TableSize()”也必须是2n。正是这个道理,在很多可以设定置换表大小的国际象棋程序中,允许的设定值总是呈倍数增长的,要么是3M6M12M24M等等(如果每个散列项有12字节),要么是4M8M16M32M等等(如果每个散列项有16字节)。】
     
    替换策略
     
      最主要的细节就包括,什么时候该覆盖散列项。在上面的例子中,我用了“始终替换”的策略,即简单地覆盖已经存在的值。这或许不是最好的策略,事实上已经有大量的工作试图找出哪个策略是最好的。
      另一个策略是“同样深度或更深时替换”。除非新局面的深度大于或等于散列表中已经有的值,否则已经存在的结点将被保留。
      还有很多试验的余地。1994年我在Usenet(新闻组网络系统)的新闻组rec.games.chess(如今是rec.games.chess.computer)上问了这个问题,得到了Ken Thompson的答复。
       他的回答是使用两个散列表。一个使用“始终替换”策略,另一个使用“同样深度或更深时替换”。当你做试探时,两个散列表都去试探,如果其中一个可以产生 截断,那就可以了。如果两者都不能产生截断,那么你可能至少得到一个最佳着法,实际上更多的可能是得到两个不同的着法,两者都应该首先(或第二个)尝试。
      记录的时候,你只要简单地根据替换策略来执行。
      如果你使用“同样深度或更深时替换”的策略,那么你的散列表可能最终会被过期的但很深的结点所占满。解决方案就是每次你走棋时都清除散列表,或者在散列项中加入“顺序”这个域,从而使这个策略变成变成“同样深度,或更深,或原来是旧的搜索,才替换”。
      我在我的程序Ferret中使用了Thompson的策略,并且运行得很好。另一个程序Gerbil也使用这个策略,你可以去看它的源代码。
      【根据译者研究的结果,只用“深度优先覆盖”策略(即“同样深度或更深时替换”),效果会比“始终替换”好得多,而代码则并不复杂,只有醒目的部分是新增的:
     
    void RecordHash(int depth, int val, int hashf) {
     HASHE *phashe = &hash_table[ZobristKey() & (TableSize() - 1)];
     if (phashe->hashf != hashfEMPTY && phashe->depth > depth) {
      return;
     }
     phashe->key = ZobristKey();
     phashe->best = BestMove();
     phashe->val = val;
     phashe->hashf = hashf;
     phashe->depth = depth;
    }
     
      如果使用这个代码,那么每走一步以前都必须把散列表中所有的标志项置为“hashfEMPTY”。】
     
    不稳定性的问题
     
      当你用置换表时,如果你允许搜索过程根据散列项来截断,那就会产生另一个问题,你的搜索会受“不稳定性”的捆扰。
      不稳定性至少是由以下因素引起的:
      1. 你可能在做6层的搜索,但是如果你在散列项中得到10层搜索的结果,就可能根据这个值来截断。在后来的搜索中,这个散列项被覆盖了,因此你在这个结点上得到了两个不同的值。
      2. Zobrist键值无法记录到达结点的线路,这个结点上不是每条线路都有相同结果的。如果某条线路遇到重复局面,那么散列项的值就会跟路线有关。因为重复局面会导致和局的分值,或者至少不一样的分值。
      就我所知,还没有什么办法能处理这些问题。
      【另外,如果搜索过程中找到杀棋,那么评价值会接近“INFINITY”或“-INFINITY”,此时记录散列表时不能简单地记录这些评价值,在后面介绍的“胜利局面”的处理中,会谈到这个问题。】
     
      原文:http://www.seanet.com/~brucemo/topics/hashing.htm
      译者:黄晨 ()
      类型:全译加译注
  • 上一篇 基本搜索方法——迭代加深
  • 下一篇 高级搜索方法——简介()
  • 返 回 象棋百科全书——电脑象棋

  • 转自:http://www.elephantbase.net/computer/search_hashing.htm
     
     

    发表于 @ 2008年06月01日 20:07:00|评论(loading...)|收藏

    新一篇:  FEN文件格式 | 旧一篇: NetBeans 时事通讯(刊号 # 9 - May 26, 2008)

    评论

    #a282590791 发表于2008-06-06 03:27:20  IP: 219.132.15.*
    都看不懂啊
    #DL88250 发表于2008-06-07 12:59:10  IP: 222.172.221.*
    基本概念应该还是比较好理解的啊,而且还有伪码说明:)
    发表评论  


    登录
    Csdn Blog version 3.1a
    Copyright © 88250