【问题标题】:C++ - Having trouble understanding binary tree recursive function (insertion)C++ - 无法理解二叉树递归函数(插入)
【发布时间】:2020-06-08 03:55:10
【问题描述】:

我有一棵看起来像这样的二叉树

struct Node 
{
  int key;
  double data;
  Node* right;
  Node* left;
};

我有这个“插入”功能用于插入新节点和构建树

void insert(Node*& p, int key, double to_be_inserted) 
{
  if (p == nullptr) 
  {
    p = new Node;
    p->key = key;
    p->data = to_be_inserted;
    p->left = nullptr;
    p->right = nullptr;
  }
  else 
  {
    if (p->key == key) 
    {
      p->data = to_be_inserted;
    }
    else 
    {
      Node*& newChild = (p->key > key) ? p->left : p->right;
      insert(newChild, key, to_be_inserted);
    }
  }
}

还有一个看起来像这样的主函数

int main(int argc, char ** argv) 
{
  Node* root = nullptr;
  insert(root, 11, 11);
  insert(root, 6, 6);
  insert(root, 4, 4);
  insert(root, 5, 5);
  insert(root, 8, 8);
  insert(root, 10, 10);
  insert(root, 19, 19);
  insert(root, 17, 17);
  insert(root, 43, 43);
  insert(root, 31, 31);
  insert(root, 49, 49);

  printTree(root, 0);
  return 0;
}

最终“打印出来”的树看起来像这样

(这个“打印输出”的意思是从左到右而不是从上到下阅读)

我不明白的是...insert 函数何时决定回溯(返回树)并构建右子树?

例如,如果我们查看main 中的insert(root, 5, 5)insert(root, 8, 8),为什么8 最终成为node 6 的子节点而不是node 5。根据insert 函数的逻辑,它应该一直沿着树向下,使8 成为node 5 的子节点……对吧?

我需要帮助来正确理解插入函数。我确信我误解了它的逻辑。

谢谢(很抱歉发了这么长的帖子)!

【问题讨论】:

  • insert 函数何时决定回溯 没有回溯,只有递归遍历才能找到正确的插入节点,这里:Node*& newChild = (p->key > key) ? p->left : p->right;。 (如果你监督它,那是一个条件运算符 - 类似于 if-else 但作为表达式。)
  • @Scheff 是的,我知道函数中没有回溯,这就是我感到困惑的原因。当insert(root, 8 ,8)main调用时,根据insert的逻辑,8不应该成为5的右孩子吗?它如何最终成为6 的正确孩子呢?这就是我不明白的。 insert 什么时候决定上树?
  • 8 最终成为 6 的(右)子节点,因为这个 "Node*& newChild = (p->key > key) ? p->left : p->right;"每次迭代您都在检查键值是否大于当前键值,然后向左移动,否则。所以这个条件让它在 6 点开始。
  • 它在最后一个else 分支中结束,在那里它确定要继续的左子或右子(使用上面提到的表达式),然后它为该子指针递归调用自身。 (...直到insert() 的递归调用之一没有在最后一个else 分支中结束。)
  • 这里只是尾递归,没有回溯

标签: c++ tree insert binary-tree recursive-backtracking


【解决方案1】:

当您插入 8 时,这三个看起来像这样(X 表示 NULL):

insert(root, 11, 11);
insert(root, 6, 6);
insert(root, 4, 4);
insert(root, 5, 5);

        11
    6         X
 4    X    X     X
   5       

现在,当您尝试插入 8( 在这一行 Node*& newChild = (p->key > key) ? p->left : p->right; 时,您首先检查 11 > 8 是否为真,因此这行告诉您您现在尝试在 left 是11 的子节点,恰好植根于6

此时插入函数自身重复,但这次三个的根不是11,而是686 大,因此它位于 6 的右侧。

因此这是插入8前后的情况。


        11                             11                
    6         X                    6         X           
 4    X    X     X   =====>     4    8    X     X        
   5                             5                     

顺便说一句, 这个函数有回溯。这是一个简单的递归函数。

【讨论】:

  • 阅读您的回答后,我认为我的困惑源于指针引用p(在insert)的工作原理。我假设每次调用insert(root, ..., ...) 时,当insert 中的p = new Node() 被执行时,root 指针就会发生变化。这意味着每次insert(root, ..., ...) 被称为树“获取”一个新根(root 指针指向一个新节点)。但你的意思是,事实并非如此?当ìnsert 被调用时,我似乎无法理解main 中的根指针的行为。
  • 我认为混淆是关于8 在三个中的哪个位置。这与实现的细节关系不大。我认为您的误解在于插入8之前和之后的三个布局如何。如果在插入8 之前树的布局是正确的,那么在遍历这三个级别的每个级别时,您唯一要问的问题是:我插入的元素是大于还是小于当前根?如果更大则向右,否则向左。就这么简单。
  • 很公平。看来我真的不太了解指针和指针引用是如何工作的(我是 C++ 新手)。我根本不明白p = new Node 行发生了什么。它为p 创建了一个新节点来指向,但p 是对root 的引用,而root 没有改变?而p 是本地的,所以一旦函数退出,树的所有内存都会丢失?一旦 main 调用了新的insert,它如何知道当前树的样子?由于范围,不应该从内存中擦除树吗?我想我只是很困惑,最好在新帖子中问这个问题?
猜你喜欢
  • 1970-01-01
  • 2012-11-16
  • 2020-07-24
  • 2014-12-03
  • 2015-06-16
  • 2020-08-04
  • 2012-10-29
  • 1970-01-01
相关资源
最近更新 更多