在Lisp中逐级遍历树(宽度优先)

时间:2019-04-10 01:08:13

标签: tree lisp common-lisp breadth-first-search

我试图按级别打印普通Lisp中的树。

列表为(1 (2 4 (5 (8 11)) 6) (3 (7 9 10))),表示树是有序的:

1. 1
2. 2 3
3. 4 5 6 7
4. 8 9 10
5. 11

这是一棵树的快速模型:

enter image description here

我试图通过执行广度优先搜索来以此顺序打印此树。

我在这里一直在想,我应该只能够carcdr穿过这棵树,但是在找出具体方法时却遇到了巨大的麻烦。这正是我在半伪代码中尝试过的内容。

(defun traverse (*cur-level*)
  (print (car *cur-level*)) ; print out the first element of the current level
    (if (cdr *cur-level*) ; if cdr of next level is not nil
      (setq *next-level* (cdr *cur-level*) ;set that to be the next level
      (traverse *next-level*)) ; recursively call until all levels traversed. 
                               ; else, simply do not do anything and terminate
                               ; the function.

我自己一步步穿过树,发现在第二个循环上,此算法中断了,因为

;first loop
(car *cur-level) = 1
(cdr *cur-level*)=((2 4 (5 (8 11)) 6) (3 (7 9 10)), 

所以在下一个循环

;second loop
(car *cur-level*) = (2 4 (5 (8 11)) 6)
(cdr *cur-level*) = (3 (7 9 10))

这意味着树实际上是分裂的,(2 4 (5 (8 11)) 6)被忽略。

此外,在同一循环中,(car cur-level)不是单个元素,而是一个列表。这意味着我需要做:

;still second loop
(car (car *cur-level*) = 2
(cdr (car *cur-level*) = (4 (5 (8 11)) 6)

所以我尝试添加一个条件来检查关卡的大小:

(if (> (list-length (car *cur-level*)) 1)
  (print (car (car *cur-level*))
  (setq *next-level* (cdr (car *cur-level*))

但这不能解决(3 (7 9 10)与树分离的事实,这意味着订单打印不正确,使我感到我正在解决仅针对此树的问题,而不是适当的问题算法。

注意:此问题发生了两次,一次发生在树的左侧的第二个循环中,另一次发生在树的左侧(其中(car cur-level) = (5 (8 11)))的第四次循环中。

如何正确执行此操作?我真的被困在这里,不知道如何继续。

2 个答案:

答案 0 :(得分:2)

我认为您在原始代码中正在尝试这样做:

(defun traverse (cur-level)
  (print (car cur-level)) ;print out the first element of the current level
  (when (cdr cur-level) ;if cdr of next level is not nil
    (setq next-level (cdr cur-level)) ;set that to be the next level
    (traverse next-level)))

我认为,通过确保所有子节点都是列表,可以使树的表示更好,

像这样:(1 (2 (4) (5 (8 (11))) (6)) (3 (7 (9) (10)))))

(defun traverse2 (children)
  (when children
    (print (mapcar 'car children)) 
    (traverse2 (apply 'append (mapcar 'cdr children)))))

(defun traverse (tree)
  (print (car tree))
  (traverse2 (cdr tree)))

尝试运行此代码here

由于我不熟悉Common Lisp,所以无法对此进行概括,但是我希望这会有所帮助。

编辑 进一步的说明

请记住,traverse2的输入将始终是子节点的列表(它们本身就是列表)

从这里开始,我将把这个子节点的输入列表称为input

  1. mapcar 'car获取input中每个子节点的第一个元素
  2. mapcar 'cdr获得input中每个子节点的第一个元素以外的所有其他元素

现在步骤2的问题在于,与car弹出一个不错的非列表元素(在这种情况下)不同,cdr给了我一个列表

因此,列表列表(或子节点列表)现在转换为列表列表(或子节点列表)

  1. apply 'append将列表列表的列表平整为列表列表(或将子节点列表的列表扩展为子节点列表)

  2. 将2的输出(列表的列表||子节点的列表)放回遍历2

答案 1 :(得分:2)

这与this question几乎是重复的:对于使用议程进行的树的宽度优先遍历,您可以使用。 my answer to the previous question为解决这个问题提供了一系列越来越复杂的方法,从简单的有礼貌的议程开始,最后显示如果您将议程换成其他结构,则可以进行各种搜索,包括广度-&深度优先,使用相同的代码。

首先抽象树结构:已经是1970年代,当我们需要其他含义时,我们不需要充满carcdr的代码。实际上,您的树的结构有些不规则,因为节点可以是(value。children)或仅仅是原始值:

(defun tree-node-value (node)
  ;; a node is either (value . children) or value
  (typecase node
    (cons
     (car node))
    (t node)))

(defun tree-node-children (node)
  ;; a node only has children if it is a cons
  (typecase node
    (cons
     (cdr node))
    (t '())))

(defun make-tree-node (value children)
  ;; only make consy nodes
  (cons value children))

我不必费心制造一个构建器,但我只定义您的示例树:

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

现在这是一个函数的实现,该函数将使用简单明了的列表式议程将访问者以广度优先的顺序遍历树木:

(defun walk-tree/breadth-first (tree visitor)
  ;; walk over a tree breadth-first, calling visitor on each node's
  ;; value, using an agenda represented explicitly as a list.
  (labels ((walk (agenda)
             (when (not (null agenda))
               ;; there is more to do
               (destructuring-bind (this . next) agenda
                 ;; call the visitor
                 (funcall visitor (tree-node-value this))
                 ;; and continue with the extended agenda
                 (walk (append next (tree-node-children this)))))))
    (walk (list tree))
    (values)))

我们可以使用print作为访客在您的树上调用此名称:

> (walk-tree/breadth-first *sample-tree* #'print)

1 
2 
3 
4 
5 
6 
7 
8 
9 
10 
11 

我鼓励您看看较旧答案中的其他实现,尤其是显式迭代的实现。

相关问题