简体   繁体   English

在C的二叉树中插入节点

[英]Inserting node in binary tree in C

I am learning C and found a basic tree implementation in my C book: 我正在学习C,并在我的C书中找到了一个基本的树实现:

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

struct tree_node {
  int data;
  struct tree_node *left_p, *right_p;
};

struct tree_node *t_search(struct tree_node *root, int v) {

  while (root) {
    printf("Looking for %d, looking at %d\n", v, root->data);
    if (root->data == v)
      return root;
    if (root->data > v)
      root = root->left_p;
    else
      root = root->right_p;
  }

  return 0;
}

int t_insert(struct tree_node **root, int v) {

  while (*root) {
    if ((*root)->data == v)
      return 1;
    if ((*root)->data > v)
      root = &((*root)->left_p);
    else
      root = &((*root)->right_p);
  }

  if ((*root = (struct tree_node *) malloc(sizeof(struct tree_node))) == 0)
    return 2;

  (*root)->data = v;
  (*root)->left_p = 0;
  (*root)->right_p = 0;

  return 0;
}

int main(void) {

  struct tree_node *tp, *root_p = 0;
  int i;

  t_insert(&root_p, 4);
  t_insert(&root_p, 2);
  t_insert(&root_p, 6);
  t_insert(&root_p, 1);
  t_insert(&root_p, 3);
  t_insert(&root_p, 4);
  t_insert(&root_p, 7);

  for (i = 0; i < 9; i++) {
    tp = t_search(root_p, i);
    if (tp)
      printf("%d found\n", i);
    else
      printf("%d not found\n", i);
  }

  return 0;
}

While the code seems to be straight forward, I am having a hard time to understand the t_insert function. 虽然代码看起来很简单,但是我很难理解t_insert函数。 Why does it take in a struct tree_node **root instead of struct tree_node *root ? 为什么要采用struct tree_node **root而不是struct tree_node *root t_serach is virtually the same code but uses only a pointer and not a pointer pointer. t_serach实际上是相同的代码,但仅使用一个指针,而不使用指针指针。 Maybe another example would explain the issue in a better way. 也许另一个例子可以更好地解释这个问题。

For what it's worth: I am from a Java background. 值得一提的是:我来自Java背景。

Note: Yes, I know the tree is not rebalanced upon insertion. 注意:是的,我知道插入时树不会重新平衡。

Some stuff we need: 我们需要一些东西:

#include <stdio.h>
struct thing {
   struct thing *next;
   int value;
   };

struct thing otherthing ={ NULL, 42};
struct thing something ={ &otherthing, 13};
int forty_two = 42;

    void do_the_int(int i);
    void do_the_intp(int *ip);
    void do_the_intpp(int **ipp);
    void do_the_structp(struct thing *p);
    void do_the_structpp(struct thing **pp);

A function can change its arguments, but the results will not be seen by the caller, so: 函数可以更改其参数,但是调用者将看不到结果,因此:

void do_the_int(int i) {
  i = 42;
}

is effectively a no-op. 实际上是无人值守。 To actually change something, the function needs a pointer to it, like in: 要实际更改某些内容,该函数需要一个指向它的指针,例如:

void do_the_intp(int *ip) {
  *ip = 42;
}

now, the value where ip points to is actually changed by the function. 现在, ip 指向的值实际上已由该函数更改。 Next, if you want to change a pointer the function will need a pointer to it: a pointer to pointer : 接下来,如果要更改指针,则该函数将需要一个指向它的指针指向pointer的指针

int forty_two = 42; // global (or static)
void do_the_intpp(int **ipp) {
  *ipp = &forty_two;
}

For other datatypes (than int), things are not different: if you want to change a struct, the function will need a pointer to struct , and if the function would need to change a pointer to struct it would need a pointer to pointer to struct . 对于其他数据类型(与int相比),没有什么不同:如果要更改结构,则该函数将需要指向struct指针 ,并且如果该函数需要将其更改为struct的指针,则将需要一个指向的指针结构 So 所以

void do_the_structp(struct thing *p) {
   p->value = 42;
}

will actually change something within the struct *p , and 实际上会在结构*p更改某些内容,并且

void do_the_structpp(struct thing **pp) {
   *pp = (*pp)->next;
}

Will actually change the pointer located at *pp . 实际上将更改位于*pp 的指针

Now, let's call them: 现在,我们称它们为:

int main(void) {
int zero=0
  , one=1
  , two=2;
int *the_p;
struct thing *tp, *cur;

  the_p = &two;
  tp = &something;

  printf("Before: zero=%d, one=%d, two=%d the_p=%p *the_p=%d\n"
        , zero, one, two, (void*) the_p,*the_p);
  for(cur=tp; cur; cur = cur->next) {
        printf("p=%p Next=%p Val=%d\n"
              , (void*) cur, (void*) cur->next, cur->value );
        }

  do_the_int(zero);
  do_the_intp(&one);
  do_the_intpp(&the_p);
  do_the_structp(tp);
  do_the_structpp( &tp);

  printf("After: zero=%d, one=%d, two=%d the_p=%p *the_p=%d\n"
        , zero, one, two, (void*) the_p,*the_p);
  for(cur=tp; cur; cur = cur->next) {
        printf("p=%p Next=%p Val=%d\n"
              , (void*) cur, (void*) cur->next, cur->value );
        }


  return 0;
}

Output: 输出:

Before: zero=0, one=1, two=2 the_p=0x7fff97a7db28 *the_p=2
p=0x601030 Next=0x601020 Val=13
p=0x601020 Next=(nil) Val=42
After: zero=0, one=42, two=2 the_p=0x601040 *the_p=42
p=0x601020 Next=(nil) Val=42

It passes in a double pointer because it needs to change pointers in the process. 它传入双指针,因为它需要在过程中更改指针。 All elements of a tree can be thought of as pointers which need to be changed when insert is called. 可以将树的所有元素视为指针,在调用insert时需要更改它们。 If you don't pass in a double pointer, what you're essentially doing is copying pointer as a local variable to the function and changes have no effect when the insert function exits. 如果您不传递双指针,那么您实际上要做的是将指针复制为函数的局部变量,并且在插入函数退出时更改无效。 Ignore me if I'm wrong. 如果我错了,别理我。

If you want to only read what is pointed by a pointer, your argument is of type foo* . 如果只想读取指针指向的内容,则参数的类型为foo* If you want to change what is pointed by a pointer, your argument is of type foo * . 如果要更改指针指向的内容,则参数的类型为foo * But there, you want to be able to change what you point to. 但是在那里,您希望能够更改指向的内容。 So, you need a pointer to your pointer, to be able to change its value. 因此,您需要一个指向您的指针的指针,以便能够更改其值。

In java, any method having as an argument a TreeNode is equivalent to a C function having as an argument a tree_node* ; 在Java中,任何以TreeNode作为参数的方法都等效于以tree_node*作为参数的C函数。 hence, if you have a method with that signature : 因此,如果您有一个带有该签名的方法:

// Java code
static void treeInsert (TreeNode t);

Within that method, you can access methods from t that can read its current status, you can even modify the state of t , and that state will be changed even once you left the method. 在该方法中,您可以从t访问可以读取其当前状态的方法,甚至可以修改t的状态,并且即使您离开该方法,该状态也将被更改。

But, if you're doing something like that 但是,如果您正在做类似的事情

t = null;

that won't have an effect once you leave the method. 离开方法后,该方法将无效。 That means 那意味着

// Java code
static void treeNull (TreeNode t) {
    t = null;  // Warning, local change only !
}

static void treeChange (TreeNode t) {
    t.changeState();
}

static void foo () {
    t = new TreeNode();
    t.treeChange();   // Ok, t has now an update state
    t.treeNull();     // Warning, t is not null here
}

You can't have the equivalent of a double pointer in java, so you can't make that work. 您在Java中不能拥有等效于双指针的功能,因此您无法使其正常工作。

In C, you can do this : 在C中,您可以执行以下操作:

// C code
void updateState(tree_node* t) {
    data = data + 1;  // This is nonsense, just to make the point
}

void localChangePointer(tree_node* t) {
    t = NULL;
}

void changePointer(tree_node** t) {
    free_and_clean(*t);
    *t = NULL;
}

void sillyStuff() {
    tree_node* t;
    // Initialize t
    ...

    updateState(t); // (*t).data changed
    localChangePointer(t);  // Warning : t is not null yet !
    changePointer(&t);      // This time, we gave the address of t, not t itself

    // Now t == NULL;
}

Maybe you will like such variant: 也许您会喜欢这样的变体:

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

typedef struct btree_node{
    int data;
    struct btree_node *left, *right;
} BTree;

// get tree leaf nearest to v
BTree *bt_get(BTree *root, int v){
    if(!root) return NULL;
    int D = root->data;
    if(D == v) return root;
    if(root->data > v) return root->left;
    else return root->right;
}

// find leaf with v or nearest to it (prev)
BTree *bt_search(BTree *root, int v, BTree **prev){
    if(!root){
        if(prev) *prev = NULL;
        return NULL;
    }
    BTree *p;
    do{
        p = root;
        root = bt_get(root, v);
    }while(root && root->data != v);
    if(prev) *prev = p;
    return root;
}

BTree *bt_create_leaf(int v){
    BTree *node = calloc(1, sizeof(BTree));
    if(!node) return NULL;
    node->data = v;
    return node;
}

// insert new leaf (or leave it as is, if exists)
// return root node
BTree *bt_insert(BTree *root, int v){
    BTree *closest, *node;
    node = bt_search(root, v, &closest);
    if(node) return node; // value exists
    if(!closest) // there's no values at all!
        return bt_create_leaf(v); // create root node
    // we have a closest node to our data, so create node and insert it:
    node = bt_create_leaf(v);
    if(!node) return NULL;
    int D = closest->data;
    if(D > v) // there's no left leaf of closest node, add our node there
        closest->left = node;
    else
        closest->right = node;
    return root;
}

int main(void){
    BTree *tp, *root_p = NULL;
    int i, ins[] = {4,2,6,1,3,4,7,14,0,12};
    for(i = 0; i < 10; i++)
        if(!(root_p = bt_insert(root_p, ins[i]))) err(1, "Malloc error"); // can't insert
    for(i = 0; i < 15; i++){
        tp = bt_search(root_p, i, NULL);
        printf("%d ", i);
        if(!tp) printf("not ");
        printf("found\n");
    }
    return 0;
}

// I was wrong in previous answer, correct //我之前的回答有误,正确

Here would be doing it without double pointers. 这将在没有双指针的情况下进行。 I don't see any difference. 我没什么区别。

int t_insert(struct tree_node *root, int v) {
  while (root) {
    if (root->data == v)
      return 1;
    if (root->data > v)
      root = &(root->left_p);
    else
      root = &(root->right_p);
  }

  if ((root = (struct tree_node *) malloc(sizeof(struct tree_node))) == 0)
    return 2;

  root->data = v;
  root->left_p = 0;
  root->right_p = 0;

  return 0;
}

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

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