什么是启发式搜索

前面讲到了,AB搜索的效果很大程度上取决于子节点的排序。

2

还是前一章的那张图,上面可以看到在第二层中,第一个节点的值是3,第二个是6。因为3比较小,而这一层的最大值会被选中,所以第二个节点也需要完整计算所有孩子。如果3和6调换一下顺序,6在前,3在后。那么当第二个节点计算出第一个孩子5的时候就没有必要计算之后的孩子了。也就是,Alpha-Beta 剪枝的效率和节点排序有很大关系,如果最优的节点能排在前面,则能大幅提升剪枝效率。

对于Beta 剪枝也是同样的道理。所以说Alpha Beta剪枝的效率是取决于每一层节点的顺序的。 我们肯定是无法精确排序的,因为每一个节点的值并不能直接计算出来,需要递归计算子节点。 但是我们依然能对节点进行大致的一个排序。前面说过了,只要有一个大致的排序 其实就能很好的提升剪枝效率。

那么如何排序呢?就是给所有待搜索的位置进行打分,按照分数的高低来排序。注意这个打分算法是对某一个空位进行打分,和对整个棋盘进行打分的 evaluate 函数是不一样的。不过打分的基本原理是相同的。具体就是根据这个位置是否能成五,活四,活三等来进行打分。具体的代码有些长就不贴出来了,请参见 board.js 中的 gen 函数 。

有了打分之后,我们就可以按照分数高低进行排序了。具体实现的时候,是根据按照 成五,活四,双三,活三,其他 的顺序来排序的。

启发式搜索函数的代码如下:

board.js

  1. gen (role, onlyThrees, starSpread) {
  2. if (this.count <= 0) return [7, 7]
  3. var fives = []
  4. var comfours=[]
  5. var humfours=[]
  6. var comblockedfours = []
  7. var humblockedfours = []
  8. var comtwothrees=[]
  9. var humtwothrees=[]
  10. var comthrees = []
  11. var humthrees = []
  12. var comtwos = []
  13. var humtwos = []
  14. var neighbors = []
  15. var board = this.board
  16. var reverseRole = R.reverse(role)
  17. for(var i=0;i<board.length;i++) {
  18. for(var j=0;j<board.length;j++) {
  19. var p = [i, j]
  20. if(board[i][j] == R.empty) {
  21. var neighbor = [2,2]
  22. if(this.allSteps.length < 6) neighbor = [1, 1]
  23. if(this.hasNeighbor([i, j], neighbor[0], neighbor[1])) { //必须是有邻居的才行
  24. var scoreHum = p.scoreHum = this.humScore[i][j]
  25. var scoreCom = p.scoreCom = this.comScore[i][j]
  26. var maxScore = Math.max(scoreCom, scoreHum)
  27. p.score = maxScore
  28. p.role = role
  29. if(scoreCom >= S.FIVE) {//先看电脑能不能连成5
  30. fives.push(p)
  31. } else if(scoreHum >= S.FIVE) {//再看玩家能不能连成5
  32. //别急着返回,因为遍历还没完成,说不定电脑自己能成五。
  33. fives.push(p)
  34. } else if(scoreCom >= S.FOUR) {
  35. comfours.push(p)
  36. } else if(scoreHum >= S.FOUR) {
  37. humfours.push(p)
  38. } else if(scoreCom >= S.BLOCKED_FOUR) {
  39. comblockedfours.push(p)
  40. } else if(scoreHum >= S.BLOCKED_FOUR) {
  41. humblockedfours.push(p)
  42. } else if(scoreCom >= 2*S.THREE) {
  43. //能成双三也行
  44. comtwothrees.push(p)
  45. } else if(scoreHum >= 2*S.THREE) {
  46. humtwothrees.push(p)
  47. } else if(scoreCom >= S.THREE) {
  48. comthrees.push(p)
  49. } else if(scoreHum >= S.THREE) {
  50. humthrees.push(p)
  51. } else if(scoreCom >= S.TWO) {
  52. comtwos.unshift(p)
  53. } else if(scoreHum >= S.TWO) {
  54. humtwos.unshift(p)
  55. } else {
  56. neighbors.push(p)
  57. }
  58. }
  59. }
  60. }
  61. }
  62. //如果成五,是必杀棋,直接返回
  63. if(fives.length) return fives
  64. // 自己能活四,则直接活四,不考虑冲四
  65. if (role === R.com && comfours.length) return comfours
  66. if (role === R.hum && humfours.length) return humfours
  67. // 对面有活四冲四,自己冲四都没,则只考虑对面活四 (此时对面冲四就不用考虑了)
  68. if (role === R.com && humfours.length && !comblockedfours.length) return humfours
  69. if (role === R.hum && comfours.length && !humblockedfours.length) return comfours
  70. // 对面有活四自己有冲四,则都考虑下
  71. var fours = role === R.com ? comfours.concat(humfours) : humfours.concat(comfours)
  72. var blockedfours = role === R.com ? comblockedfours.concat(humblockedfours) : humblockedfours.concat(comblockedfours)
  73. if (fours.length) return fours.concat(blockedfours)
  74. var result = []
  75. if (role === R.com) {
  76. result =
  77. comtwothrees
  78. .concat(humtwothrees)
  79. .concat(comblockedfours)
  80. .concat(humblockedfours)
  81. .concat(comthrees)
  82. .concat(humthrees)
  83. }
  84. if (role === R.hum) {
  85. result =
  86. humtwothrees
  87. .concat(comtwothrees)
  88. .concat(humblockedfours)
  89. .concat(comblockedfours)
  90. .concat(humthrees)
  91. .concat(comthrees)
  92. }
  93. // result.sort(function(a, b) { return b.score - a.score })
  94. //双三很特殊,因为能形成双三的不一定比一个活三强
  95. if(comtwothrees.length || humtwothrees.length) {
  96. return result
  97. }
  98. // 只返回大于等于活三的棋
  99. if (onlyThrees) {
  100. return result
  101. }
  102. var twos
  103. if (role === R.com) twos = comtwos.concat(humtwos)
  104. else twos = humtwos.concat(comtwos)
  105. twos.sort(function(a, b) { return b.score - a.score })
  106. result = result.concat(twos.length ? twos : neighbors)
  107. //这种分数低的,就不用全部计算了
  108. if(result.length>config.countLimit) {
  109. return result.slice(0, config.countLimit)
  110. }
  111. return result
  112. }

下一篇:五子棋AI设计教程第二版六:迭代加深