【问题标题】:Are there any "strategies" to tackling Binary Tree problems?有没有解决二叉树问题的“策略”?
【发布时间】:2013-08-24 14:56:01
【问题描述】:

我希望这是一个可以接受的问题。我理解递归的思维模式,我想先考虑基本情况,然后再考虑递归情况,但是对于一些更困难的 BST 问题,我只是空白,感觉就像在没有好的方向的情况下迷失了方向。

以链表为例,似乎有一种解决问题的模式,但 BT 似乎你知道或不知道。任何提示/指针?我似乎已经理解的唯一概念是,如果我正在处理空节点并且我想对它们或关于它们做一些事情,我会把它作为一个案例

if(root == null)
     //do something

或者如果我与空节点没有任何关系,那么我使用倒置的基本情况

if(root != null)
     //do stuff
else 
     //do nothing for null case

但即便如此,我也会不知所措。我想这是一个我被困住并且不知道如何解决的问题的例子。我不一定要寻找答案,只是寻找处理此类问题(以及常规二叉树问题)的潜在策略。


编写一个方法numberNodes,更改存储在二叉树中的数据,为每个节点分配从1开始的连续整数,以便前序遍历将产生按顺序(1、2、3等)的数字。 )。例如,给定由左下方的树引用的树,tree.numberNodes(); 的调用将覆盖现有数据,将节点值从 1 分配到 6,以便树的前序遍历将产生1, 2, 3, 4, 5, 6

你不能改变树的结构。您只是更改存储在数据字段中的值。您的方法应该返回树中有多少节点的计数。

假设您将此方法添加到IntTree 类中,定义如下:

 public class IntTree {
     private IntTreeNode overallRoot;
     ...
 }

再看一遍代码后,我想我应该使用我的int count 作为一种方法来确定我是前往左根还是右根,因为它是一个二叉搜索树,但我仍然无法实现这一点功能...啊编码块!

【问题讨论】:

  • 画出来,写下你的 Node 类并完成你需要做的事情

标签: java binary-tree preorder


【解决方案1】:

因为我没有很好地开始使用java。对于任何寻找 CPP 实现的人,我已经尝试过并且它在许多测试用例中都有效。

这是我的 CPP 实现

struct Node{
      int data;
      Node* left, *right;
}
    
int NumberNodes(Node *root){
       
      if(root == NULL)
              return 0;
      
      if(root -> left != NULL)
              root -> left -> data = root -> data + 1;
      int l = NumberNodes(root -> left);

      if(root -> right != NULL)
              root -> right -> data = max(root->data, l) + 1;
      int r = NumberNodes(root -> right);

      return max(l, max(r, root->data));
}

int main(){
      Node *root = new Node();    //Or provide the root of your    ///  binary tree as mentioned in problem. 
      root->data = 1;
      int count = NumberNodes(root);  // count has the number of nodes in tree. 
}

【讨论】:

    【解决方案2】:

    在考虑使用二叉树进行递归时,基本情况是一棵空树,因此您在正确的轨道上。另一个关键概念元素是考虑根、左子树和右子树(其中任何一个都可能为空)。

    所以我会像这样分解你的示例问题:

    • 如果树为空,则无事可做
    • 否则,将下一个数字分配给根(啊哈!-我需要将“下一个数字”传递给此方法。)
    • 在左子树上递归,比当前数字多传递一个。
    • 在右子树上递归,通过 . . .啊。我需要知道左子树处理后的下一个数字是多少。因此,我需要调用左子树来返回下一个数字(比分配的最后一个数字多一个)。这意味着我最好做同样的事情。这将是在右子树上递归后返回的数字。
    • 当我在根节点上调用这个方法时,它会在设置完所有值后返回下一个可用的数字。设置的节点数将比此少一。
    • 实际上,最好只返回最后使用的数字,而不是下一个可用的数字。然后我不需要一个单独的函数来从结果中减去一个。所以我最好修改之前的分析,说要传入方法的数字(以及返回的数字)应该是最后使用的数字(不是下一个可用的数字),并相应调整所有处理。李>

    这样,您就大致了解了该方法。下面是我的写法:

    public class IntTree {
        private IntTreeNode overallRoot;
    
        public int numberNodes() {
            return numberNodes(overallRoot, 0);
        }
    
        /** Helper function */
        private static int numberNodes(IntTreeNode node, int n) {
            if (node != null) {
                node.data = ++n; // preorder means we assign to node first
                n = numberNodes(node.left, n);
                n = numberNodes(node.right, n);
            }
            return n;
        }
    }
    

    【讨论】:

    • 第二个 numberNodes 函数必须是静态的吗?如果它不是静态的,会有什么东西坏掉吗?我知道静态意味着它不需要对类的引用,但是它仍然可以接受对树类的引用吗?
    • @randomUser47534 - 在这种情况下,它不必是静态的,但也没有充分的理由让它成为实例方法。如果它是从 static 上下文调用的(例如,IntTree 的另一个 static 方法),那么将其设为实例方法确实会破坏某些东西。另外,如果不是private,那么实例方法可能会被子类覆盖,从而导致意外行为; static 方法(private 与否)不能被覆盖(尽管它可以被隐藏)。
    【解决方案3】:

    首先,没有空节点。可以为 null 的是 rightleft 指针/引用。

    其次,有许多不同类型的二叉树。我的意思是本质上不同。这就是您看不到太多共性的原因之一。

    public class BTNode {
        int value;
        BTNode left;
        BTNode right;
    }
    
    public static int enumerate(BTNode startNode,int startNumber) {
        int currentNumber= startNumber ;
        if( startNode == null ) return currentNumber ;
        startNode.value= currentNumber ;    // also currentNumber++ and delete next instruction
        currentNumber++;
        currentNumber= enumerate(startNode.left,currentNumber) ;
        currentNumber= enumerate(startNode.right,currentNumber) ;    //  also together with return
        return currentNumber ;
    }
    

    计数与节点的值无关。这仅与他们的立场有关。左右才是最重要的。

    【讨论】:

    • 为什么根不能为空?你会如何表示一棵空树?
    • 当您说root= null ; 时,您将null 分配给不是根的变量,它包含对根的引用(如果存在的话)。另一种查看方式:节点是对象。对象不能本质上为空。 对对象的引用可以为空。
    • 如果前两个听起来过于依赖于特定的编程语言,第三种方式:二叉树是一种由节点组成的结构。每个节点都有一个值,0 或 1 个左孩子,0 或 1 个右孩子。如果存在null 节点,您可以访问它们的左右子节点。甚至将它们设置为其他节点。但是你会得到一棵树,它的 null 根不为空。没有任何意义。
    • 拥有一个名为root 的变量(字段?)并且不能说它是“树的根”和“根可能为空”似乎过于迂腐。是的,Java 中的所有非原始变量都是对对象的引用,而不是对对象的引用,但坚持区分是最尴尬的。甚至 Java 语言规范风格也会将 root 描述为“BTNode 类型”而不是“BTNode 类型引用”。
    • 您的解决方案确实导致所有节点都具有正确的数据字段。在我最初考虑这个问题时,我已经找到了一个类似的解决方案,除了在递归调用中我通过 currentNumber+1 而不是在它自己的行上增加 currentNumber。不用说它失败了。您的解决方案的唯一问题是我必须从公共方法中的返回值中减去 1,以便计数正确。考虑到 currentNumber 在返回调用之前递增是有道理的。谢谢!
    【解决方案4】:

    一般 - 画出它,编写你的 Node 类并完成你需要做的事情

    在这种情况下,有几个步骤,您基本上需要对其进行逆向工程。

    1. 搞清楚什么是前序遍历

    2. 画出树并用正确的数字标记每个节点,这样预购就会返回他们需要的东西。

    3. 完成您需要做的事情,使用 3 节点树更容易进行扩展。

    例如

       1
      / \ 
     2   3
    

    这就是你需要的

    所以你可以看到粗略的想法是

    Given X
    
      Set node.value = X
      X = Call on Left with X + 1    # add null check
      X = Call on Right with X + 1   # add null check
      return X
    

    并且您的 numberMethod() 将是一个包装器,它在检查空根之后在 X =1 的根节点上调用上述函数。

    【讨论】:

    • @MilanNovaković 啊有趣,你能详细说明为什么它不起作用吗?另外,您应该将其添加到问题中。 p.s. root 在这里是一个坏的变量名(或好的?)。它是一种样式选择,但我会按照上面的说明移动空检查。
    • 抱歉,我无法通过车辆发表评论。当我到达目的地时,我必须做出回应,这样我才能正确地格式化我的答案。
    • @MilanNovaković 不必费心在评论中格式化代码,如果您直接将其添加到您的问题中会更容易。也不要打字和开车:P
    • 好吧,我丢失了之前答案的代码,但它不起作用的原因是它为每个节点返回了错误的数据值。它在左侧正常工作,但右侧的任何偏差都包含错误的数据字段。 Veronica Cornejo 在下面发布的答案确实产生了正确的数据字段,但我不得不对我的公共方法进行轻微调整,从“currentNumber”中减去一个以获得正确的值返回。
    • @MilanNovaković 我可以看到差异,但我认为以我的方式进行空检查,这种方法也可以工作
    猜你喜欢
    • 2013-01-07
    • 1970-01-01
    • 2013-01-24
    • 2022-01-02
    • 2018-01-04
    • 2014-10-11
    • 2014-02-07
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多