[英]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”。 而且我沒有絲毫暗示我弄錯了。
任何的想法 ?
幾個問題:
您沒有向我們展示nodeBT
類型的定義,但您已聲明aux
, root
和parent
是指向該類型的指針。
然后指定aux
指向BinaryTree
即使它被聲明指向nodeBT
。
分配給aux->dr
,這不是一部分BinaryTree
,所以我不能只是假設你鍵入nodeBT
,你的意思BinaryTree
。 分配給nodeBT->st
,這不是一部分BinaryTree
要么。
您嘗試通過分配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;
我們需要設置我們的本地leftChild
和rightChild
變量而leftResult
和rightResult
變量仍然在范圍:
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){ ... }
語法,那么您應該尋找更好的編譯器。 該語法自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.