在C中的二叉树中插入节点

时间:2013-04-07 09:37:03

标签: c binary-tree

我正在学习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;
}

虽然代码似乎很直接,但我很难理解t_insert函数。为什么选择struct tree_node **root代替struct tree_node *roott_serach实际上是相同的代码,但只使用指针而不是指针指针。也许另一个例子可以更好地解释这个问题。

它的价值:我来自Java背景。

注意:是的,我知道插入后树没有重新平衡。

5 个答案:

答案 0 :(得分:3)

我们需要的一些东西:

#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);

函数可以更改其参数,但调用者不会看到结果,因此:

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

实际上是无操作。要实际更改某些内容,该函数需要一个指向它的指针,如:

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

现在,ip 指向的值实际上已被函数更改。接下来,如果要更改指针,该函数将需要一个指向它的指针:指向指针的指针

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

对于其他数据类型(比int),情况并没有区别:如果要更改结构,该函数将需要一个指向struct 的指针,如果该函数需要更改指针要构造它需要一个指向struct 的指针的指针。所以

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

实际上会在结构*p

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

实际上会更改位于*pp指针

现在,我们打电话给他们:

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;
}

输出:

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

答案 1 :(得分:1)

它传递一个双指针,因为它需要在进程中更改指针。树的所有元素都可以被认为是在调用insert时需要更改的指针。如果你没有传入双指针,你实际上要做的是将指针作为局部变量复制到函数中,当插入函数退出时,更改不起作用。如果我错了,请忽略我。

答案 2 :(得分:0)

如果您只想读取指针指向的内容,则您的参数类型为foo*。如果要更改指针指向的内容,则参数类型为foo *。但在那里,你希望能够改变你指向的东西。因此,您需要一个指向指针的指针,以便能够更改其值。

在java中,任何具有TreeNode参数的方法等同于具有tree_node*参数的C函数;因此,如果你有一个带有该签名的方法:

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

在该方法中,您可以从t中访问可以读取其当前状态的方法,甚至可以修改t的状态,即使您离开该方法,该状态也会被更改。

但是,如果你正在做那样的事情

t = null;
离开方法后,

无效。这意味着

// 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
}

你不能在java中拥有等价的双指针,所以你无法做到这一点。

在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;
}

答案 3 :(得分:0)

也许你会喜欢这样的变体:

#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;
}

//我在上一个回答中错了,正确

答案 4 :(得分: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;
}