[英]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
),則將如下所示:
如您所見,每個左孩子等於或小於其父對象,每個右孩子大於其父對象。 像這樣使用DOT語言輸出也是調試樹函數的絕佳方法。 您甚至可以使用節點形狀(例如橢圓形或矩形)來表示紅色/黑色-或使用實際顏色。 DOT屬性列表在這里 。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.