CLISP dfs导致程序堆栈溢出

时间:2018-04-12 16:50:41

标签: recursion lisp common-lisp stack-overflow depth-first-search

我是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个可能的状态)。当然,这可能需要一些时间,但我怎样才能避免堆栈溢出呢?

2 个答案:

答案 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顺序,实现广度 - 第一次搜索。