Common Lisp实现Alpha-beta剪枝遇循环问题求助
修复Alpha-beta剪枝的递归循环问题
我帮你分析下代码里的问题哈,主要有两个关键点导致depth持续减小、出现循环:
1. 终止条件的判断不可靠
你用了(EQ depth 0)来判断递归终止,但Lisp里EQ是用来判断对象同一性的,对于数字来说,虽然小整数可能共享对象,但用=或者eql来判断数值相等才是更稳妥的做法。如果EQ判断失败,depth到0的时候不会触发终止,递归会继续执行,depth就会一直减小下去。
把终止条件改成这样:
((= depth 0) (calculaH tab))
2. 循环中没有更新移动对象
你的代码里只在循环开始前初始化了一次movimiento,但循环过程中没有获取下一个可能的移动,导致循环一直处理同一个移动,每次递归调用都会让depth减1——就算某次递归触发了depth=0返回启发值,循环又会再次调用递归,depth变成-1,这时候终止条件不生效,就无限递归了。
必须在每次循环结束后更新movimiento为下一个合法移动,比如在循环体末尾加上:
(setq movimiento (sigMov depth node tab))
另外,你之前的剪枝逻辑没写完,我帮你补全了完整的alpha-beta剪枝逻辑,修复后的完整代码大概是这样:
(defun ab(tab node depth a b) (cond ;; 数值相等判断,确保depth为0时终止递归 ((= depth 0) (calculaH tab)) ;; 偶数层:最大化玩家回合 ((evenp depth) (let ((v -999999) (movimiento (sigMov depth node tab))) (loop while (not (null movimiento)) do ;; 递归调用下一层 (setq v (max v (ab (nth 3 movimiento) movimiento (- depth 1) a b))) (setq a (max a v)) ;; Alpha剪枝:如果当前值大于等于beta,直接跳出循环 (when (>= v b) (return)) ;; 更新为下一个移动,避免无限循环 (setq movimiento (sigMov depth node tab))) v)) ;; 奇数层:最小化玩家回合 ((oddp depth) (let ((v 999999) (movimiento (sigMov depth node tab))) (loop while (not (null movimiento)) do (setq v (min v (ab (nth 3 movimiento) movimiento (- depth 1) a b))) (setq b (min b v)) ;; Beta剪枝:如果当前值小于等于alpha,直接跳出循环 (when (<= v a) (return)) (setq movimiento (sigMov depth node tab))) v))))
这里还做了个小优化:用let来初始化局部变量,比直接用setq更符合Lisp的函数式编程习惯,避免意外修改全局变量。
另外补充一句:如果你的sigMov函数是一次性返回所有合法移动的列表,那循环可以改成更简洁的遍历写法,比如(loop for movimiento in (sigMov depth node tab) do ...),这样就不用手动更新movimiento了,也能避免循环问题。
内容的提问来源于stack exchange,提问作者Jorge
相关产品推荐
相关产品推荐

