簡體   English   中英

C標准二叉樹

[英]C standard binary trees

就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. 您沒有向我們展示nodeBT類型的定義,但您已聲明auxrootparent是指向該類型的指針。

  2. 然后指定aux指向BinaryTree即使它被聲明指向nodeBT

  3. 分配給aux->dr ,這不是一部分BinaryTree ,所以我不能只是假設你鍵入nodeBT ,你的意思BinaryTree 分配給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來解析每個子nodeFromExpression 要解析左子,我們需要跳過expression的前兩個字符,因為那些是信息和(當前節點的):

        leftChild = nodeFromExpression(expression + 2);

但是我們跳過多少來解析正確的孩子呢? 我們需要跳過解析左子時我們使用的所有字符...

        rightChild = nodeFromExpression(expression + ??? 

我們不知道有多少個角色! 事實證明,我們需要使nodeFromExpression返回它解析的節點, nodeFromExpression返回它消耗了多少個字符的一些指示。 所以我們需要更改nodeFromExpression的簽名以允許它。 如果我們在解析時遇到錯誤怎么辦? 讓我們定義一個結構, nodeFromExpression可以用來返回它解析的節點,它消耗的字符數,以及它遇到的錯誤(如果有的話):

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

我們會說如果error為非null,則node為null, 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++];

我第一次沒有提到這個,但我們應該在這里處理你的$ token為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來解析正確的子nodeFromExpression

        ParseResult rightResult = nodeFromExpression(expression, offset);

如果我們不想泄漏內存,現在的錯誤情況會更復雜一些。 我們需要在返回錯誤之前釋放左子項:

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

請注意,如果將NULL傳遞給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
    };
}

我已經將所有代碼放在這個要點中,以便於復制'''''''''''''''''''

UPDATE

如果您的編譯器不喜歡(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);
    }

暫無
暫無

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

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