我是Lisp的新手,我正在尝试使用简单的dfs(深度优先搜索)解决8-puzzle。 但我得到一个程序堆栈溢出。 我的代码:
(setq used (list))
(defun is_used (state lst)
(cond
((null lst) nil)
((equalp (car lst) state) t)
(t (is_used state (cdr lst)))))
(defun move (lst direction)
(let* ( (zero (find_zero lst))
(row (floor zero 3))
(col (mod zero 3))
(res (copy-list lst)))
(cond
((eq direction 'L)
(if (> col 0)
(rotatef (elt res zero) (elt res (- zero 1)))))
((eq direction 'R)
(if (< col 2)
(rotatef (elt res zero) (elt res (+ zero 1)))))
((eq direction 'U)
(if (> row 0)
(rotatef (elt res zero) (elt res (- zero 3)))))
((eq direction 'D)
(if (< row 2)
(rotatef (elt res zero) (elt res (+ zero 3))))))
(if (equalp res lst)
(return-from move nil))
(return-from move res))
nil)
(defun dfs (cur d prev)
; (write (length used))
; (terpri)
(push cur used)
(let* ((ways '(L R U D)))
(loop for dir in ways
do (if (move cur dir)
(if (not (is_used (move cur dir) used))
(dfs (move cur dir) (+ d 1) cur))))))
state
这里是9个原子的列表。
使用取消注释的(write (length used))
,它会在堆栈溢出发生之前打印723 - used
中的项目数。
现在,在解决8-puzzle之前,我只想迭代所有可能的状态(正好有9个!/ 2 = 181440个可能的状态)。当然,这可能需要一些时间,但我怎样才能避免堆栈溢出呢?
答案 0 :(得分:2)
这是一些人工智能编程书中解释的典型问题。如果需要搜索大量/无限量的状态,则不应使用递归。 CL中的递归受到堆栈深度的限制。一些实现可以优化尾递归 - 但是你需要构建你的代码,以便它是尾递归的。
通常,该数据结构称为 议程 。它使各州仍然需要探索。如果你看一个州,你就会把所有的州都从那里开始探讨议程。确保议程以某种方式排序(这可能首先确定它是深度还是广度)。然后从议程中取出下一个状态并进行检查。如果达到目标,那么你就完成了。如果在找到目标之前议程是空的,则没有解决方案。否则循环......
答案 1 :(得分:0)
您的代码已经简化,
(setq *used* (list))
(defun move (position direction)
(let* ( (zero (position 0 position))
(row (floor zero 3))
(col (mod zero 3))
(command (find direction `((L ,(> col 0) ,(- zero 1))
(R ,(< col 2) ,(+ zero 1))
(U ,(> row 0) ,(- zero 3))
(D ,(< row 2) ,(+ zero 3)))
:key #'car)))
(if (cadr command)
(let ((res (copy-list position)))
(rotatef (elt res zero) (elt res (caddr command)))
res))))
(defun dfs-rec (cur_pos depth prev_pos)
(write (length *used*)) (write '_) (write depth) (write '--)
; (terpri)
(push cur_pos *used*)
(let* ((dirs '(L R U D)))
(loop for dir in dirs
do (let ((new_pos (move cur_pos dir)))
(if (and new_pos
(not (member new_pos *used* :test #'equalp)))
(dfs-rec new_pos (+ depth 1) cur_pos))))))
(print (dfs-rec '(0 1 2 3 4 5 6 7 8) 0 '()))
而不是依靠递归处理四个移动逐个,而不是跟踪每个下一个等级,只需将所有结果位置推送到to-do
列表,然后弹出并继续前一个;在to-do
列表不为空时重复(即,要执行,字面意思):
(defun new-positions (position)
(let* ( (zero (position 0 position))
(row (floor zero 3))
(col (mod zero 3)) )
(mapcan
#'(lambda (command)
(if (cadr command)
(let ((res (copy-list position)))
(rotatef (elt res zero) (elt res (caddr command)))
(list res))))
`((L ,(> col 0) ,(- zero 1))
(R ,(< col 2) ,(+ zero 1))
(U ,(> row 0) ,(- zero 3))
(D ,(< row 2) ,(+ zero 3))) )))
; non-recursive dfs function skeleton
(defun dfs (start-pos &aux to-do curr new)
(setf to-do (list start-pos))
(loop while to-do
do (progn (setf curr (pop to-do))
(setf new (new-positions curr))
(setf to-do (nconc new to-do)))))
通过这种方式,没有更多的信息可以通过递归来跟踪 - 它都在to-do
列表中。
这意味着生成的位置将以LIFO顺序处理,即to-do
列表将用作堆栈,实现深度优先搜索策略。
如果你改为追加 to-do
列表的 end 的每一步的所有新职位,那就意味着它被用作一个队列,按照FIFO顺序,实现广度 - 第一次搜索。