LISP中的广度优先搜索

LISP中的广度优先搜索,lisp,common-lisp,Lisp,Common Lisp,我用列表表示一棵树。 例如: (1 ((2 (3)) (3 (2)))) (2 ((1 (3)) (3 (1)))) (3 ((1 (2)) (2 (1)))))` 现在我需要在维护层次结构树的同时逐级遍历它。例如: 遍历根节点1 穿越深度1 1 2 1 3 2 1 3 1 3 2 穿越深度211231231231231231231231231231231231231231231 我不知道如何用Lisp来做。任何帮助,即使是一个伪代码,都将不胜感激。我想到了几种方法,但没有一种是合法的。使用议

我用列表表示一棵树。 例如:

(1 ((2 (3)) (3 (2)))) (2 ((1 (3)) (3 (1)))) (3 ((1 (2)) (2 (1)))))`
现在我需要在维护层次结构树的同时逐级遍历它。例如:

遍历根节点1 穿越深度1 1 2 1 3 2 1 3 1 3 2 穿越深度211231231231231231231231231231231231231231231 我不知道如何用Lisp来做。任何帮助,即使是一个伪代码,都将不胜感激。我想到了几种方法,但没有一种是合法的。

使用议程进行广度优先搜索 进行广度优先搜索的经典方法是维护一个议程:下一步要看的事情列表。然后,只需将对象从议程的开头剥离,并将其子对象添加到议程的末尾。对于这样一个议程,一个非常简单的方法是一个节点列表:添加到列表的末尾,然后使用append

我不能理解你的树结构,当问需要数据结构或算法规范的问题时,请给出该规范:尝试二次猜测这一点是浪费每个人的时间,因此我用列表的方式制作了自己的树:树是一个cons,其car是它的值,其cdr是一个孩子的列表。下面是创建和访问这种树结构的函数,以及一个示例树

(defun tree-node-value (n)
  (car n))

(defun tree-node-children (n)
  (cdr n))

(defun make-tree-node (value &optional (children '()))
  (cons value children))

(defparameter *sample-tree*
  (make-tree-node
   1
   (list
    (make-tree-node 2 (list (make-tree-node 3)))
    (make-tree-node 4 (list (make-tree-node 5) (make-tree-node 6)))
    (make-tree-node 7 (list (make-tree-node 8 (list (make-tree-node 9))))))))
现在我再也不用担心树的显式结构了

现在,这里有一个函数,它使用一个议程来搜索这棵树中给定的节点值:

(defun search-tree/breadth-first (tree predicate)
  ;; search a tree, breadth first, until predicate matches on a node's
  ;; value.  Return the node that matches.
  (labels ((walk (agenda)
             (if (null agenda)
                 ;; we're done: nothing matched
                 (return-from search-tree/breadth-first nil)
               (destructuring-bind (this . next) agenda
                 (if (funcall predicate (tree-node-value this))
                     ;; found it, return the node
                     (return-from search-tree/breadth-first this)
                   ;; missed, add our children to the agenda and
                   ;; carry on
                   (walk (append next (tree-node-children this))))))))
    (walk (list tree))))
为了进行比较,这里有一个深度优先搜索:

(defun search-tree/depth-first (tree predicate)
  ;; search a tree, depth first, until predicate matches on a node's
  ;; value
  (labels ((walk (node)
             (if (funcall predicate (tree-node-value node))
                 (return-from search-tree/depth-first node)
               (dolist (child (tree-node-children node) nil)
                 (walk child)))))
    (walk tree)))
现在,您可以使用一个谓词来比较这些实现,该谓词打印其参数,但总是失败,从而导致遍历整个树:

> (search-tree/breadth-first *sample-tree*
                             (lambda (v)
                               (print v)
                               nil))

1 
2 
4 
7 
3 
5 
6 
8 
9 
nil

> (search-tree/depth-first *sample-tree*
                           (lambda (v)
                             (print v)
                              nil))

1 
2 
3 
4 
5 
6 
7 
8 
9 
nil
附录1:更好的议程实施 这种天真的议程实现的一个问题是,我们最终总是调用append。更聪明的实现可以有效地将项目附加到末尾。下面是这样一个实现:

(defun make-empty-agenda ()
  ;; an agenda is a cons whose car is the list of items in the agenda
  ;; and whose cdr is the last cons in that list, or nil is the list
  ;; is empty.  An empty agenda is therefore (nil . nil)
  (cons nil nil))

(defun agenda-empty-p (agenda)
  ;; an agenda is empty if it has no entries in its list.
  (null (car agenda)))

(defun agenda-next-item (agenda)
  ;; Return the next entry from the agenda, removing it
  (when (agenda-empty-p agenda)
    (error "empty agenda"))
  (let ((item (pop (car agenda))))
    (when (null (car agenda))
      (setf (cdr agenda) nil))
    item))

(defun agenda-add-item (agenda item)
  ;; add an item to the end of the agenda, returning it
  (let ((item-holder (list item)))
    (if (agenda-empty-p agenda)
        (setf (car agenda) item-holder
              (cdr agenda) item-holder)
      (setf (cdr (cdr agenda)) item-holder
            (cdr agenda) item-holder))
    item))
请注意,无法复制提供的其中一个议程

这里有一个明确的迭代函数,它使用这个“聪明”的议程:

(defun search-tree/breadth-first/iterative (tree predicate)
  (loop with agenda = (make-empty-agenda)
        initially (agenda-add-item agenda tree)
        while (not (agenda-empty-p agenda))
        for node = (agenda-next-item agenda)
        when (funcall predicate (tree-node-value node))
        do (return-from search-tree/breadth-first/iterative node)
        else do (loop for c in (tree-node-children node)
                      do (agenda-add-item agenda c))
        finally (return nil)))
最后,任何基于议程的搜索都可以很容易地修改为可重启:它只需要在匹配点返回当前议程,并允许传入议程。以下是支持重新启动搜索的上述函数的变体:

(defun search-tree/breadth-first/iterative (tree predicate 
                                                 &optional (agenda
                                                            (make-empty-agenda)))
  ;; search TREE using PREDICATE.  if AGENDA is given and is not empty
  ;; instead restart using it (TREE is ignored in this case).  Return
  ;; the node found, or nil, and the remaining agenda
  (loop initially (unless (not (agenda-empty-p agenda))
                    (agenda-add-item agenda tree))
        while (not (agenda-empty-p agenda))
        for node = (agenda-next-item agenda)
        when (funcall predicate (tree-node-value node))
        do (return-from search-tree/breadth-first/iterative
             (values node agenda))
        else do (loop for c in (tree-node-children node)
                      do (agenda-add-item agenda c))
        finally (return (values nil agenda))))
附录2:带议程的一般搜索 事实上,可以进一步推广基于议程的搜索树方法。特别是:

如果议程是队列FIFO,则得到广度优先搜索; 如果议程是一堆后进先出,那么你得到的是深度优先搜索。 对于这两种情况,实际的搜索实现可能是相同的,这很简单

下面是一些演示这一点的代码。这定义了树访问的通用函数和基于cons的树的方法,因此不需要关心这一点,并进一步定义了议程的协议,其中包含两个具体类,队列和堆栈,它们具有适当的方法。然后,搜索函数完全不知道它是进行深度优先还是广度优先搜索,并且在这两种情况下都可以重新启动

这是一段相当重要的代码:我把它留在这里,以防它对任何人都有用

;;;; Trees
;;;

(defgeneric tree-node-value (n)
  (:documentation "The value of a tree node"))

(defgeneric tree-node-children (n)
  (:documentation "The children of a tree"))

;;;; Consy trees
;;;

(defmethod tree-node-value ((n cons))
  (car n))

(defmethod tree-node-children ((n cons))
  (cdr n))

(defun make-cons-tree-node (value &optional (children '()))
  ;; consy trees: I could do some clever EQL method thing perhaps to
  ;; abstract this?
  (cons value children))

(defun form->tree (form &key (node-maker #'make-cons-tree-node))
  (labels ((walk-form (f)
             (destructuring-bind (value . child-forms) f
               (funcall node-maker
                        value
                        (mapcar #'walk-form child-forms)))))
    (walk-form form)))

(defparameter *sample-tree*
  (form->tree '(1 (2 (3))
                  (4 (5) (6))
                   (7 (8 (9))))))


;;;; Agendas
;;;

(defclass agenda ()
  ())

(defgeneric agenda-empty-p (agenda)
  (:documentation "Return true if AGENDA is empty"))

(defgeneric agenda-next-item (agenda)
  (:documentation "Return the next item from AGENDA.
If there is no next item, signal an error: there is a before method which does this.")
  (:method :before ((agenda agenda))
   (when (agenda-empty-p agenda)
     (error "empty agenda"))))

(defmethod initialize-instance :after ((agenda agenda) &key
                                       (item nil itemp)
                                       (items (if itemp (list item) '()))
                                       (ordered nil))
  (agenda-add-items agenda items :ordered ordered))

(defgeneric agenda-add-item (agenda item)
  (:documentation "Add ITEM to AGENDA, returning ITEM.
There is an around method which arranges for ITEM to be returned.")
  (:method :around ((agenda agenda) item)
   (call-next-method)
   item))

(defgeneric agenda-add-items (agenda items &key ordered)
  (:documentation "Add ITEMS to AGENDA.
If ORDERED is true do so in a way that AGENDA-NEXT-ITEM will pull them
off in the same order.  Return AGENDA (there is an around method which
arranges for this).  The default method just adds the items in the
order given.")
  (:method :around ((agenda agenda) items &key ordered)
   (declare (ignorable ordered))
   (call-next-method)
   agenda)
  (:method ((agenda agenda) items &key ordered)
   (declare (ignorable ordered))
   (loop for item in items
         do (agenda-add-item agenda item))))

;;;; Queues are FIFO agendas
;;;

(defclass queue (agenda)
  ((q :initform (cons nil nil)))
  (:documentation "A queue"))

(defmethod agenda-empty-p ((queue queue))
  (null (car (slot-value queue 'q))))

(defmethod agenda-next-item ((queue queue))
  (let* ((q (slot-value queue 'q))
         (item (pop (car q))))
    (when (null (car q))
      (setf (cdr q) nil))
    item))

(defmethod agenda-add-item ((queue queue) item)
  (let ((q (slot-value queue 'q))
        (item-holder (list item)))
    (if (null (car q))
        (setf (car q) item-holder
              (cdr q) item-holder)
      (setf (cdr (cdr q)) item-holder
            (cdr q) item-holder))))

;;;; Stacks are LIFO agendas
;;;

(defclass stack (agenda)
  ((s :initform '()))
  (:documentation "A stack"))

(defmethod agenda-empty-p ((stack stack))
  (null (slot-value stack 's)))

(defmethod agenda-next-item ((stack stack))
  (pop (slot-value stack 's)))

(defmethod agenda-add-item ((stack stack) item)
  (push item (slot-value stack 's)))

(defmethod agenda-add-items ((stack stack) items &key ordered)
  (loop for item in (if ordered (reverse items) items)
        do (agenda-add-item stack item)))


;;;; Searching with agendas
;;;

(defun tree-search (tree predicate &key (agenda-class 'stack))
  ;; search TREE using PREDICATE.  AGENDA-CLASS (default STACK)
  ;; defines the type of search: a STACK will result in a depth-first
  ;; search while a QUEUE will result in a breadth-first search.  This
  ;; is a wrapper around AGENDA-SEARCH.
  (agenda-search (make-instance agenda-class :item tree) predicate))

(defun agenda-search (agenda predicate)
  ;; Search using an agenda.  PREDICATE is compared against the value
  ;; of a tree node.  On success return the node matched and the
  ;; agenda, on failure return NIL and NIL.  If the returned agenda is
  ;; not empty it can be used to restart the search.
  (loop while (not (agenda-empty-p agenda))
        for node = (agenda-next-item agenda)
        when (funcall predicate (tree-node-value node))
        do (return-from agenda-search
             (values node agenda))
        else do (agenda-add-items agenda (tree-node-children node)
                                  :ordered t)
        finally (return (values nil nil))))

您的树数据结构应该如何工作?它基本上是不可读的,因为它是目前写的,我无法计算出你的遍历如何与之对应。非常有指导意义!但我认为议程项目末尾缺少一个括号。