【问题标题】:C standard binary treesC标准二叉树
【发布时间】:2012-12-29 20:24:52
【问题描述】:

在 C 编程方面,我几乎是个菜鸟。

几天来一直在尝试从以下形式的表达式创建二叉树:

A(B,C(D,$))

每个字母都是节点。

'(' 在我的树中向下一层(向右)。

',' 转到我树的左侧分支

'$' 插入一个 NULL 节点。

')' 表示升一级。

这是我在编码 2-3 天后得出的结论:

#define SUCCESS 0

typedef struct BinaryTree
{
char info;
BinaryTree *left,*right,*father;
}BinaryTree;



int create(BinaryTree*nodeBT, const char *expression)
{   
    nodeBT *aux;
    nodeBT *root;
    nodeBT *parent;
    nodeBT=(BinaryTree*) malloc (sizeof(BinaryTree));         
        nodeBT->info=*expression;
    nodeBT->right=nodeBT->left=NULL;
    nodeBT->father = NULL;

    ++expression;   
    parent=nodeBT;                                                 
    root=nodeBT;

    while (*expression)
        {if (isalpha (*expression))
            {aux=(BinaryTree*) malloc (sizeof(BinaryTree));
             aux->info=*expression;
             aux->dr=nodeBT->st=NULL;
             aux->father= parent;
             nodeBT=aux;}

        if (*expression== '(')
            {parent=nodeBT;
            nodeBT=nodeBT->dr;}

        if (*expression== ',')
            {nodeBT=nodeBT->father;
            nodeBT=nodeBT->dr;}

        if (*expression== ')')
            {nodeBT=nodeBT->father;
            parent= nodeBT->nodeBT;}

        if (*expression== '$')
            ++expression;

        ++expression;
    }

nodeBT=root;
return SUCCESS;
}

最后,在尝试访问新创建的树时,我不断收到“内存不可读 0xCCCCCC”。而且我没有丝毫暗示我在哪里弄错了。

有什么想法吗?

【问题讨论】:

  • 您缺少尝试访问树失败的代码
  • 欢迎来到 Stack Overflow。您需要提供 SSCCE (Short, Self-Contained, Correct Example) 以便人们可以提供帮助。这意味着代码应该编译、运行并显示问题。我们不应该猜测你做错了什么;代码应该在问题中。是的,它应该是最少的代码,但也有完整的帮助。
  • 大括号紧贴代码的格式(除了 while 循环的结尾)是一种不寻常的格式(请客气点)。我不建议长期使用它或将其用于已发布的代码——除非你找到或启动了一个项目标准。
  • 当字符串有 'node在“左侧节点”的左侧。这是让我感到困惑,还是让你感到困惑(或两者兼而有之)?

标签: c data-structures tree binary-tree


【解决方案1】:

几个问题:

  1. 您没有向我们展示nodeBT 类型的定义,但您已将auxrootparent 声明为指向该类型的指针。

    李>
  2. 然后您将 aux 分配为指向 BinaryTree,即使它已声明为指向 nodeBT

  3. 1234563您分配给nodeBT->st,它也不属于BinaryTree
  4. 您尝试通过分配nodeBT=root 返回已解析的树。问题在于 C 是一种“按值调用”的语言。这意味着当您的create 函数分配给nodeBT 时,它只是更改其局部变量的值。 create 的调用者没有看到这种变化。所以调用者没有收到根节点。这可能就是您收到“内存不可读”错误的原因;调用者正在访问一些随机内存,而不是包含根节点的内存。

如果您使用称为“递归下降”的标准技术编写解析器,您的代码实际上会更容易理解。方法如下。

让我们编写一个从表达式字符串中解析一个节点的函数。天真地,它应该有这样的签名:

BinaryTree *nodeFromExpression(char const *expression) {

要解析一个节点,我们首先需要得到该节点的info

    char info = expression[0];

接下来,我们需要看看节点是否应该有子节点。

    BinaryTree *leftChild = NULL;
    BinaryTree *rightChild = NULL;
    if (expression[1] == '(') {

如果它应该有孩子,我们需要解析它们。这就是我们将“递归”放在“递归下降”中的地方:我们只需再次调用nodeFromExpression 来解析每个孩子。要解析左孩子,我们需要跳过expression 中的前两个字符,因为它们是当前节点的信息和(

        leftChild = nodeFromExpression(expression + 2);

但是我们跳过多少来解析正确的孩子?我们需要跳过我们在解析左孩子时使用的所有字符……

        rightChild = nodeFromExpression(expression + ??? 

我们不知道那是多少个字符!事实证明,我们需要让nodeFromExpression 不仅返回它解析的节点,还需要一些指示它消耗了多少字符。所以我们需要更改nodeFromExpression 的签名以允许这样做。如果我们在解析时遇到错误怎么办?让我们定义一个结构,nodeFromExpression 可以使用它来返回它解析的节点、它消耗的字符数以及它遇到的错误(如果有的话):

typedef struct {
    BinaryTree *node;
    char const *error;
    int offset;
} ParseResult;

如果error 不为空,则node 为空,offset 是我们发现错误的字符串中的偏移量。否则,offset 刚好超过解析 node 所消耗的最后一个字符。

所以,重新开始,我们将让nodeFromExpression 返回一个ParseResult。它将把整个表达式字符串作为输入,并且它将在该字符串中开始解析的偏移量:

ParseResult nodeFromExpression(char const *expression, int offset) {

现在我们有了报告错误的方法,让我们做一些错误检查:

    if (!expression[offset]) {
        return (ParseResult){
            .error = "end of string where info expected",
            .offset = offset
        };
    }
    char info = expression[offset++];

我第一次没有提到这一点,但我们应该在这里处理你的$ NULL 令牌:

    if (info == '$') {
        return (ParseResult){  
            .node = NULL,
            .offset = offset   
        };
    }

现在我们可以回到解析孩子的问题了。

    BinaryTree *leftChild = NULL;
    BinaryTree *rightChild = NULL;
    if (expression[offset] == '(') {

所以,要解析左孩子,我们只需再次递归调用自己。如果递归调用出错,我们返回相同的结果:

        ParseResult leftResult = nodeFromExpression(expression, offset);
        if (leftResult->error)
            return leftResult;

好的,我们成功解析了左孩子。现在我们需要检查并使用孩子之间的逗号:

        offset = leftResult.offset;
        if (expression[offset] != ',') {
            return (ParseResult){
                .error = "comma expected",
                .offset = offset
            };
        }
        ++offset;

现在我们可以递归调用nodeFromExpression来解析右孩子:

        ParseResult rightResult = nodeFromExpression(expression, offset);

如果我们不想泄漏内存,现在的错误情况会稍微复杂一些。我们需要在返回错误之前释放左孩子:

        if (rightResult.error) {
            free(leftResult.node);
            return rightResult;
        }

请注意,如果您将NULL 传递给free,则它不会执行任何操作,因此我们不需要明确检查。

现在我们需要检查并消费孩子之后的)

        offset = rightResult.offset;
        if (expression[offset] != ')') {
            free(leftResult.node);
            free(rightResult.node);
            return (ParseResult){
                .error = "right parenthesis expected",
                .offset = offset
            };
        }
        ++offset;

我们需要设置我们的本地 leftChildrightChild 变量,而 leftResultrightResult 变量仍在范围内:

        leftChild = leftResult.node;
        rightChild = rightResult.node;
    }

如果需要的话,我们已经解析了两个子节点,所以现在我们准备好构造我们需要返回的节点:

    BinaryTree *node = (BinaryTree *)calloc(1, sizeof *node);
    node->info = info;
    node->left = leftChild;
    node->right = rightChild;

我们还有最后一件事要做:我们需要设置孩子的father 指针:

    if (leftChild) {
        leftChild->father = node;
    }
    if (rightChild) {
        rightChild->father = node;
    }

最后,我们可以返回一个成功的ParseResult

    return (ParseResult){
        .node = node,
        .offset = offset
    };
}

我已将所有代码放在this gist 中,方便复制粘贴。

更新

如果您的编译器不喜欢(ParseResult){ ... } 语法,您应该寻找更好的编译器。该语法自 1999 年以来一直是标准的(§6.5.2.5 复合文字)。当您正在寻找更好的编译器时,您可以像这样解决它。

首先,添加两个静态函数:

static ParseResult ParseResultMakeWithNode(BinaryTree *node, int offset) {
    ParseResult result;
    memset(&result, 0, sizeof result);
    result.node = node;
    result.offset = offset;
    return result;
}

static ParseResult ParseResultMakeWithError(char const *error, int offset) {
    ParseResult result;
    memset(&result, 0, sizeof result);
    result.error = error;
    result.offset = offset;
    return result;
}

然后,将有问题的语法替换为对这些函数的调用。例子:

    if (!expression[offset]) {
        return ParseResultMakeWithError("end of string where info expected",
            offset);
    }

    if (info == '$') {
        return ParseResultMakeWithNode(NULL, offset);
    }

【讨论】:

  • 感谢您的帮助。我会尽快试试你的要点。很抱歉对我的原始版本进行了稀少的翻译 - *aux、*root 和 *parent 属于 BinaryTree 类型。
  • 好的,试过了。我遇到的唯一问题是这种语法' return (ParseResult){ .node = node, .offset = offset };'我的编译器不允许,所以我只创建了一个 ParseResult tmp;并做了'tmp.node = node; tmp.offset = 偏移量;返回 (tmp);'
猜你喜欢
  • 1970-01-01
  • 2016-09-03
  • 1970-01-01
  • 2011-12-20
  • 1970-01-01
  • 2021-07-23
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多