二叉搜索树插入如何使用递归工作?

How binary search tree insertion works using recursion?

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

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->right = insert(root->right, data); 应该将新创建的节点的地址存储在 root->right 中,因此此代码不适用于高度>2 的树。
但是,它对任何数量的节点都工作得很好。
我一定在这里遗漏了一些关键细节。

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

我在这里分解递归部分:
root->right = insert(root->right, 25);
或者15->right = insert(15->right,25);这里,递归调用一遍因为25>20
insert(root->right, 25) => root->right->right = insert(root->right->right, 25);
insert(15->right, 25) => 20->right = insert(20->right, 25);

insert(20->right,25)NULL 因此创建了一个新节点 tmp
insert(20->right,25); returns tmp.

现在展开递归。

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

所以,

20->right= 300 (临时地址);

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

15->right = 20->next;
因此 15->right = [300] 地址。
要么 root->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]

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

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

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

因此,你可以拥有任意高度的树(但是,本地根指针指向高度<2的子树)

请注意 insert() 总是 return 作为参数传递给它的 root 除非 root == NULL。因此,您插入的新节点无法进入"walk up the tree"。递归调用中发生的事情并不重要——你总是 return 与你在非 NULL 情况下传递的相同 root

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

如果您传递了一个非 NULL 节点和 data < root->data,如果您这样做 root->left = insert(root->left, data) 并假设 insert() 神奇地 "just works"(即,它将 data 插入到左树中,并且 return 是该树的根)?

如果逻辑检查了左右两种情况,那么您将考虑基本情况:如果传递给您 NULL 节点,您是否会 return 正确的单元素树?

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

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

   15
  /  \
     20
    /  \
       30

当你去添加 25 时,结果如下:

   15
  /  \
     20
    /  \
        30
       /
      25

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

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

被调用。这递归调用相同的插入函数,但现在使用 20 节点作为比较。再次不为 null 且 25 > 20 因此如上所述在右节点上调用插入。这再次调用递归函数,但现在在 30. 25<30 上,因此它调用左节点上的函数。在这一点上,函数被传递到一个 NULL 指针中,因为那里什么都没有,一个新节点被创建并放置在这个地方。

您忘记了 root->right 是您作为 root 传递给函数的地址的 root->right。根据您遍历的方式,每次调用 insert 都会在 root->right 或 root->left 中传递。

这种说法是错误的:

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

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

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

最后一个为空,因此它创建了带有 25 的节点,然后 return 将其发送到调用 insert(20->right,25) 并将 25 设置为 20->right 这样您就有了一棵树看起来像这样

/* consider following BST with their addresses[]:

              20 [200]
               \
                25 [300]  
*/

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

/* 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 上再次调用插入,该节点恰好为 null

insert(null,25)

现在让我们看看 returns

insert(null,25) return一个有25的节点,然后从栈中移除

 return 25;

insert(20,25) 获取它的 return 节点的 25。它将其右子节点设置为 25,如下所示

 20->right = 25;
 return 20;

现在我们回到最初的 insert(15,25) 调用。它获得了 returned 20。它确实如此

15->right = 20;
return 15;