【问题标题】:Building a binary tree from a list (preorder)从列表构建二叉树(预购)
【发布时间】:2015-12-23 19:55:24
【问题描述】:

几天前我遇到了一个有趣的问题:是否有一种优雅的函数式编程解决方案可以从列表中构建(节点标记的)二叉树?

生成的树应该是左平衡的,即树中的每一级节点都应该被完全填充,或者如果是最低的节点,则从左到右填充。此外,树的级别顺序遍历(即从上到下,从左到右)应该给出原始列表。

示例:列表 1,2,3,4,5 应导致

          1
    2         3
 4    5

显而易见的解决方案是将列表转换为数组,将第零个值分配给根,然后递归地为节点 n 为子节点分配值 2n+12n+2

但我想知道:有没有更“实用”的方式不需要辅助数组?

【问题讨论】:

  • 当示例树被预排序遍历时,我得到 (1 2 4 5 3) 预排序树看起来像 (1 (2 (3 () ()) (4 () () ) (5 () ())),也许你的意思是水平顺序?还是我有什么问题?

标签: tree functional-programming preorder


【解决方案1】:

rsr5 方案表达的思想

我最初的想法涉及一棵无限树,它的节点是函数,它接受一个值和一棵树,并将该值插入到该树中找到函数的同一位置。然后,您可以对列表进行类似 fold2 的递归,并对函数树进行 level-oder 横向,将连续函数应用于连续列表元素以累积二叉树。 (但仅适用于惰性评估)

虽然看起来有点笨拙,所以我将想法修改为一棵数据树,可以将其提供给像 ('l 'r 'l) 这样的函数或可以告诉在哪里插入和索引的东西。

(define (insert-new ls-and-rs tree)
  (cond ((or (empty-tree?) (null? ls-and-rs))
         (if (and (empty-tree?) (null? ls-and-rs))
             (make-tree x emptyTree emptyTree)
             (error "insert-new can only insert at emptyree")))
        ((sybol=? (car ls-and-rs) 'l)
         (make-tree (node tree) 
                    (insert-new (cdr ls-and-rs) 
                                (left-tree tree)) 
                    (right-tree tree)))
        ((sybol=? (car ls-and-rs) 'r)
         (make-tree (node tree) 
                    (left-tree tree) 
                    (insert-new (cdr ls-and-rs) 
                                (right-tree tree))))
        (else (error "insert-new expected 'l or 'r, recieved " 
                     (car ls-and-rs)))))) 

然后我看到您可以从索引本身构建它。如果 1 是树的头部,则为索引。否则,如果它很奇怪,它是一个节点的右分支。如果它甚至是一个左分支。它的父母的索引总是孩子的地板除以二。根据这些知识,您可以仅使用索引构建插入器或访问器。

(define (branch-order i)
  (let loop ((i i) (accum '()))
    (cond ((i = 1) accum)
          ((odd? i) (loop (quotient i 2) (cons 'r accum)))
          (else     (loop (quotient i 2) (cons 'l accum))))))

这是一个简单的递归

 (define (list->tree list)
   (let loop ((list list) (i 1) (tree emptyTree))
     (cond ((null? list) tree)
           (else (loop (cdr list)
                       (+ i 1)
                       (insert-new (branch-order i) tree))))))

当然,最简单的方法是您可以接受最小深度的二叉树。深度树除了节点和分支之外,还列出了它的最小深度。然后插入将插入到左子树中,除非右子树的最小深度小于左子树的最小深度。

(define (list->d-tree list)
  (fold-left (flip balanced-insert) emptyTree list))

(define (balanced-insert x d-tree)
  (cond ((= 0 (d-d-tree d-tree)) 
        (mk-d-tree x emptyTree emptyTree)
       ((= 1 (- (d-d-tree d-tree) (d-d-tree (l-d-tree d-tree))))
        (mk-d-tree (n-d-tree d-tree)
                   (balanced-insert x (l-d-tree d-tree))
                   (r-d-tree d-dtree)))
      (else 
       (mk-d-tree (n-d-tree d-tree)
                  (l-d-tree d-tree)
                  (balanced-insert x (r-d-tree d-dtree))))))

(define (mk-d-tree n l r)
 (let ((d-l (d-d-tree l))
       (d-r (d-d-tree r)))
 (list n l r (+ 1 (min d-l d-r)))))
(define (n-d-tree d-tree)
 (car d-tree))
(define (l-d-tree d-tree)
 (cadr d-tree))
(define (r-d-tree d-tree)
 (caddr d-tree))
(define (d-d-tree d-tree)
 (if emptyTree? d-tree)
     0
     (cadddr d-tree)))
(define (emptyTree? tree)
       (null? tree))
(define emptyTree '())

(define (flip f)
 (lambda (a b)
   (f b a)))

TLdr;使用最小深度树并插入到最左边的最小深度。

【讨论】:

  • 我不是很了解所有这些(我真的不擅长阅读类似 Lisp 的程序),但我会接受答案并稍后再考虑。谢谢!
  • 现在回到它,我也没有说得最清楚。第一个示例看起来并不完整和正确。主要思想是在您沿列表向下 cdr 时跟踪索引,并使用该索引生成插入前需要在树遍历中进行的转弯列表。第二个使用树中的额外数据元素来指导插入决策。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2016-11-06
  • 1970-01-01
  • 2019-10-18
  • 1970-01-01
  • 2021-11-23
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多