C标准二叉树

时间:2012-12-29 20:24:52

标签: c data-structures tree binary-tree

对于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”。而且我没有丝毫暗示我弄错了。

有什么想法吗?

1 个答案:

答案 0 :(得分:8)

几个问题:

  1. 您尚未向我们展示类型nodeBT的定义,但您已声明auxrootparent是指向该类型的指针类型。

  2. 然后,您指定aux指向BinaryTree,即使它已声明指向nodeBT

  3. 您已分配给aux->drBinaryTree不属于nodeBT,因此我不能假设您在BinaryTree中输入nodeBT->st。您已分配给BinaryTree,这也不是nodeBT=root的一部分。

  4. 您尝试通过分配create来返回已解析的树。问题是C是一种“按值调用”语言。这意味着当您的nodeBT函数分配给create时,它只会更改其局部变量的值。 BinaryTree *nodeFromExpression(char const *expression) { 的来电者没有看到这种变化。因此调用者不会收到根节点。这可能就是为什么你的“内存不可读”错误;调用者正在访问一些随机内存,而不是包含根节点的内存。

  5. 如果使用称为“递归下降”的标准技术编写解析器,您的代码实际上将更容易理解。这是怎么回事。

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

    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++];
    

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

    $

    我第一次没有提到这个,但我们应该在这里处理你的 if (info == '$') { return (ParseResult){ .node = NULL, .offset = offset }; } 令牌为NULL:

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

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

    free

    请注意NULL如果您将)传递给 offset = rightResult.offset; if (expression[offset] != ')') { free(leftResult.node); free(rightResult.node); return (ParseResult){ .error = "right parenthesis expected", .offset = offset }; } ++offset; ,则无效,因此我们无需明确检查。

    现在我们需要在孩子们之后检查并使用leftChild

    rightChild

    我们需要设置本地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
        };
    }
    

    最后,我们可以返回成功的(ParseResult){ ... }

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

    我已将所有代码放在this gist中以方便copy'n'paste。

    更新

    如果您的编译器不喜欢 if (!expression[offset]) { return ParseResultMakeWithError("end of string where info expected", offset); } 语法,那么您应该寻找更好的编译器。该语法自1999年以来一直是标准的(§6.5.2.5复合文字)。当您正在寻找更好的编译器时,您可以像这样解决它。

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

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

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

    {{1}}

    {{1}}