【问题标题】:Insert function for BST with strings in C在 C 中插入带有字符串的 BST 函数
【发布时间】:2017-01-19 10:30:21
【问题描述】:

这是 BST 节点

struct BST_node{
    char *name1;
    char *data1;
    struct BST_node* left;
    struct BST_node* right;
};   

struct BST_node* Insert(struct BST_node *rootptr, datatype_t *d){
if(rootptr == NULL){
    char name[66];
    char data[1466];
    rootptr = (struct BST_node*)malloc(sizeof(struct BST_node));
    rootptr->name1 = d->name;
    rootptr->data1 = d->data;
    printf("%s 1\n", rootptr->name1);
    rootptr->left = rootptr->right = NULL; 
}

if(strcmp(rootptr->name1, d->name) < 0){
    printf("%s left", rootptr->name1);
    rootptr->left = Insert(rootptr -> left, d);
}
else if(strcmp(rootptr->name1, d->name) > 0){
    printf("right\n");
    rootptr->right = Insert(rootptr -> right, d);
}
else if(strcmp(rootptr->name1, d->name)==0){
    printf("duplicate\n");
}
return rootptr;
}

所以我正在使用文件 ptrs 扫描 CSV 文件并从 datatype_d 读取数据。 例如,这是 CSV 文件中数据的样子。 “姓名,资料。”

这就是我读取 csv 文件并从我的主函数调用插入函数的方式。

int main(int argc, char** argv)
{
char* eq_csv_file = NULL;
char* eq_csv_file1 = NULL;
char* read_mode="r";
char* write_mode="w+";
struct BST_node* root = NULL;
int index = 0;


if(argv[1]!=NULL && argv[2] != NULL )
{
    eq_csv_file=argv[1];
    eq_csv_file1 = argv[2];
}
else
{
    return EXIT_FAILURE;
}

FILE* Data_input = safe_open_file(eq_csv_file,read_mode);
FILE* Data_output = safe_open_file(eq_csv_file1, write_mode);

datatype_t **earth = (datatype_t**)malloc(MAX_NUM_LINE*sizeof(datatype_t *));
datatype_t *earth_one = read(Data_input);


while((earth_one) != NULL)
{

    earth[index] = earth_one;


    if(root != NULL){
    printf("%s\n", (root->name1));

    }
    root = Insert(root, earth[index]);

    index++;
    earth_one= read(Data_input);
}

现在,当我运行此代码以检查其是否正常工作时,它会打印出 csv 文件中除第一个数据之外的所有数据的 Duplicate。我根本不知道我到底在哪里犯了错误并更改了 rootptr 并使其等于 datatype_d ,它在检查 strcmp 时打印重复。

所以一个例子是我从一个 CSV 文件中读取这个。

“戴夫”、“在加州大学洛杉矶分校学习”
“约翰”、“在 Google 工作”
“迈克”、“学校老师”

所以我想将这些插入 BST,但由于某种原因,它没有跟踪头节点“Dave”。它从不去左子树或右子树,而是说当我使用 strcmp 时它们是重复的。

这是输出:
戴夫 1
约翰
重复
分段错误:11

这是我从主函数调用以读取 csv 文件的读取代码。

read(FILE* fp)
{
char name[65];
char data[1465];
if (fscanf(fp, "%[^,] %[^\n]", name, data) == 2) {
    datatype_t *d = (datatype_t*)malloc(sizeof(datatype_t));
    d->name = name;
    d->data = data;
    return d;
}
return NULL;
}

【问题讨论】:

  • 请发帖minimal reproducible example。相信我,这将为您和我们节省大量浪费的时间。
  • 什么是BST_node?您可以发布其余代码以提供完整的上下文吗?
  • if(rootptr == NULL){ ... return rootptr; }
  • 就像@BLUEPIXY 说的那样,或者在if (strcmp(rootptr-&gt;name1, d-&gt;name) &lt; 0) { 代码行前面放一个else。注意else if(strcmp(rootptr-&gt;name1, d-&gt;name)==0){中的测试是多余的;您之前已确定它既不小于也不大于零,因此它必须等于零而不第三次运行strcmp()。只需使用else(也可以将条件转换为else 块内的断言)。
  • @BLUEPIXY :在读取两个值后给出了 mea seg 错误。

标签: c string data-structures binary-search-tree strcmp


【解决方案1】:

此代码有效。主要更改是确保您在设置根节点后立即返回。 (请注意,在您的跟踪中,您会被告知您插入的第一个节点是“重复的”——那是因为您没有提前返回。)我在随后的 if 语句中使用了 else 而不是添加一个额外的return rootptr;,但这也可以。

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

typedef struct datatype_t
{
    char *name;
    char *data;
} datatype_t;

struct BST_node
{
    char *name1;
    char *data1;
    struct BST_node *left;
    struct BST_node *right;
};

struct BST_node *Insert(struct BST_node *rootptr, datatype_t *d);

struct BST_node *Insert(struct BST_node *rootptr, datatype_t *d)
{
    if (rootptr == NULL)
    {
        rootptr = (struct BST_node *)malloc(sizeof(struct BST_node));
        rootptr->name1 = d->name;
        rootptr->data1 = d->data;
        printf("%s 1\n", rootptr->name1);
        rootptr->left = rootptr->right = NULL;
    }
    else if (strcmp(rootptr->name1, d->name) < 0)
    {
        printf("%s left\n", rootptr->name1);
        rootptr->left = Insert(rootptr->left, d);
    }
    else if (strcmp(rootptr->name1, d->name) > 0)
    {
        printf("right\n");
        rootptr->right = Insert(rootptr->right, d);
    }
    else
    {
        assert(strcmp(rootptr->name1, d->name) == 0);
        printf("duplicate\n");
    }
    return rootptr;
}

static void BST_print_inorder(const char *tag, struct BST_node *node)
{
    if (tag != NULL)
        printf("%s:\n", tag);
    if (node != NULL)
    {
        BST_print_inorder(NULL, node->left);
        printf("%s (%s)\n", node->name1, node->data1);
        BST_print_inorder(NULL, node->right);
    }
}

int main(void)
{
    datatype_t earth[] =
    {
        { "Dave", "Studying at UCLA" },
        { "John", "Works at Google" },
        { "Mike", "School teacher" },
    };
    enum { NUM_LINES = sizeof(earth) / sizeof(earth[0]) };
    struct BST_node *root = NULL;
    for (int index = 0; index < NUM_LINES; index++)
    {
        if (root != NULL)
            printf("Root node: %s\n", (root->name1));
        root = Insert(root, &earth[index]);
        BST_print_inorder("Insert complete", root);
    }
    return 0;
}

示例运行:

Dave 1
Insert complete:
Dave (Studying at UCLA)
Root node: Dave
Dave left
John 1
Insert complete:
John (Works at Google)
Dave (Studying at UCLA)
Root node: Dave
Dave left
John left
Mike 1
Insert complete:
Mike (School teacher)
John (Works at Google)
Dave (Studying at UCLA)

您不能没有打印功能,也不能在插入循环的每次迭代中不调用它,直到您知道一切正常为止。

使用valgrind 运行可以让它在内存访问方面保持健康。我没有写无树函数。

请注意,此代码无需担心名称和数据值是如何分配的,因为它们是初始化数组的一部分。但是,在您的“真实”代码中,您需要确保每个名称和数据值都有自己的内存。


还在崩溃?

如果您在此之后仍然崩溃,那么您需要仔细查看 read() 代码 - 或其他支持代码。

请注意,read() 不是在类 Unix 系统上使用的好函数名;还有一个read() 系统调用。链接器会为您解决问题,以便库代码正常工作,但如果您的任何代码使用 read() 期望系统调用,那么获取您的函数将会令人惊讶。

来自read() 的未定义行为

read() 函数已发布为:

datatype_t *read(FILE* fp)
{
    char name[65];
    char data[1465];
    if (fscanf(fp, "%[^,] %[^\n]", name, data) == 2) {
        datatype_t *d = (datatype_t*)malloc(sizeof(datatype_t));
        d->name = name;
        d->data = data;
        return d;
    }
    return NULL;
}

您正在返回一个已分配的数据结构,但结构中包含的两个字符串是指向局部变量 namedata 的指针,一旦函数退出,它们就不再有效。您需要复制字符串。

另外,fscanf() 格式有点奇怪。它不单独读取逗号,因此逗号被解析为第二个扫描集的一部分。当然,格式中的空白意味着“跳过可选的空白”。因此,当第一个扫描集在逗号处停止时,空白不会更改任何内容,然后以逗号开头的换行符之前的数据被读入data。此外,第二个和后续名称以换行符开头。您需要一个格式字符串,例如:

" %[^,], %[^\n]"

扫描集、%c%n 转换规范是唯一不跳过前导空格的规范。

例如:

datatype_t *read(FILE* fp)
{
    char name[65];
    char data[1465];
    if (fscanf(fp, " %[^,], %[^\n]", name, data) == 2) {
        datatype_t *d = (datatype_t*)malloc(sizeof(datatype_t));
        d->name = strdup(name);
        d->data = strdup(data);
        return d;
    }
    return NULL;
}

(注意:用于交互使用的scanf()-family 格式字符串中的尾随空格是一种可用性灾难——永远不要使用它。)

使用修改后的 read():

修改 main
int main(void)
{
    datatype_t *earth;
    struct BST_node *root = NULL;
    while ((earth = read(stdin)) != NULL)
    {
        if (root != NULL)
            printf("Root node: %s\n", (root->name1));
        root = Insert(root, earth);
        BST_print_inorder("Insert complete", root);
    }

    BST_free(root);
    return 0;
}

BST_free() 是:

static void BST_free(struct BST_node *node)
{
    if (node != NULL)
    {
        BST_free(node->left);
        BST_free(node->right);
        free(node->name1);
        free(node->data1);
        free(node);
    }
}

Insert() 的先前代码中存在内存泄漏——实际上是其中两个。一个是针对每个添加的节点; datatype_t 结构在应该被释放时没有被释放(但成员仍在使用中;它们已被转移到树中)。另一种是发现重复项时;那么新的数据成员也需要被释放。

我还再次修改了main() 以模拟问题中代码的作用。

struct BST_node *Insert(struct BST_node *rootptr, datatype_t *d)
{
    if (rootptr == NULL)
    {
        rootptr = (struct BST_node *)malloc(sizeof(struct BST_node));
        rootptr->name1 = d->name;
        rootptr->data1 = d->data;
        rootptr->left = rootptr->right = NULL;
        printf("%s 1\n", rootptr->name1);
        free(d);
    }
    else if (strcmp(rootptr->name1, d->name) < 0)
    {
        printf("%s left\n", rootptr->name1);
        rootptr->left = Insert(rootptr->left, d);
    }
    else if (strcmp(rootptr->name1, d->name) > 0)
    {
        printf("right\n");
        rootptr->right = Insert(rootptr->right, d);
    }
    else
    {
        assert(strcmp(rootptr->name1, d->name) == 0);
        free(d->name);
        free(d->data);
        free(d);
        printf("duplicate\n");
    }
    return rootptr;
}

...

int main(void)
{
    struct BST_node *root = NULL;

    enum { MAX_NUM_LINE = 1000 };

    datatype_t **earth = (datatype_t **)malloc(MAX_NUM_LINE * sizeof(datatype_t *));
    assert(earth != NULL);

    datatype_t *earth_one;
    size_t index = 0;

    while ((earth_one = read(stdin)) != NULL)
    {
        earth[index] = earth_one;

        if (root != NULL)
            printf("Root node: %s\n", (root->name1));
        root = Insert(root, earth[index]);
        BST_print_inorder("Insert complete", root);
        index++;
        assert(index < MAX_NUM_LINE);
    }
    printf("%zu nodes\n", index);

    BST_free(root);
    free(earth);

    return 0;
}

这可以在 Mac OS X 10.11.6 上的 GCC 6.1.0 下干净地编译,使用:

$ gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes \
>     -Wold-style-definition ll23.c -o ll23 
$

它在valgrind 下干净地运行。

我使用了两个数据文件:

data.1:

Dave, Studying at UCLA
John, Works at Google
Mike, School teacher

data.2:

Dave, Studying at UCLA
John, Works at Google
Mike, School teacher
Jane, CEO of Humdinger Enterprises
Anne, Chief Dogsbody
Beryl, Tech Lead at Apple
Mina, Doctor at Kaiser Permanente
Dave, Transfer PhD to UCB

【讨论】:

  • 嘿乔纳森,非常感谢代码,尽管我仍然卡在 seg 错误上。知道为什么吗?
  • 我猜这与您的 earth 数组的内存管理有关。我不清楚为什么你需要树和数组。您通常使用其中一种,但不能同时使用两种。遛狗回来后我会看起来更努力。
  • 嗨,我找到了错误,看起来 fscanf 行没有扫描任何值。我尝试使用您之前指定的逗号,但它仍然向我抛出该错误!
  • 我注意到的一个细节是您的示例数据在文件中的字段周围有双引号,但在程序的输出中没有。这有什么意义吗?我即将添加另一个新的main() 和一个经过轻微修改的Insert()(确保正确释放内存),但这对我来说很干净。
  • 哦,不,双引号没有意义,我们不必包含它们。我会尝试相应地修改我的代码并回复您。再次感谢:-)
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2014-04-24
  • 1970-01-01
  • 2018-09-10
  • 1970-01-01
  • 2017-09-05
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多