使用搜索查找树中最深的节点,然后移动它

时间:2015-03-03 15:27:06

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

我有一棵树,我想找到最深的节点的最长路径然后我想以某种方式改变它以使其更加平衡。在这个非常简单的例子中,我想移动4,因为它是最深的路径,而是把它放在1中,这样高度的差异就不那么大了。我想在lisp中这样做,我不太确定如何,我知道我想使用搜索,但它拥有它,以便它确实找到最长的。我知道如何从根到达给定节点的路径,但我不知道如何实际获得最深的节点。我想我对如何将最深的节点放在更好的部分有一个想法,但任何建议都会很棒。

到目前为止,我只有一些代码可以返回树的深度,但是有什么方法可以让它返回最深的节点?

(defun maxdepth (l)
  (cond ((null l) 0)
    ((atom l) 0)
    (t (+ 1 (max (maxdepth (cadr l)) (maxdepth (caddr l)))))))

Basically in this very simple example i want to move 4 since it is the deepest path and instead put it in 1 so that the difference in height is not as big

enter image description here

2 个答案:

答案 0 :(得分:1)

我假设基于maxdepth的实现,如果节点有分支,则树的格式为(value left-branch &optional right-branch),如果是叶节点,则为value

因为没有这样做导致我们必须在每次迭代时重建整个树的病态情况,(value)也是一个叶子节点,(value nil)(value nil nil)。< / p>

我还假设您要在现有树中移动分支,而不是在不同的位置创建分支的新树。

如果是这样,您的目标是保留在计算树的maxdepth时遇到的最后一个非叶节点。

新函数的工作方式与maxdepth完全相同,但它会返回三个值而不是一个。对于您的图表,表示为(1 (2 (3 4))),它将返回:

3
(3 4)
LEFT

...其中3是深度,(3 4)是最深的非叶节点,并且是LEFT 意味着4位于该节点的左侧分支(CADR)而不是 右(CADDR)分支。

这意味着每次它使用自己的返回值时,它必须捕获所有三个返回值,以便它可以重新返回它们,并且需要特殊处理 确保我们不会将“4”作为返回值,而您不会 能够移动。

因为我们正在抛弃3个值,所以我们必须定义我们自己的max1+版本作为宏(lbetterp是下面的自由变量。它们的值分别是l的{​​{1}}参数和要使用的比较运算符:

maxdepth

因为叶节点可以是裸值,也可以是带有一个的列表 非null成员,我们需要一些函数来帮助平滑这些差异 出。第一个函数识别一个叶子,第二个函数将任何叶子转换为一个裸值:

(defmacro my-max (value-form1 value-form2)
  (let ((n1 (gensym))
    (v1 (gensym))
    (n2 (gensym))
    (v2 (gensym))
    (pos1 (gensym))
    (pos2 (gensym)))
    `(multiple-value-bind (,n1 ,v1 ,pos1) ,value-form1
       (multiple-value-bind (,n2 ,v2 ,pos2) ,value-form2
     (if (funcall betterp ,n1 ,n2)
         (apply #'values (cons ,n1 (if (consp ,v1)
                                       (list ,v1 ,pos1)
                                       (list l 'left))))
         (apply #'values (cons ,n2 (if (consp ,v2)
                                       (list ,v2 ,pos2)
                                       (list l 'right)))))))))

(defmacro my-1+ (value-form)
  (let ((number (gensym))
    (value (gensym))
    (position (gensym)))
    `(multiple-value-bind (,number ,value ,position) ,value-form
       (values (1+ ,number) ,value ,position))))

然后它进入(defun leafp (val) (or (not (listp val)) (= (length (remove nil val)) 1))) (defun leaf-value (leaf) (if (listp leaf) (car leaf) leaf)) 函数本身,我只做了一些小改动:

maxdepth

最大的变化是(defun maxdepth (l &optional (betterp #'>=)) (cond ((null l) (values -1 l nil)) ((leafp l) (values 0 (leaf-value l) nil)) (t (my-1+ (my-max (maxdepth (cadr l) betterp) (maxdepth (caddr l) betterp)))))) 可以通过maxdepth作为mindepth参数转换为#'<=。这是因为我们必须在树上找到我们找到的深度值的位置。

此外,betterp = l被视为深度为-1。这样,如果你有分支NIL,最深的值将是(8 NIL 10)上的10,而不是`NIL,可能根本不是一个节点,只是一个存根节点已被删除或尚未添加。

移动节点,一旦找到,就可以使用RIGHT(setf (cdr x))完成。但首先,您需要编写(setf (cddr x))函数:

mindepth

我想出了这个函数来实际将值从最深的节点移动到最浅的节点:

(defun mindepth (tree)
    (maxdepth tree #'<=))

如果在我测试中使用的树上重复调用此函数,最终是(defun balance-tree (l) (multiple-value-bind (deepest-depth deepest-node deepest-side) (maxdepth l) (multiple-value-bind (shallowest-depth shallowest-node shallowest-side) (mindepth l) (let ((max-value (case deepest-side ((left) (prog1 (cadr deepest-node) (setf (cadr deepest-node) nil))) ((right) (prog1 (caddr deepest-node) (setf (cddr deepest-node) nil)))))) (case shallowest-side ((left) (cond ((= (length shallowest-node) 1) (setf (cdr shallowest-node) (list max-value))) ((leafp (cadr shallowest-node)) (setf (cadr shallowest-node) (list (leaf-value (cadr shallowest-node)) max-value))) (t (setf (cadr shallowest-node) max-value)))) ((right) (case (length shallowest-node) ((1) (setf (cdr shallowest-node) (list nil max-value))) ((2) (setf (cddr shallowest-node) (list max-value))) ((3) (cond ((null (caddr shallowest-node)) (setf (caddr shallowest-node) max-value)) ((leafp (caddr shallowest-node)) (setf (caddr shallowest-node) (list (leaf-value (caddr shallowest-node)) max-value))) (t (error "Didn't think this was reachable!")))))))))) ;; Return value: the modified tree. l) 树在以下两种状态之间交替:

(1 (2 (3 4)) (5 (6 (7 (8 nil 10)))))

...两者的(1 (2 (3 4) 10) (5 (6 (7 NIL)) (8 NIL))) (1 (2 (3 NIL 4) 10) (5 (6 (7 NIL)) (8 NIL))) 均为3。

在原始示例中使用时,会产生正确的结果:

maxdepth

......相当于:

CL-USER> (balance-tree '(1 (2 (3 4))))
(1 (2 (3 NIL)) 4)

如果您想将(1 (2 3) 4) 等实例转换为普通(3 NIL),则有一个 函数,但它遍历整个树并重建它,所以谨慎使用:

3

由于这两种情况只是返回其参数或其参数的函数,(defun clean-tree (tree) (cond ((null tree) nil) ((atom tree) tree) ((= (length (remove nil tree)) 1) (car tree)) ((and (= (length tree) 3) (null (caddr tree))) (setf (cddr tree) nil) (clean-tree tree)) (t (unless (null (cadr tree)) (setf (cadr tree) (clean-tree (cadr tree)))) (unless (null (caddr tree)) (setf (caddr tree) (clean-tree (caddr tree)))) tree))) 应始终以clean-tree形式使用:

setf

以上是以上示例输出之一的(setf tree (clean-tree tree)) 输出:

clean-tree

答案 1 :(得分:0)

您可以使用深度优先搜索,每次(每个节点)递增一个计数器,展开一个节点,保留所有探索节点的列表,然后解析它以找到应该是最深路径的最高计数器。 / p>