简体   繁体   English

二进制搜索树中的分段错误(核心已转储)

[英]segmentation fault (core dumped) in binary search tree

I am trying to implement a r&b tree, but firstly I want a simple binary tree (which does not save content on its leaves) and then implement r&b properties. 我正在尝试实现一个R&B树,但是首先我想要一个简单的二叉树(它不会在其叶子上保存内容),然后实现R&B属性。 Problem is I get segmentation fault I cannot explain. 问题是我遇到无法解释的细分错误。

Program is as follows: 程序如下:

#include <stdio.h>
#include <stdlib.h>
#include <math.h>
#include <string.h>
#include <stdbool.h>

typedef struct node
{
    unsigned long int val;
    bool black;
    struct node* parent;
    struct node* lchild;
    struct node* rchild;
}mynode;

mynode* createNode(unsigned long int ival, mynode* father);
mynode* createLeaf(unsigned long int ival, mynode* father);
mynode* search (unsigned long int ival, mynode *root);
void insert ( unsigned long int ival, mynode *root);


int main()
{
    mynode root;
    mynode *rootptr;
    mynode *leafptr;
    FILE *fp;
    int ch;
    unsigned long long lines=0, i=0;
    unsigned long *myArr;
    unsigned long int ival;

   fp = fopen("integers.txt","r");
   if(fp == NULL)
   {
      printf("Error in opening file.");
      return(-1);
   }

    while(!feof(fp))
    {
    ch = fgetc(fp);
    if(ch == '\n')
    {
        lines++;
    }
    }
    lines++;
    printf("lines = %lu", lines);
    myArr = (unsigned long*)calloc(lines, sizeof(unsigned long));

    fseek(fp, 0, SEEK_SET);
    while(!feof(fp))
    {
          fscanf(fp, "%lu,", &myArr[i] ); // des ta pos k giati tou input.
          i++;
    }
    fclose(fp);

    root.val = myArr[0];
    root.parent = NULL;
    root.lchild = NULL;
    root.rchild = NULL;
    root.black = true;
    rootptr = &root;
    leafptr = createLeaf(rootptr->val, rootptr);
    rootptr->lchild = leafptr;
    leafptr = createLeaf(rootptr->val, rootptr);
    rootptr->rchild = leafptr;
    for(i=1; i<lines; i++)
    {
        ival = myArr[i];
        insert(ival, rootptr);
    }

    return 0;
}

mynode* createNode(unsigned long int ival, mynode* father)
{
  mynode* nodeptr;
  mynode node;
  nodeptr = &node;
  nodeptr->val = ival;
  nodeptr->lchild = NULL;
  nodeptr->rchild = NULL;
  nodeptr->parent = father;
  nodeptr->black = true;
  return nodeptr;
}

mynode* createLeaf(unsigned long int ival, mynode* father)
{
  mynode* nodeptr;
  mynode leaf;
  nodeptr = &leaf;
  nodeptr->val = ival;
  nodeptr->lchild = NULL;
  nodeptr->rchild = NULL;
  nodeptr->parent = father;
  nodeptr->black = true;
  return nodeptr;
}

mynode* search (unsigned long int ival, mynode *rootptr)
{
    mynode* myptr;

    myptr = rootptr;

    while ( ( (myptr->lchild) != NULL) && ( (myptr->rchild) != NULL))
    {
        if ( ival < myptr->val)
        {
            myptr = myptr->lchild;
        }
        else
        {
            myptr = myptr->rchild;
        }
    }
    return myptr;
    }

void insert (unsigned long int ival, mynode *root)
{
    mynode * current;
    mynode * leafptr;
    mynode * father;
    unsigned long int max, min;
    unsigned long int num;

    current = search(ival, root);
    num = current->val;
    if((current->val) == ival)
    {
        return ;
    }
    else
    {
        if(ival>(current->val))
        {
            max = ival;
            min = current->val;
        }
        else
        {
            max = current->val;
            min = ival;
        }
        father = current->parent;
        current = createNode(min, father);
        if(num == (father->lchild)->val)
        {
            father->lchild = current;
        }
        else
        {
            father->rchild = current;
        }
        leafptr = createLeaf(min, current);
        current->lchild = leafptr;
        leafptr = createLeaf(max, current);
        current->rchild = leafptr;
       return ;
    }
}

I open a file. 我打开一个文件。 Count the number of lines, because I know each line has one number. 计算行数,因为我知道每一行都有一个数字。 Create an array using the above information. 使用以上信息创建一个数组。 I then create the root and its 2 leaves. 然后,我创建根及其2个叶子。 And then I insert (there I get segmentantion fault) rest of the array to my data structure. 然后,将数组的其余部分插入(出现分段错误)到我的数据结构中。 I think problem lies in the functions. 我认为问题出在功能上。

Here is the text file with numbers. 这是带有数字的文本文件。

mynode* nodeptr;
mynode node;
nodeptr = &node;

Here node lives on the stack in memory that will be reclaimed at function exit. 此处, node位于内存中的堆栈上,该堆栈将在函数退出时回收。 You're returning a pointer to memory that you do not own! 您正在返回一个不属于您的内存的指针! You'll want to use the function malloc() , like so: 您将需要使用函数malloc() ,如下所示:

mynode* nodeptr = malloc(sizeof(mynode));

This will allocate memory on the heap that nodeptr will point to. 这将在nodeptr指向的堆上分配内存。 This memory will not be reclaimed on function exit. 函数退出时不会回收此内存。 To reclaim this memory, you will need to call free() . 要回收此内存,您将需要调用free()

Just too many bugs and issues to go thorough. 太多的错误和问题无法解决。

Let us instead look at a proper example, with error checking, that outputs the generated binary search tree in Graphviz DOT format. 相反,让我们看一个带有错误检查的正确示例,该示例以Graphviz DOT格式输出生成的二进制搜索树。

First, you'll want to keep the pointers at the beginning of the structure, because they are most used, and require proper alignment. 首先,您将希望将指针保留在结构的开头,因为它们使用最多,并且需要正确对齐。 (If you do not put the biggest members first, the compiler may insert padding into your structures, wasting memory. No, the compiler is not allowed to reorder the structure members in C, so it cannot do that for you.) (如果您不将最大的成员放在首位,则编译器可能会在您的结构中插入填充,从而浪费内存。否,不允许编译器对C中的结构成员进行重新排序,因此它无法为您做到这一点。)

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

struct node {
    struct node *parent;
    struct node *left;
    struct node *right;
    double       value;
};

Next, we need a function for creating a new node. 接下来,我们需要一个用于创建新节点的函数。 It is a good idea to check for malloc() returning NULL , and if so, abort the program: 检查malloc()返回NULL是一个好主意,如果是,则中止程序:

struct node *new_node(const double value)
{
    struct node *n;

    n = malloc(sizeof (struct node));
    if (!n) {
        fprintf(stderr, "Out of memory.\n");
        exit(EXIT_FAILURE);
    }

    n->parent = NULL;
    n->left = NULL;
    n->right = NULL;
    n->value = value;

    return n;
}

Next, you need a function that will insert a node into a tree. 接下来,您需要一个将节点插入树中的函数。 The tree itself is just the handle to its root member, and since the new node can be the new root, we need to pass a pointer to the pointer to the root element: 树本身只是其根成员的句柄,并且由于新节点可以是新根,因此我们需要将指针传递给指向根元素的指针:

void insert_node(struct node **root, struct node *leaf)
{
    struct node *parent;

    /* Make sure we have a pointer to the root pointer, and a leaf node. */
    if (!root) {
        fprintf(stderr, "insert_node(): root == NULL!\n");
        exit(EXIT_FAILURE);
    }
    if (!leaf) {
        fprintf(stderr, "insert_node(): leaf == NULL!\n");
        exit(EXIT_FAILURE);
    }

    /* Make sure leaf pointers are all NULL. */
    leaf->parent = NULL;
    leaf->left = NULL;
    leaf->right = NULL;

The above code is just sanity checks, but I wanted to include them for completeness. 上面的代码只是完整性检查,但我想将它们包括在内以确保完整性。 Anyway, if the tree is empty, the root pointer points to a NULL pointer, ie *root == NULL . 无论如何,如果树为空,则根指针指向NULL指针,即*root == NULL In this case, the leaf is the new (sole) node in the tree: 在这种情况下, leaf是树中的新(唯一)节点:

    /* Is this a new root node? */
    if (!*root) {
        /* Yes. */
        *root = leaf;
        return;
    }

Otherwise, we need to descend into the tree. 否则,我们需要进入树中。 I've decided that left means "less than or equal to" , because it is easy to remember. 我已经确定left的意思是“小于或等于” ,因为它很容易记住。 If we are going left, and parent's left node is empty, that is where we'll put the new leaf node. 如果我们要向左走,而父节点的左节点为空,则将放置新的叶节点。 Similarly, if we are going right, and parent's right node is empty, we put the leaf there. 同样,如果我们向右走,而父节点的右节点为空,则将叶子放在此处。 Otherwise we descend. 否则,我们下降。

    /* Find the parent node where leaf belongs. */
    parent = *root;
    while (1)
        if (parent->value >= leaf->value) {
            if (parent->left) {
                parent = parent->left;
                continue;
            }

            /* This belongs at parent->left. */
            parent->left = leaf;
            leaf->parent = parent;
            return;

        } else {
            if (parent->right) {
                parent = parent->right;
                continue;
            }

            /* This belongs at parent->right. */
            parent->right = leaf;
            leaf->parent = parent;
            return;
        }
}

To traverse the tree, and print its structure in DOT language, one needs just two functions: a recursive function to print the node, and a main function to print the boilerplate, and to call the recursive function. 要遍历树并以DOT语言打印其结构,只需要两个函数:一个用于打印节点的递归函数,一个用于打印样板并调用递归函数的主函数。 I use %p or node pointer value as the node identifiers, because it is simple and reliable: 我将%p或节点指针值用作节点标识符,因为它简单可靠:

static void dot_recurse(FILE *out, struct node *one)
{
    fprintf(out, "    \"%p\" [ label=\"%.3f\" ];\n", (void *)one, one->value);

    if (one->parent)
        fprintf(out, "    \"%p\" -> \"%p\";\n", (void *)one, (void *)(one->parent));

    if (one->left) {
        dot_recurse(out, one->left);
        fprintf(out, "    \"%p\" -> \"%p\" [ label=\"≤\" ];\n", (void *)one, (void *)(one->left));
    }

    if (one->right) {
        dot_recurse(out, one->right);
        fprintf(out, "    \"%p\" -> \"%p\" [ label=\">\" ];\n", (void *)one, (void *)(one->right));
    }
}

void dot(FILE *out, struct node *tree)
{
    if (out && tree) {
        fprintf(out, "digraph {\n");
        dot_recurse(out, tree);
        fprintf(out, "}\n");
    }
}

Above, arrows to parent will be unlabeled, arrows to left child will be labeled , and arrows to right child will be labeled > near the middle of the arrow. 在上方,指向父级的箭头将不标记,向左子级的箭头将标记为 ,向右子级的箭头将标记为>靠近箭头中间。

Note that for dot() , the first parameter is the stream the file will be emitted to, and the second parameter is a pointer to the root node. 请注意,对于dot() ,第一个参数是文件将被发送到的流,第二个参数是指向根节点的指针。 Because we do not modify the tree, the pointer to the root node suffices; 因为我们不修改树,所以指向根节点的指针就足够了。 we do not need a pointer to a pointer to the root node here. 我们在这里不需要指向根节点的指针。

Finally, we need to read values from a stream (here, standard input), and construct a tree node from each parsed value, and insert them to the tree. 最后,我们需要从流(这里是标准输入)中读取值,并根据每个解析后的值构造一个树节点,然后将其插入树中。 There is absolutely no reason to read the file twice, as the number of values is irrelevant: we can simply read values until we cannot! 绝对没有理由两次读取文件,因为值的数量无关紧要:我们可以简单地读取值,直到无法读取为止!

int main(void)
{
    struct node *tree = NULL;
    double       value;

    while (scanf(" %lf", &value) == 1)
        insert_node(&tree, new_node(value));

    /* Dump tree in DOT format. Use Graphviz to visualize the output. */
    dot(stdout, tree);

    return EXIT_SUCCESS;
}

The latter part of main() just dumps the tree in DOT format to standard output, and exits (successfully). main()的后半部分只是将DOT格式的树转储到标准输出,然后退出(成功)。 It is not necessary to free dynamically allocated memory prior to exiting, as the operating system will do that automatically. 退出之前不必释放动态分配的内存,因为操作系统会自动执行此操作。

Let's say we have an input file in.txt containing 假设我们有一个输入文件in.txt其中包含

4.695 5.108 3.518 4.698 8.496
7.956 9.435 5.341 0.583 7.074
7.661 5.966 0.557 4.332 1.436
6.170 7.936 4.630 7.694 0.220

and we executed our program, piping that file to its standard input, and piping the output to say out.dot . 然后我们执行了程序,将该文件传递到其标准输入,并out.dot输出到out.dot (In Linux, Mac OS, and BSDs, this would be just ./binary < in.txt > out.dot , after compiling the above C source to an executable named binary in the current directory.) (在Linux,Mac OS和BSD中,将上述C源代码编译为当前目录中名为binary的可执行文件后,它就是./binary < in.txt > out.dot 。)

The out.dot will then contain 然后out.dot将包含

digraph {
    "0x13dd020" [ label="4.695" ];
    "0x13dd080" [ label="3.518" ];
    "0x13dd080" -> "0x13dd020";
    "0x13dd1a0" [ label="0.583" ];
    "0x13dd1a0" -> "0x13dd080";
    "0x13dd260" [ label="0.557" ];
    "0x13dd260" -> "0x13dd1a0";
    "0x13dd3b0" [ label="0.220" ];
    "0x13dd3b0" -> "0x13dd260";
    "0x13dd260" -> "0x13dd3b0" [ label="≤" ];
    "0x13dd1a0" -> "0x13dd260" [ label="≤" ];
    "0x13dd2c0" [ label="1.436" ];
    "0x13dd2c0" -> "0x13dd1a0";
    "0x13dd1a0" -> "0x13dd2c0" [ label=">" ];
    "0x13dd080" -> "0x13dd1a0" [ label="≤" ];
    "0x13dd290" [ label="4.332" ];
    "0x13dd290" -> "0x13dd080";
    "0x13dd350" [ label="4.630" ];
    "0x13dd350" -> "0x13dd290";
    "0x13dd290" -> "0x13dd350" [ label=">" ];
    "0x13dd080" -> "0x13dd290" [ label=">" ];
    "0x13dd020" -> "0x13dd080" [ label="≤" ];
    "0x13dd050" [ label="5.108" ];
    "0x13dd050" -> "0x13dd020";
    "0x13dd0b0" [ label="4.698" ];
    "0x13dd0b0" -> "0x13dd050";
    "0x13dd050" -> "0x13dd0b0" [ label="≤" ];
    "0x13dd0e0" [ label="8.496" ];
    "0x13dd0e0" -> "0x13dd050";
    "0x13dd110" [ label="7.956" ];
    "0x13dd110" -> "0x13dd0e0";
    "0x13dd170" [ label="5.341" ];
    "0x13dd170" -> "0x13dd110";
    "0x13dd1d0" [ label="7.074" ];
    "0x13dd1d0" -> "0x13dd170";
    "0x13dd230" [ label="5.966" ];
    "0x13dd230" -> "0x13dd1d0";
    "0x13dd2f0" [ label="6.170" ];
    "0x13dd2f0" -> "0x13dd230";
    "0x13dd230" -> "0x13dd2f0" [ label=">" ];
    "0x13dd1d0" -> "0x13dd230" [ label="≤" ];
    "0x13dd200" [ label="7.661" ];
    "0x13dd200" -> "0x13dd1d0";
    "0x13dd320" [ label="7.936" ];
    "0x13dd320" -> "0x13dd200";
    "0x13dd380" [ label="7.694" ];
    "0x13dd380" -> "0x13dd320";
    "0x13dd320" -> "0x13dd380" [ label="≤" ];
    "0x13dd200" -> "0x13dd320" [ label=">" ];
    "0x13dd1d0" -> "0x13dd200" [ label=">" ];
    "0x13dd170" -> "0x13dd1d0" [ label=">" ];
    "0x13dd110" -> "0x13dd170" [ label="≤" ];
    "0x13dd0e0" -> "0x13dd110" [ label="≤" ];
    "0x13dd140" [ label="9.435" ];
    "0x13dd140" -> "0x13dd0e0";
    "0x13dd0e0" -> "0x13dd140" [ label=">" ];
    "0x13dd050" -> "0x13dd0e0" [ label=">" ];
    "0x13dd020" -> "0x13dd050" [ label=">" ];
}

and if visualized (using eg dot -Tsvg out.dot > out.svg ), will look like this: 并且如果可视化(例如使用dot -Tsvg out.dot > out.svg ),则将如下所示:

点生成的SVG图像

As you can see, each left child is equal to or less than its parent, and each right child is greater than its parent. 如您所见,每个左孩子等于或小于其父对象,每个右孩子大于其父对象。 Using DOT language output like this is an excellent method of debugging tree functions, too; 像这样使用DOT语言输出也是调试树函数的绝佳方法。 you can even use node shape (oval or rectangle, for example) to indicate red/black -- or use actual colors. 您甚至可以使用节点形状(例如椭圆形或矩形)来表示红色/黑色-或使用实际颜色。 The DOT attribute list is here . DOT属性列表在这里

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

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