【问题标题】:How binary search tree insertion works using recursion?二叉搜索树插入如何使用递归进行?
【发布时间】:2015-05-20 14:12:05
【问题描述】:

我在理解二叉搜索树插入的递归部分时遇到了一些麻烦。

bstnode* insert(bstnode* root,int data)
{
    if(root==NULL){
        bstnode* tmp= new bstnode();
        tmp->data=data;
        tmp->left=tmp->right=NULL;
        return tmp;
    }

    if(data<root->data)     
        root->left = insert(root->left, data); 
    else 
        root->right = insert(root->right, data); //can't understand the logic here
    return root; 
}

/* consider following BST with their addresses[]:
              15 [100]
             /  \
           10    20 [200]
                   \
                   tmp [300]  
*/

据我所知root-&gt;right = insert(root-&gt;right, data); 应该将新创建的节点的地址存储在root-&gt;right 中,因此此代码不适用于高度>2 的树。
但是,它适用于任意数量的节点。
我必须在这里遗漏一些重要的细节。

假设我想在 BST 中插入 25,即 insert(root,25);
如 25>15:-

我在这里分解递归部分:
root-&gt;right = insert(root-&gt;right, 25);
15-&gt;right = insert(15-&gt;right,25); 在这里,再次递归调用它,因为 25>20
insert(root-&gt;right, 25) => root-&gt;right-&gt;right = insert(root-&gt;right-&gt;right, 25);
insert(15-&gt;right, 25) => 20-&gt;right = insert(20-&gt;right, 25);

insert(20-&gt;right,25)NULL,因此创建了一个新节点 tmp
insert(20-&gt;right,25); 返回 tmp

现在展开递归。

//20->right = insert(20->right, 25);

所以,

20-&gt;right=300(tmp地址);

//insert(15->right, 25) => 20->right
//and 15->right = insert(15->right,25);

15-&gt;right = 20-&gt;next;
因此15-&gt;right = [300] 地址。
要么 root-&gt;right = [300] 地址。
我的方法有什么问题?

再次概述递归调用:

15->right = insert(15->right,25);
15->right = [20->right = insert(20->right,25)]; //20->right is NULL so creating new node
15->right = [20->right=   300 address of tmp];
15->right = [20->right or 300]
15->right = [300] // but in reality 15->right = [200]

【问题讨论】:

  • root-&gt;right = root-&gt;right-&gt;right = tmp; - 第一个root-&gt;right = 来自哪里?好像是从哪里冒出来的……
  • 它来自条件 25>15
  • 错误...insert(root-&gt;right, 25) 返回什么?
  • 这是一个递归函数,在达到基本情况后,它返回设置为 root->right->right 的新节点,然后再次展开到 root->right
  • 杰瑞你忘记了当它展开时返回到上一个函数的指针已经从它上面级别的根目录更改了一个

标签: c++ c algorithm recursion data-structures


【解决方案1】:

您忘记了 root->right 是您作为 root 传递给函数的地址的 root->right。每个对 insert 的调用都会传入 root->right 或 root->left ,具体取决于您遍历的方式。

此说法不正确:

root->right = root->right->right = tmp;

一旦函数的迭代被返回,它就会从堆栈中删除,所以在这种情况下,我们有 3 次调用,我将用你的数字代替指针值。

insert(15->right,25)
insert(20->right,25) 

最后一个是空的,所以它使用 25 创建节点并将其返回到调用 insert(20->right,25) 并将 25 设置为 20->right 所以你有一个看起来像这样的树

/* consider following BST with their addresses[]:

              20 [200]
               \
                25 [300]  
*/

然后它将这棵树返回到调用 insert(15->right,25) 并将该树设置为我们刚刚返回的树,这样我们就得到了最终的树

/* consider following BST with their addresses[]:
          15 [100]
         /  \
       30    20 [200]
               \
                25 [300]  
*/

编辑:让我看看我是否可以澄清。让我们再看看你的树

/* consider following BST with their addresses[]:
          15 [100]
         /  \
       10    20 [200]
               \
               tmp [300]  
*/

我们想要插入 25,所以我们调用(我将再次使用树的那个节点处的值来表示我们正在传递的指针) 插入(15, 25)

然后在 root->right 上调用 insert,恰好是 20

insert(20, 25)

这会在右边的 20 个节点上再次调用 insert,这恰好是 null

insert(null,25)

现在让我们看看回报

insert(null,25) 返回一个包含 25 的节点,然后从堆栈中移除

 return 25;

insert(20,25) 返回一个 25 的节点。它将其右子节点设置为 25,如下所示

 20->right = 25;
 return 20;

现在我们回到原来的 insert(15,25) 调用。它返回了 20。所以它确实

15->right = 20;
return 15; 

【讨论】:

  • "然后将这个 "TREE" 返回到调用 insert(15->right,25)" no.. 第一个 20->right 存储 insert(25->right,25) 的地址然后 root->right 存储 20->right 的地址。又是 300。
  • 对不起,它应该是返回指向子树根的指针
  • 哦是的..是对的..返回是从最后一条语句返回根;这就是为什么它返回 root 而不是 root->right。我怎么可能没有注意到它..呼终于明白了。
  • 是的,你去。我很高兴我能以你能看到的方式把它拿出来。
【解决方案2】:

我认为您的困惑可能来自两个不同的来源。 首先,在您的代码中注释的树是不可能的。其次,只有在函数传入空指针时才会创建新节点。只有小于 15 的值才能向左移动。它会是这样的(取决于添加顺序):

   15
  /  \
     20
    /  \
       30

当你把 25 加到这个上时,结果如下:

   15
  /  \
     20
    /  \
        30
       /
      25

我将尝试逐步通过代码进行解释。在第一个函数调用上将 25 添加到原始树时,第一个节点不是 NULL 并且 25 > 15 所以

else
{ 
    root->right = insert(root->right, data);
}

被调用。这会递归调用相同的插入函数,但现在使用 20 节点作为比较。再次不是 null 并且 25 > 20 所以在右节点上调用 insert 如上所述。这再次调用递归函数,但现在在 30. 25

【讨论】:

  • 30 是一个拼写错误..我已经纠正了它。“然后所有函数都将这个新创建的节点返回到原始函数调用。”如何?你能举例说明一下吗?
  • 它不会一直返回到原始调用。您必须在每次插入调用迭代的堆栈中回溯。它返回指向更大子树的指针,直到它回到原来的调用
  • 当然。事后看来,我的措辞有点不清楚,在进一步检查后我会编辑删除。 return 实际上只是用于在正确的位置创建正确的节点时将其设置在右侧或左侧。
【解决方案3】:

在某种程度上你是对的。您永远不能拥有高度 > 2 的子树(不是树)。

在这段代码中,您永远不会有root-&gt;right-&gt;right,因为就代码而言,当您调用 root-&gt;left = insert(root-&gt;left, data);

(本地)根指针现在指向您刚刚插入的节点。 (本地)根指向root-&gt;left.

因此,您可以拥有任意高度的树(但是,本地根指针指向高度

【讨论】:

    【解决方案4】:

    注意insert() 总是返回作为参数传递给它的root,除非root == NULL。因此,您插入的新节点无法“向上走”。递归调用中发生的事情无关紧要——您总是返回与在非NULL 情况下传递的相同的root

    尽管有些人教递归的方式,我认为它有助于(无论如何对我的大脑)尝试展开递归,而是考虑逻辑是否有意义:

    如果您传递了一个非NULL 节点和data &lt; root-&gt;data,如果您执行root-&gt;left = insert(root-&gt;left, data) 并假设insert() 神奇地“正常工作”(即它插入@987654330),您会得到正确的结果吗? @ 进入左树并返回该树的根)?

    如果逻辑检查了左右情况,则考虑基本情况:如果传递了NULL 节点,你会返回正确的单元素树吗?

    如果逻辑也检查了基本情况,那么您知道您的代码必须是正确的,因为递归步骤是有意义的,并且您知道您将进入一个也有意义的基本情况(因为您最终会达到一个NULL 节点,当你沿着树走时)。

    【讨论】:

    • “递归调用中发生的事情无关紧要——你总是返回相同的根”不.. 当我使用 insert(root->right, data); 时函数不起作用;而不是 root->right = insert(root->right, data);
    • @JerryGoyal:如果您认为insert() 的工作是将data 作为第一个参数提供到您提供的树中并返回指向新树的指针,那么它会起作用。 (在这种情况下,实际上指针通常与旧的root-&gt;right 相同,但如果root-&gt;rightNULL 并插入新元素,则指针会有所不同。)
    • insert(tree, data)data 插入tree 并返回指向结果树的指针。要将新元素插入root 的右子树并使生成的树成为新的右子树,请执行root-&gt;right = insert(root-&gt;right, data)
    • 所以一般的方法是假设insert(tree, data) 会做到这一点,并围绕该假设构建递归案例。一旦递归案例“有意义”,您就转向基本案例 (root == NULL) 并确保它也可以工作。由于您的递归案例和基本案例在这一点上都有意义,因此没有代码可以中断的地方,因此它必须是正确的。如果这种思维方式对您没有帮助,我很抱歉,但这是我说服自己递归算法是正确的最简单的方法。
    • 如果这有点模糊,请考虑以下几点:如果基本情况是正确的,那么您知道该算法适用于高度为 1 的树。对于高度为 2 的树,您知道递归案例是正确的,因此它也必须工作(因为递归调用将您置于基本案例中)。对于高度为 3 的树,您知道高度为 2 的树有效。由于递归情况是正确的,这意味着高度为 3 的树也可以工作。你可以一直这样下去,因此该算法必须适用于所有树,无论高度如何。
    猜你喜欢
    • 1970-01-01
    • 2011-12-15
    • 1970-01-01
    • 2021-09-01
    • 1970-01-01
    • 1970-01-01
    • 2017-10-08
    • 2018-02-06
    相关资源
    最近更新 更多