【问题标题】:C recursively build tree using structure pointerC使用结构指针递归构建树
【发布时间】:2015-02-23 18:14:00
【问题描述】:

我现在正在实施 Barnes-Hut Algorithms 来模拟 N 体问题。我只想问一下 building-tree 部分。

我做了两个函数来为它构建树。

我递归地构建树,并在构建时打印每个节点的数据,一切似乎都是正确的,但是当程序返回主函数时,只有树的根和根的子节点存储值。其他节点的值没有被存储,这很奇怪,因为我在递归期间打印了它们,它们应该已经被存储了。

这里有部分代码经过修改,我认为问题可能出在哪里:

#include<...>
typedef struct node{
    int data;
    struct node *child1,*child2;
}Node;
Node root; // a global variable
int main(){
    .
    set_root_and_build(); // is called not only once cuz it's actually in a loop
    traverse(&root);
    .
}

这里是函数 set_root_and_build():

我已将子指针设置为 NULL,但一开始没有显示。

void set_root_and_build(){
    root.data = ...;
    ..// set child1 and child2 =NULL;
    build(&root,...); // ... part are values of data for it's child 
}

然后构建:

void build(Node *n,...){
    Node *new1, *new2 ;
    new1 = (Node*)malloc(sizeof(Node));
    new2 = (Node*)malloc(sizeof(Node));
    ... // (set data of new1 and new2 **,also their children are set NULL**)
    if(some condition holds for child1){ // else no link, so n->child1 should be NULL
        build(new1,...);
        n->child1 = new1;
        //for debugging, print data of n->child1 & and->child2
    }
    if(some condition holds for child2){ // else no link, so n->child2 should be NULL
        build(new2,...);
        n->child1 = new2;
        //for debugging, print data of n->child1 & and->child2
    }
}

树中的节点可能有1~2个孩子,这里不是所有的都有2个孩子。

程序在build()函数递归时打印出正确的数据,但是当它返回主函数并调用traverse()时,由于分段错误而失败。

我尝试打印 traverse() 中的所有内容,发现只有 root 和 root.child1、root.child2 存储了我提到的值。

由于我必须多次调用build(),即使是并行调用,new1 和new2 也不能定义为全局变量。 (但我认为它们不会导致这里出现问题)。

有谁知道哪里出错了?

带有调试信息的遍历部分:

void traverse(Node n){
    ...//print out data of n
    if(n.child1!=NULL)
        traverse(*(n.child1))
    ...//same for child2
}

【问题讨论】:

  • 这应该标记为 C,而不是 C++
  • 是的 malloc() 是 C 而不是 C++
  • 谢谢大家,我会换标签的。但是我的代码是.cpp文件,这样用malloc还可以吗?
  • 能否把traverse()代码贴出来
  • 在 cpp 中使用 malloc() 是一种不好的做法。你可以改用new1=new Node;

标签: c pointers recursion tree structure


【解决方案1】:

当条件不成立时,您可能没有正确设置 n 的子代。你可能想要这个:

void set_root_and_build()
{
    root.data = ...;
    build(&root,...); // ... part are values of data for it's child 
}

void build(Node *n,...)
{
    n->child1 = n->child2 = NULL;

    Node *new1, *new2;
    new1 = (Node*) malloc(sizeof(Node));
    new2 = (Node*) malloc(sizeof(Node));

    // set data of new1 and new2 somehow (read from stdin?)

    if (some condition holds for new1) 
    {
        n->child1 = new1;
        build(n->child1,...);
        //for debugging, print data of n->child1
    }
    else
      free(new1);  // or whatever else you need to do to reclaim new1

    if (some condition holds for new2)
    {
        n->child2 = new2; 
        build(n->child2,...);
        //for debugging, print data of n->child2
    }
    else
      free(new2);  // or whatever else you need to do to reclaim new2
}

当然,您也应该检查 malloc() 的返回值并处理错误。

此外,您的遍历有点奇怪,因为它通过复制而不是引用进行递归。你这样做有充分的理由吗?如果没有,那么也许你想要:

void traverse(Node *n)
{
    ...//print out data of n
    if (n->child1 != NULL)
        traverse(n->child1)
    ...//same for child2
}

【讨论】:

  • 谢谢,我确实为每个新节点设置了默认值。你是对的,我总是分配两个子节点,但不确保它应该保留在树中。但我认为没关系......至于 malloc,我确实打印了其中的数据,所以我认为它有效。
  • @cshushu 这很好,但您不能无条件地将您的孩子与他们的父母联系起来。在您的代码中,移动 n->child1 = new1; n->child2 = new2;在 if(条件)之外。
  • 并非所有节点都有 2 个子节点,很抱歉我没有说明我的实际意思,我已经编辑了代码并添加了注释。
  • 孩子的病情不成立怎么办?您是否要释放它们,否则它们会丢失/泄漏?
  • 我只是离开它并没有将它链接到任何节点,我对其进行了修改以便它们被释放,但没有改变任何东西:(
【解决方案2】:

树遍历中的问题是您肯定会处理树,直到找到一个为 NULL 的节点指针。

不幸的是,当您创建节点时,这些节点既没有使用malloc() 也没有使用new 进行初始化(它将使用calloc() 进行初始化,但是在cpp 代码中的这种做法与malloc() 一样糟糕)。因此,您的遍历将继续在随机指针的梦幻世界中循环/递归。

我建议您利用 cpp 并将您的结构稍微更改为:

struct Node {   // that's C++:  no need for typedef
    int data;
    struct node *child1,*child2;
    Node() : data(0), child1(nullptr), child2(nullptr) {} // Makes sure that every created are first initalized
};

然后摆脱旧的 malloc。并构造代码以避免不必要的分配:

if(some condition holds for child1){ // else no link, so n->child1 should be NULL
    new1=new Node; // if you init it here, no need to free in an else !!
    build(new1,...);
    n->child1 = new1;
    ...
}
if (... child2) { ... }

但是请注意,分配有new 的指针应该使用delete 释放,并使用free() 进行注释。

编辑:你的代码sn-p不匹配:

traverse(&root);  // you send here a Node* 

void traverse(Node n){  // but your function defines an argument by value !
   ...
}

检查您没有忽略编译器发出的一些警告,并且您的代码中没有滥用强制转换。

【讨论】:

  • 非常感谢,我一分钟前刚试了C++,结果还是一样。
  • 我已经编辑了我的答案。你能按照建议检查你的编译器消息吗?
猜你喜欢
  • 1970-01-01
  • 2012-07-15
  • 2015-06-16
  • 2016-03-13
  • 1970-01-01
  • 2019-04-14
  • 1970-01-01
  • 1970-01-01
  • 2020-06-05
相关资源
最近更新 更多