简体   繁体   English

在 C 中插入二叉搜索树

[英]Inserting into a binary search tree in C

I'm currently learning C and also some datastructures such as binary search trees etc. I have trouble understanding HOW exactly changing pointer values within a function works in some cases and in others doesn't... I'll attach some of my code I wrote.我目前正在学习 C 以及一些数据结构,例如二叉搜索树等。我无法理解如何在 function 中准确更改指针值在某些情况下有效,而在其他情况下则不...我将附上我的一些代码我写。 It's an insert function which inserts values in the correct places in the BST (it works as it should).这是一个插入 function 插入值在 BST 中的正确位置(它应该工作)。 I tried working with pointers to pointers to be able to change values withing a function.我尝试使用指向指针的指针,以便能够使用 function 更改值。 Even though it works, im still really confused why it actually does.即使它有效,我仍然很困惑它为什么会这样做。 I don't quite understand why my insert function actually changes the BST even though I only work with local variables (tmp, parent_ptr) in my insert function and I don't really dereference any pointers apart from " tmp = *p2r " in the insert function.我不太明白为什么我的插入 function 实际上会更改 BST,即使我在插入 function 中只使用局部变量(tmp,parent_ptr)并且我并没有真正取消引用除“tmp = *p2r”中的任何指针插入 function。

Thanks for helping out.感谢您的帮助。

#include <stdio.h>
#include <stdlib.h>


struct TreeNode{
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

struct TreeNode** createTree(){
    struct TreeNode** p2r;
    p2r = malloc(sizeof(struct TreeNode*));
    *p2r = NULL;
    return p2r;
}

void insert(struct TreeNode** p2r, int val){
    // create TreeNode which we will insert
    struct TreeNode* new_node = malloc(sizeof(struct TreeNode));
    new_node -> val = val;
    new_node -> left = NULL;
    new_node -> right = NULL;
    //define onestep delayed pointer
    struct TreeNode* parent_ptr = NULL;
    struct TreeNode* tmp = NULL;
    tmp = *p2r;
    // find right place to insert node
    while (tmp != NULL){
        parent_ptr = tmp;
        if (tmp -> val < val) tmp = tmp->right;
        else tmp = tmp->left;
    }
    if (parent_ptr == NULL){
        *p2r = new_node;
    }
    else if (parent_ptr->val < val){ //then insert on the right
        parent_ptr -> right = new_node;
    }else{
        parent_ptr -> left = new_node;
    }
}

int main(){
    struct TreeNode **p2r = createTree();
    insert(p2r, 4);
    insert(p2r, 2);
    insert(p2r, 3);
    return 0;
}

While the pointers themselves are indeed local variables, they point to a specific location in memory.虽然指针本身确实是局部变量,但它们指向 memory 中的特定位置。 When you dereference the pointer by using the -> symbol, you're basically accessing the memory where that exact variable to which the pointer is pointing to is stored.当您使用 -> 符号取消引用指针时,您基本上是在访问 memory ,其中存储了指针指向的确切变量。 This is why your changes are reflected outside the function as well.这就是为什么您的更改也反映在 function 之外的原因。

You basically told a local variable where your tree is stored, it helped with the insertion, and then it went out of scope.你基本上告诉了一个局部变量你的树存储在哪里,它有助于插入,然后它退出 scope。 The tree itself is not a local variable so the changes are reflected on it.树本身不是局部变量,因此更改会反映在其上。

I suggest reading up on how pointers work.我建议阅读指针的工作原理。

First of all, always remember one thing about the pointers, they store a memory address, rather than a value.首先,永远记住关于指针的一件事,它们存储一个 memory 地址,而不是一个值。 For example:例如:

int val = 5;
int copy = val;
int *original = &val;

printf("%d\n", val);
printf("%d\n", copy);
printf("%d\n", *original);

val = 8;

printf("%d\n", val);
printf("%d\n", copy);
printf("%d\n", *original);

On executing this piece of code, the output will be在执行这段代码时,output 将是

5
5
5
8
5
8

Notice, how on changing the value of val , the value of copy remains the same, and the value pointed the by original changes.注意,更改val的值时, copy的值保持不变,而 original 指向的值发生了变化。 This happens because the pointer original points to the memory location val .发生这种情况是因为指针original指向 memory 位置val

Now, coming to the insert function, although you are only working with local variables(tmp, parent_ptr), but remember they are pointer variables, they refer to a memory address.现在,进入插入 function,虽然您只使用局部变量(tmp,parent_ptr),但请记住它们是指针变量,它们引用 memory 地址。 So whenever within the loop, you traverse to tmp -> right or tmp -> left , you are actually jumping in memory from one location to another, in the correct order, that's why it works.因此,无论何时在循环中,您遍历tmp -> righttmp -> left ,您实际上是在 memory 以正确的顺序从一个位置跳到另一个位置,这就是它起作用的原因。 The following example will make it more clear.下面的例子会更清楚。

     56 (A)
     /    \
    /      \
  45 (B)  60 (C)

Consider the above BST, with the memory address in brackets.考虑上面的 BST,括号中是 memory 地址。 Let's insert 40 into this BST.让我们在这个 BST 中插入 40。 Initially, tmp will point to A, address of 56. Now 40 is less than 56, so tmp goes to left and now points to B, address of 45. Once, again it goes to left and now it is null.最初,tmp 将指向 A,地址为 56。现在 40 小于 56,因此 tmp 向左移动,现在指向 B,地址 45。再次向左移动,现在它是 null。 But by now, parent_ptr points to B. So the new node for 40 gets attached to left of B.但是现在,parent_ptr 指向 B。因此 40 的新节点附加到 B 的左侧。

      56 (A)
     /    \
    /      \
  45 (B)  60 (C)
  /
 /
40 (D)

Let's analyze the approach step by step.让我们逐步分析方法。

At first we consider the following simple program.首先我们考虑以下简单的程序。

#include <stdio.h>
#include <stdlib.h>

struct TreeNode{
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

void create( struct TreeNode *head, int val )
{
    head = malloc( sizeof( struct TreeNode ) );
    
    head->val   = val;
    head->left  = NULL;
    head->right = NULL;
}

int main(void) 
{
    struct TreeNode *head = NULL;
    
    printf( "Before calling the function create head == NULL is %s\n",
            head == NULL ? "true" : "false" );
            
    create( head, 10 );
    
    printf( "After  calling the function create head == NULL is %s\n",
            head == NULL ? "true" : "false" );
            
    return 0;
}

The program output is程序 output 是

Before calling the function create head == NULL is true
After  calling the function create head == NULL is true

As you can see the pointer head in main was not changed.如您所见, main 中的指针head没有改变。 The reason is that the function deals with a copy of the value of the original pointer head .原因是 function 处理原始指针head值的副本。 So changing the copy does not influence on the original pointer.因此更改副本不会影响原始指针。

If you rename the function parameter to head_parm (to distinguish the original pointer named head and the function parameter) then you can imagine the function definition and its call the following way如果将 function 参数重命名为head_parm (以区分原始指针名为head和 function 参数)那么您可以想象 ZC1C425268E68385D1AB5074C17A94F14 的定义和调用方式如下

create( head, 10 );

//...

void create( /*struct TreeNode *head_parm, int val */ )
{
    struct TreNode *head_parm = head;
    int val = 10;
    head_parm = malloc( sizeof( struct TreeNode ) );
    //...

That is within the function there is created a local variable head_parm that is initialized by the value of the argument head and this function local variable head_parm is changed within the function.也就是说,在 function 中创建了一个由参数 head 的值初始化的局部变量head_parm ,并且此 function 局部变量head_parm在 ZC1C425268E68385D1AB5074F 中更改。

It means that function arguments are passed by value.这意味着 function arguments 是按值传递的。

To change the original pointer head declared in main you need to pass it by reference.要更改 main 中声明的原始指针head ,您需要通过引用传递它。

In C the mechanism of passing by reference is implemented by passing an object indirectly through a pointer to it.在 C 中,通过引用传递的机制是通过指向它的指针间接传递 object 来实现的。 Thus dereferencing the pointer in a function you will get a direct access to the original object.因此,取消引用 function 中的指针,您将可以直接访问原始 object。

So let's rewrite the above program the following way.所以让我们用下面的方式重写上面的程序。

#include <stdio.h>
#include <stdlib.h>

struct TreeNode{
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

void create( struct TreeNode **head, int val )
{
    *head = malloc( sizeof( struct TreeNode ) );
    
    ( *head )->val   = val;
    ( *head )->left  = NULL;
    ( *head )->right = NULL;
}

int main(void) 
{
    struct TreeNode *head = NULL;
    
    printf( "Before calling the function create head == NULL is %s\n",
            head == NULL ? "true" : "false" );
            
    create( &head, 10 );
    
    printf( "After  calling the function create head == NULL is %s\n",
            head == NULL ? "true" : "false" );
            
    return 0;
}

Now the program output is现在程序 output 是

Before calling the function create head == NULL is true
After  calling the function create head == NULL is false            

In your program in the question you did not declare the pointer to the head node like in the program above在您的问题程序中,您没有像上面的程序那样声明指向头节点的指针

struct TreeNode *head = NULL;

You allocated this pointer dynamically.您动态分配了这个指针。 In fact what you are doing in your program is the following实际上,您在程序中所做的是以下内容

#include <stdio.h>
#include <stdlib.h>

struct TreeNode{
    int val;
    struct TreeNode *left;
    struct TreeNode *right;
};

void create( struct TreeNode **head, int val )
{
    *head = malloc( sizeof( struct TreeNode ) );
    
    ( *head )->val   = val;
    ( *head )->left  = NULL;
    ( *head )->right = NULL;
}

int main(void) 
{
    struct TreeNode **p2r = malloc( sizeof( struct TreeNode * ) );
    *p2r = NULL;
    
    printf( "Before calling the function create *p2r == NULL is %s\n",
            *p2r == NULL ? "true" : "false" );
            
    create( p2r, 10 );
    
    printf( "After  calling the function create *p2r == NULL is %s\n",
            *p2r == NULL ? "true" : "false" );
            
    return 0;
}

The program output is程序 output 是

Before calling the function create *p2r == NULL is true
After  calling the function create *p2r == NULL is false

That is compared with the previous program when you used the expression &head of the type struct TreeNode ** to call the function create you are now introduced an intermediate variable p2r which stores the value of the expression &head due to this code snippet与之前的程序相比,当您使用struct TreeNode **类型的表达式&head调用 function create时,您现在引入了一个中间变量p2r ,它存储表达式&head由于此代码片段的值

struct TreeNode **p2r = malloc( sizeof( struct TreeNode * ) );
*p2r = NULL;

That is early you called the function create like那是你早早叫 function 创建像

create( &head, 10 );

Now in fact you are calling the function like现在实际上您正在调用 function 之类的

struct TreeNode **p2r = &head; // where head was allocated dynamically
create( p2r, 10 );

The same takes place in your program.在您的程序中也会发生同样的情况。 That is within the function insert dereferencing the pointer p2r you have a direct access to the pointer to the head node那是在 function 插入取消引用指针 p2r 您可以直接访问指向头节点的指针

if (parent_ptr == NULL){
    *p2r = new_node;
    ^^^^ 
}

As a result the function changes the pointer to the head node passed by reference through the pointer p2r .结果,function 将指针更改为通过指针p2r通过引用传递的头节点。

The data members left and right of other nodes are also changed through references to them using the pointer parent_ptr其他节点左右的数据成员也通过使用指针parent_ptr对它们的引用而改变

else if (parent_ptr->val < val){ //then insert on the right
    parent_ptr -> right = new_node;
    ^^^^^^^^^^^^^^^^^^^  
}else{
    parent_ptr -> left = new_node;
    ^^^^^^^^^^^^^^^^^^
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM