Sho*_*ama 4 c tree binary-tree data-structures
就C编程而言,我几乎都是一个菜鸟.
尝试了几天从表单的表达式创建二叉树:
A(B,C(D,$))
Run Code Online (Sandbox Code Playgroud)
每个字母都是节点.
'(' 在我的树下(向右)下降.
',' 去我树的左侧分支
'$' 插入一个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;
}
Run Code Online (Sandbox Code Playgroud)
最后,在尝试访问新创建的树时,我不断得到"内存不可读的0xCCCCCC".而且我没有丝毫暗示我弄错了.
任何的想法 ?
几个问题:
你还没有告诉我们类型的定义nodeBT,但你已经声明aux,root以及parent将指向该类型.
然后你指定aux指向一个BinaryTree即使它被声明指向a nodeBT.
你指定的aux->dr,不是其中的一部分BinaryTree,所以我不能只假设你键入nodeBT你的意思BinaryTree.您指定nodeBT->st,这不是任何一部分BinaryTree.
您尝试通过分配返回已解析的树nodeBT=root.问题是C是一种"按值调用"语言.这意味着当你的create函数分配时nodeBT,它只是改变它的局部变量的值.调用者create看不到这种变化.因此调用者不会收到根节点.这可能就是为什么你的"内存不可读"错误; 调用者正在访问一些随机内存,而不是包含根节点的内存.
如果使用称为"递归下降"的标准技术编写解析器,您的代码实际上将更容易理解.这是如何做.
让我们编写一个从表达式字符串中解析一个节点的函数.天真地,它应该有这样的签名:
BinaryTree *nodeFromExpression(char const *expression) {
Run Code Online (Sandbox Code Playgroud)
要解析节点,我们首先需要获取节点info:
char info = expression[0];
Run Code Online (Sandbox Code Playgroud)
接下来,我们需要查看节点是否应该有子节点.
BinaryTree *leftChild = NULL;
BinaryTree *rightChild = NULL;
if (expression[1] == '(') {
Run Code Online (Sandbox Code Playgroud)
如果它应该有孩子,我们需要解析它们.这就是我们将"递归"放在"递归下降"的地方:我们nodeFromExpression再次调用来解析每个孩子.要解析左边的子节点,我们需要跳过前两个字符expression,因为那些是信息和(当前节点:
leftChild = nodeFromExpression(expression + 2);
Run Code Online (Sandbox Code Playgroud)
但是我们跳过多少来解析正确的孩子呢?我们需要跳过解析左子时我们使用的所有字符...
rightChild = nodeFromExpression(expression + ???
Run Code Online (Sandbox Code Playgroud)
我们不知道有多少个角色!事实证明,我们不仅需要nodeFromExpression返回它所解析的节点,还需要指出它消耗了多少个字符.所以我们需要更改签名nodeFromExpression以允许它.如果我们在解析时遇到错误怎么办?让我们定义一个结构,它nodeFromExpression可以用来返回它解析的节点,它消耗的字符数,以及它遇到的错误(如果有的话):
typedef struct {
BinaryTree *node;
char const *error;
int offset;
} ParseResult;
Run Code Online (Sandbox Code Playgroud)
我们会说如果error是非null,node则为null,并且offset是我们发现错误的字符串中的偏移量.否则,offset刚刚过去要解析的最后一个字符node.
所以,从重新开始,我们将nodeFromExpression返回一个ParseResult.它将整个表达式字符串作为输入,它将获取该字符串中开始解析的偏移量:
ParseResult nodeFromExpression(char const *expression, int offset) {
Run Code Online (Sandbox Code Playgroud)
现在我们有办法报告错误,让我们做一些错误检查:
if (!expression[offset]) {
return (ParseResult){
.error = "end of string where info expected",
.offset = offset
};
}
char info = expression[offset++];
Run Code Online (Sandbox Code Playgroud)
我第一次没有提到这个,但是我们应该$在这里处理你的令牌:
if (info == '$') {
return (ParseResult){
.node = NULL,
.offset = offset
};
}
Run Code Online (Sandbox Code Playgroud)
现在我们可以回到解析孩子了.
BinaryTree *leftChild = NULL;
BinaryTree *rightChild = NULL;
if (expression[offset] == '(') {
Run Code Online (Sandbox Code Playgroud)
所以,为了解析左边的孩子,我们再次递归地称呼自己.如果递归调用出错,我们返回相同的结果:
ParseResult leftResult = nodeFromExpression(expression, offset);
if (leftResult->error)
return leftResult;
Run Code Online (Sandbox Code Playgroud)
好的,我们成功解析了左子.现在我们需要检查并使用孩子之间的逗号:
offset = leftResult.offset;
if (expression[offset] != ',') {
return (ParseResult){
.error = "comma expected",
.offset = offset
};
}
++offset;
Run Code Online (Sandbox Code Playgroud)
现在我们可以递归调用nodeFromExpression解析正确的孩子:
ParseResult rightResult = nodeFromExpression(expression, offset);
Run Code Online (Sandbox Code Playgroud)
如果我们不想泄漏内存,现在的错误情况会更复杂一些.我们需要在返回错误之前释放左子项:
if (rightResult.error) {
free(leftResult.node);
return rightResult;
}
Run Code Online (Sandbox Code Playgroud)
请注意,free如果您通过它NULL,则不执行任何操作,因此我们无需明确检查.
现在我们需要)在孩子们之后检查并消费:
offset = rightResult.offset;
if (expression[offset] != ')') {
free(leftResult.node);
free(rightResult.node);
return (ParseResult){
.error = "right parenthesis expected",
.offset = offset
};
}
++offset;
Run Code Online (Sandbox Code Playgroud)
我们需要设置我们的本地leftChild和rightChild变量,而leftResult和rightResult变量仍然在范围:
leftChild = leftResult.node;
rightChild = rightResult.node;
}
Run Code Online (Sandbox Code Playgroud)
如果需要,我们已经解析了两个子节点,所以现在我们已经准备好构造我们需要返回的节点:
BinaryTree *node = (BinaryTree *)calloc(1, sizeof *node);
node->info = info;
node->left = leftChild;
node->right = rightChild;
Run Code Online (Sandbox Code Playgroud)
我们还有最后一件事要做:我们需要设置子项的father指针:
if (leftChild) {
leftChild->father = node;
}
if (rightChild) {
rightChild->father = node;
}
Run Code Online (Sandbox Code Playgroud)
最后,我们可以回归成功ParseResult:
return (ParseResult){
.node = node,
.offset = offset
};
}
Run Code Online (Sandbox Code Playgroud)
我已经将所有代码放在这个要点中,以便于复制'''''''''''''''''''
如果您的编译器不喜欢这种(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;
}
Run Code Online (Sandbox Code Playgroud)
然后,用对这些函数的调用替换有问题的语法.例子:
if (!expression[offset]) {
return ParseResultMakeWithError("end of string where info expected",
offset);
}
Run Code Online (Sandbox Code Playgroud)
if (info == '$') {
return ParseResultMakeWithNode(NULL, offset);
}
Run Code Online (Sandbox Code Playgroud)
| 归档时间: |
|
| 查看次数: |
854 次 |
| 最近记录: |