我尝试使用维基百科上找到的伪代码来编写Alpha-beta算法。当程序到达(EQ depth 0)
时,它返回了启发式值,但深度值继续减少,导致循环。现在我的代码看起来像这样:
(defun ab(tab node depth a b)(cond ((EQ depth 0) (calculaH tab)) ((eq (mod depth 2) 0) (setq v -999999) (setq 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)) (cond((<= b a) (break)) (t (setq movimiento (sigMov depth movimiento tab))))) (return v)) (t (setq v 999999) (setq 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 a (min b v)) (cond((<= b a) (break)) (t (setq movimiento (sigMov depth movimiento tab))))) (return v))))
我应该在代码的某个地方增加深度值吗?为什么递归没有自动增加这个值?
回答:
维基百科上的Alpha-beta剪枝算法可以几乎原样翻译成Lisp。由于它使用了无穷值,我们不应该用“999999”来凑合,而是定义能够可靠处理这些特殊值的min
和max
函数:
(defpackage :alphabeta (:use :cl) ;; 自定义支持无穷大的min/max函数 (:shadow min max))(in-package :alphabeta)(defvar -∞ '-∞ "负无穷大符号")(defvar +∞ '+∞ "正无穷大符号")(defun min (a b) (cond ((eql a +∞) b) ((eql b +∞) a) ((eql a -∞) -∞) ((eql b -∞) -∞) (t (cl:min a b))))(defun max (a b) (cond ((eql a -∞) b) ((eql b -∞) a) ((eql a +∞) +∞) ((eql b +∞) +∞) (t (cl:max a b))))
代码还依赖于辅助函数,我在这里声明它们以避免警告:
;; 你需要实现以下函数(declaim (ftype function terminal-node-p heuristic-value children))
然后,伪代码可以几乎原样编写。为了回答这个问题,我保留了相同的希腊变量,但正如Dan Robertson在评论中指出的,这可能会导致意外:
使用像α或β这样的名称时要小心,因为一个典型的支持Unicode的Lisp实现会将它们大写为Α和Β。你能区分A和Α或B和Β之间的区别吗?
(defun alphabeta (node depth α β maximizing-player-p) (when (or (= depth 0) (terminal-node-p node)) (return-from alphabeta (heuristic-value node))) (if maximizing-player-p (let ((value -∞)) (dolist (child (children node)) (setf value (max value (alphabeta child (1- depth) α β nil))) (setf α (max α value)) (when (<= β α) ;; β剪枝 (return))) value) (let ((value +∞)) (dolist (child (children node)) (setf value (min value (alphabeta child (1- depth) α β t))) (setf α (min α value)) (when (<= β α) ;; α剪枝 (return))) value)))