【问题标题】:Counting nodes in a tree in Java在Java中计算树中的节点
【发布时间】:2009-02-13 20:50:39
【问题描述】:

首先,我发誓这不是作业,这是我在一次采访中被问到的问题。我想我把它弄得一团糟(尽管我确实意识到解决方案需要递归)。问题来了:

实现 count() 方法,该方法返回树中的节点数。如果一个节点既没有左孩子也没有右孩子,相关的getXXChild()方法将返回null

class Tree {

  Tree getRightChild() {
    // Assume this is already implemented
  }

  Tree getLeftChild() {
    // Assume this is already implemented
  }

  int count() {
    // Implement me
  }
}

我问这个问题的原因只是想看看正确的解决方案,从而衡量我的问题有多糟糕。

干杯, 托尼

【问题讨论】:

  • // 不……你没说拜托
  • @Tony 你能发布你想出的任何解决方案吗?如果我们指出您的设计存在的问题,而不是仅仅把正确的答案扔在那里,这可能对您更有价值。
  • 如果这是一个面试问题,面试官可能不仅希望看到“代码”(发布了许多很好的解决方案),而且还希望讨论递归与迭代(参见例如 David Hanak 的答案)和堆栈与堆相比。
  • int numberOfRightKids = getRightChild() != null ? getRightChild().count() : 0; int numberOfLeftKids = getLeftChild() != null ? getLeftChild().count() : 0 int me = 1;返回 numberOfLeftKids + numberOfRightKids + 我;
  • Java 没有 Functor 接口?哈哈。

标签: java recursion tree


【解决方案1】:
int count() {
  Tree right = getRightChild();
  Tree left = getLeftChild();
  int c = 1;                                      // count yourself!
  if ( right != null ) c += right.count();        // count sub trees
  if ( left != null ) c += left.count();          // ..
  return c;
}

【讨论】:

  • 将“返回计数”编辑为“返回 c”。 :-)
【解决方案2】:

一个简单的递归解决方案:

int count() {
   Tree l = getLeftTree();
   Tree r = getRightTree();
   return 1 + (l != null ? l.count() : 0) + (r != null ? r.count() : 0);
}

一个不那么简单的非递归的:

int count() {
    Stack<Tree> s = new Stack<Tree>();
    s.push(this);
    int cnt = 0;
    while (!s.empty()) {
        Tree t = s.pop();
        cnt++;
        Tree ch = getLeftTree();
        if (ch != null) s.push(ch); 
        ch = getRightTree();
        if (ch != null) s.push(ch); 
    }
    return cnt;
}

后者可能更节省内存,因为它用堆栈和迭代代替了递归。它也可能更快,但如果没有测量就很难判断。一个关键的区别是递归解决方案使用堆栈,而非递归解决方案使用堆来存储节点。

编辑:这是迭代解决方案的一个变体,它对堆栈的使用较少:

int count() {
    Tree t = this;
    Stack<Tree> s = new Stack<Tree>();
    int cnt = 0;
    do {
        cnt++;
        Tree l = t.getLeftTree();
        Tree r = t.getRightTree();
        if (l != null) {
            t = l;
            if (r != null) s.push(r);
        } else if (r != null) {
            t = r;
        } else {
            t = s.empty() ? null : s.pop();
        }
    } while (t != null);
    return cnt;
}

您是否需要更高效或更优雅的解决方案自然取决于您的树的大小以及您打算使用此例程的频率。记住 Hoare 所说的:“过早的优化是万恶之源。”

【讨论】:

  • 这不会用 Java 编译。不幸的是,您需要显式测试某些东西是否 == null,因此您需要在此处使用 l== null 和 r == null。
  • 谢谢!前段时间我积极使用Java。
  • 如何允许将空值压入堆栈,但仅在弹出的对象不为空时才增加计数器。您可以通过这种方式删除 1 if 语句,然后您也可以执行 s.push(getLeft()) 和 s.push(getRight())
  • 我相信这在 CPU 上会稍微贵一些(额外的推送/弹出),但它绝对是一种选择,而且,就像你说的,比你说的少一条线。 :-)
  • 我还以为是高德纳说的! :)
【解决方案3】:

我更喜欢这个,因为它写着:

左返回计数 + 右计数 + 1

  int count() {
      return  countFor( getLeftChild() ) + countFor( getRightChild() ) + 1;
  }
  private int countFor( Tree tree )  { 
       return tree == null ? 0 : tree.count();
  }

更倾向于文学编程。

顺便说一句,我不喜欢 Java 上如此常用的 getter/setter 约定,我认为使用 leftChild() 会更好:

  return countFor( leftChild() ) + countFor( rightChild() ) + 1;

就像 Hoshua Bloch 在这里解释的那样 http://www.youtube.com/watch?v=aAb7hSCtvGw 至少。 32:03

如果你能正确理解你的代码...

但是,我不得不承认 get/set 约定现在几乎是语言的一部分。 :)

对于许多其他部分,遵循此策略会创建自记录代码,这是一件好事。

托尼:我想知道,你在采访中的回答是什么。

【讨论】:

  • -1 分反对 JavaBean 习语,这已被证明非常有用。 (反正我给你加分了!)
  • @oxbow:是的,Java bean 模型从来没有达到预期的效果(即拥有真正独立的 bean),但这个成语非常有益,特别是在 web 开发中。
【解决方案4】:
return (getRightChild() == null ? 0 : getRightChild.count()) + (getLeftChild() == null ? 0 : getLeftChild.count()) + 1;

或者类似的东西。

【讨论】:

  • 在实践中我不会把它写成单行的,但事实上你可以说明简单性...... +1
  • 为什么不呢?无法击败一点三元滥用:-)
  • 如果你在采访中写到,我会很生气,没有留下深刻印象。
  • @Kip 如果我最终参加 Java 面试,我不会那么恼火 :-)
【解决方案5】:

这样的事情应该可以工作:

int count()
{
    int left = getLeftChild() == null ? 0 : getLeftChild().count();
    int right = getRightChild() == null ? 0 : getRightCHild().count();

    return left + right + 1;
}

【讨论】:

  • 记得自己数数:P
  • Pfft 你们在说什么,我总是加 1 ;-) 谢谢!
  • 谁说count应该返回null?问题说“相关方法”,所以它指的是“leftNode/rightNode”。这是你的观点还是遗漏了什么?
  • 我也认为他的意思是如果没有子节点,count() 方法应该返回 null。直到二读才清楚。当他说“相关方法”时,我认为他的意思是与这个问题相关的方法。
【解决方案6】:
class Tree {

  Tree getRightChild() {
    // Assume this is already implemented
  }

  Tree getLeftChild() {
    // Assume this is already implemented
  }

  int count() {
   return 1 
      + getRightChild() == null? 0 : getRightChild().count()
      + getLeftChild() == null? 0 : getLeftChild().count();
  }
}

【讨论】:

    【解决方案7】:

    您可以通过遍历许多ways 来计算这棵树。简单的前序遍历,代码将是(基于您定义的函数):

    int count() {
        count = 1;
        if (this.getLeftChild() != null)
            count += this.getLeftChild().count();
        if (this.getRightChild() != null)
            count += this.getRightChild().count();
        return count;
    }
    

    【讨论】:

      【解决方案8】:

      实现方法:

      public static int countOneChild(Node root)
      {
          ...
      }
      

      计算具有一个孩子的二叉树中内部节点的数量。将该函数添加到tree.java 程序中。

      【讨论】:

        【解决方案9】:

        我是通过前序递归做到的。尽管使用 localRoot 并不完全遵循采访格式,但我想你明白了。

        private int countNodes(Node<E> localRoot, int count) {
            if (localRoot == null) 
                return count;     
            count++; // Visit root
            count = countNodes(localRoot.left, count); // Preorder-traverse (left)
            count = countNodes(localRoot.right, count); // Preorder-traverse (right)
            return count;
        }
        
        public int countNodes() {
           return countNodes(root, 0);
        }
        

        【讨论】:

          【解决方案10】:

          这是一个标准的递归问题:

          count():
              cnt = 1 // this node
              if (haveRight) cnt += right.count
              if (haveLeft)  cnt += left.count
          return cnt;
          

          非常低效,如果树很深,那就是一个杀手,但这对你来说是递归......

          【讨论】:

          • 但这已经是最有效的了,不需要缓存更高级别的计数,对吧?
          • 算法很好,只是没有真正回答原始问题。另外,haveLeft 和 haveRight 虽然很明显,但并未在此处声明。
          【解决方案11】:
          int count()
          
          {
             int retval = 1;
              if(null != getRightChild()) retval+=getRightChild().count();
              if(null != getLeftChild()) retval+=getLeftChild().count();
              return retval;
          
          }
          

          上帝希望我没有犯错。

          编辑:我确实做到了。

          【讨论】:

          • 你的 if 语句需要明确检查 == null,否则这不适用于 Java。
          • 是的,记得我发帖的时候 :)
          【解决方案12】:

          当然,如果您想在计数时避免访问树中的每个节点,并且处理时间对您来说比内存更有价值,您可以通过在构建树时创建计数来作弊。

          1. 每个节点都有一个 int 计数, 初始化为一个,其中 表示节点数 以该节点为根的子树。

          2. 插入节点时,在 从递归插入返回 例程,增加计数 当前节点。

          public void insert(Node root, Node newNode) {
            if (newNode.compareTo(root) > 1) {
              if (root.right != null) 
                insert(root.right, newNode);
              else
                root.right = newNode;
            } else {
              if (root.left != null)
                insert(root.left, newNode);
              else
                root.left = newNode;
            }
            root.count++;
          }
          

          然后从任意点获取计数只需要查找 node.count

          【讨论】:

            【解决方案13】:

            我的第一次尝试没有添加任何新内容,但后来我开始怀疑递归深度以及是否可以重新排列代码以利用最新 Java 编译器的尾调用优化功能。主要问题是空测试 - 可以使用 NullObject 解决。我不确定 TCO 是否可以同时处理这两个递归调用,但它至少应该优化最后一个。

            static class NullNode extends Tree {
            
                private static final Tree s_instance = new NullNode();
            
                static Tree instance() {
                    return s_instance;
                }
            
                @Override
                Tree getRightChild() {  
                    return null;
                }  
            
                @Override
                Tree getLeftChild() {  
                    return null;
                }  
            
                int count() {  
                    return 0;
                }
            }
            
            int count() {      
                Tree right = getRightChild();      
                Tree left  = getLeftChild();      
            
                if ( right == null ) { right = NullNode.instance(); }
                if ( left  == null ) { left  = NullNode.instance(); }
            
                return 1 + right.count() + left.count();
            }   
            

            NullNode 的精确实现取决于 Tree 中使用的实现 - 如果 Tree 使用 NullNode 而不是 null,那么子访问方法可能应该抛出 NullPointerException 而不是返回 null。无论如何,主要思想是使用 NullObject 以尝试从 TCO 中受益。

            【讨论】:

            • 您必须在返回之前对每个计数调用的结果进行更多处理(将它们添加到返回寄存器的当前值),所以我认为您在这里没有适当的尾调用。 :-(
            • 白金天蓝色:感谢您的评论。这似乎是我的想法中的一个致命缺陷。如果 TCO 不能处理返回值的累积,那么我想我需要更好的优化:-(
            【解决方案14】:

            与二叉树相关的问题应该在面试中被预料到。我会说在下一次面试之前花点时间通过this链接。解决了大约14个问题。您可以看看解决方案是如何完成的。这将使您了解将来如何解决二叉树的问题。

            我知道您的问题是针对 count 方法的。这也在我提供的链接中实现

            【讨论】:

              【解决方案15】:
              class Tree {
              
                Tree getRightChild() {
                  // Assume this is already implemented
                }
              
              Tree getLeftChild() {
                  // Assume this is already implemented
               }
              
               int count() {
                  if(this.getLeftChild() !=null && this.getRightChild()!=null) 
                      return 1 + this.getLeftChild().count() + this.getRightChild().count();
                  elseif(this.getLeftChild() !=null && this.getRightChild()==null)
                      return 1 + this.getLeftChild().count();
                  elseif(this.getLeftChild() ==null && this.getRightChild()!=null)
                      return 1 + this.getRightChild().count();
                  else return 1;//left & right sub trees are null ==> count the root node
                }
              }
              

              【讨论】:

                猜你喜欢
                • 2019-05-12
                • 2019-05-05
                • 2013-02-12
                • 1970-01-01
                • 2016-05-20
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多