簡體   English   中英

二進制搜索樹中的分段錯誤(核心已轉儲)

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

我正在嘗試實現一個R&B樹,但是首先我想要一個簡單的二叉樹(它不會在其葉子上保存內容),然后實現R&B屬性。 問題是我遇到無法解釋的細分錯誤。

程序如下:

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

我打開一個文件。 計算行數,因為我知道每一行都有一個數字。 使用以上信息創建一個數組。 然后,我創建根及其2個葉子。 然后,將數組的其余部分插入(出現分段錯誤)到我的數據結構中。 我認為問題出在功能上。

這是帶有數字的文本文件。

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

此處, node位於內存中的堆棧上,該堆棧將在函數退出時回收。 您正在返回一個不屬於您的內存的指針! 您將需要使用函數malloc() ,如下所示:

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

這將在nodeptr指向的堆上分配內存。 函數退出時不會回收此內存。 要回收此內存,您將需要調用free()

太多的錯誤和問題無法解決。

相反,讓我們看一個帶有錯誤檢查的正確示例,該示例以Graphviz DOT格式輸出生成的二進制搜索樹。

首先,您將希望將指針保留在結構的開頭,因為它們使用最多,並且需要正確對齊。 (如果您不將最大的成員放在首位,則編譯器可能會在您的結構中插入填充,從而浪費內存。否,不允許編譯器對C中的結構成員進行重新排序,因此它無法為您做到這一點。)

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

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

接下來,我們需要一個用於創建新節點的函數。 檢查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;
}

接下來,您需要一個將節點插入樹中的函數。 樹本身只是其根成員的句柄,並且由於新節點可以是新根,因此我們需要將指針傳遞給指向根元素的指針:

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;

上面的代碼只是完整性檢查,但我想將它們包括在內以確保完整性。 無論如何,如果樹為空,則根指針指向NULL指針,即*root == NULL 在這種情況下, leaf是樹中的新(唯一)節點:

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

否則,我們需要進入樹中。 我已經確定left的意思是“小於或等於” ,因為它很容易記住。 如果我們要向左走,而父節點的左節點為空,則將放置新的葉節點。 同樣,如果我們向右走,而父節點的右節點為空,則將葉子放在此處。 否則,我們下降。

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

要遍歷樹並以DOT語言打印其結構,只需要兩個函數:一個用於打印節點的遞歸函數,一個用於打印樣板並調用遞歸函數的主函數。 我將%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");
    }
}

在上方,指向父級的箭頭將不標記,向左子級的箭頭將標記為 ,向右子級的箭頭將標記為>靠近箭頭中間。

請注意,對於dot() ,第一個參數是文件將被發送到的流,第二個參數是指向根節點的指針。 因為我們不修改樹,所以指向根節點的指針就足夠了。 我們在這里不需要指向根節點的指針。

最后,我們需要從流(這里是標准輸入)中讀取值,並根據每個解析后的值構造一個樹節點,然后將其插入樹中。 絕對沒有理由兩次讀取文件,因為值的數量無關緊要:我們可以簡單地讀取值,直到無法讀取為止!

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

main()的后半部分只是將DOT格式的樹轉儲到標准輸出,然后退出(成功)。 退出之前不必釋放動態分配的內存,因為操作系統會自動執行此操作。

假設我們有一個輸入文件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

然后我們執行了程序,將該文件傳遞到其標准輸入,並out.dot輸出到out.dot (在Linux,Mac OS和BSD中,將上述C源代碼編譯為當前目錄中名為binary的可執行文件后,它就是./binary < in.txt > out.dot 。)

然后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=">" ];
}

並且如果可視化(例如使用dot -Tsvg out.dot > out.svg ),則將如下所示:

點生成的SVG圖像

如您所見,每個左孩子等於或小於其父對象,每個右孩子大於其父對象。 像這樣使用DOT語言輸出也是調試樹函數的絕佳方法。 您甚至可以使用節點形狀(例如橢圓形或矩形)來表示紅色/黑色-或使用實際顏色。 DOT屬性列表在這里

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM