【九度OJ】题目1009-二叉搜索树

时间:2023-03-09 22:39:58
【九度OJ】题目1009-二叉搜索树

题目

思路

  • 构建二叉搜索树,并保存先序遍历和中序遍历的序列在samplePreOrder,sampleInOrder
  • 每遇到一个新的序列,构建一棵二叉搜索树,保存先序遍历和中序遍历的序列testPreOrder,testInOrder
  • 需要树一棵,字符串四个

实现中遇到的错误

  1. 递归基直接返回return creat(x);,少了一个操作,root = creat(x); return root;
    • 原因:递归的层次没有区分清楚,错误地认为构造一个节点,然后返回给M->lchild,这个想法中的M是上一层递归中的节点,不应出现在这层的递归实例中。
    • 正确思路:本层递归实例,是最底层的递归,要往空树中插入节点,空树就是root,构造一个节点,要赋值给root,再返回root。
  2. 非递归基时,没有写return,致命错误,递归最重要的接口是传入的参数和返回的值。
    • 如果不指明非递归基的递归实例中的返回值,将只能在递归基中返回,即只返回叶节点,每层递归结束后,树都只有一个节点!
    • 一层正确的递归应该是,传入一棵树,添加一个节点,再返回增加了节点以后的树。
    • 结合上述正确情况,再仔细想想不写return的情况,新一层的递归实例,不会增加树的节点!整体递归结束后,树只有一个节点——最后插入的节点。
    • 对递归函数insert()的理解需要再进一步,每次传入一棵树的根的指针,最后返回这个指针。
  3. 出现一个“Runtime Error”
    • 参考序列为sampleString,待考察序列为testString。
    • 第一组testString读入并构造二叉搜索树后,没有清空loc,使第二组testString直接在前面的树中插入。
    • 当测试数据再多几组时,loc一直增加,使得保存遍历结果的字符串testInOrder超出了预设的长度,内存访问错误。
    • 在Visual Studio中,运行时不会检查字符串是否以0结束,strcmp()仍然会做比较,并给出NO的结果,似乎输出正确,实则访问了非法地址而没有提示。

总结

  • 递归
    • 在逻辑上,要区分开层次,每层递归调用不可互相干扰。
    • 表现在实现上,要写好每层递归的:
      • 传入的参数;
      • 返回的值;
      • 递归基的语句中是对谁操作,谁是指参与操作的变量是属于哪层递归调用(调用栈)的。
      • 函数体,是一次调用过程中,对形参的操作(语句中全是形参);上一层递归调用本层递归,将以实参的形式向本层传入数据,不要被实参干扰本层递归的思路。
  • 一两组数据测试的结果不可信,运行时的错误不易被发现,只看静态代码很难找出逻辑错误。当遇到运行时的错误,调试的能力至关重要
    • 合理设置断点,合理选择单步、逐过程、继续。
    • 合理选择监视哪些变量,程序运行到什么阶段,从哪里开始出错,全是从监视的变量中观察到的。

完整代码

#include <stdio.h>
#include <string.h>
using namespace std;
typedef struct BTNode {
BTNode * lchild;
BTNode * rchild;
char data;
}BTNode;
BTNode tree[15];
int loc;
int strIdx; //遍历树时存储到字符串的当前位置
//构造节点
BTNode * creat(char x) {
tree[loc].data = x;
tree[loc].lchild = tree[loc].rchild = NULL;
return &tree[loc++];
}
//将节点插入树,构建二叉搜索树
BTNode * insert(char x, BTNode * root) {
if (root == NULL) {
/*return creat(x); 【错误1】,到达递归基,直接返回一个指针
*到达递归基时insert(x, M->lchild)
*M无lchild,插入后要将M->lchild->data=x,
*而这里仅仅创造了一个新节点,没有把新创造的节点插入树中
*将新构造的节点插入树中的方法是将creat返回的指针赋值给root
*/
root = creat(x);
return root;
}
else if (x < root->data) {
//insert(x, root->lchild);到达递归基后返回一个指针,不保存这个返回值,错
root->lchild = insert(x, root->lchild);
}
else if (x > root->data) {
root->rchild = insert(x, root->rchild);
}
/*【错误2】没有写return,因此所有的返回值都是上面那个return返回的
*在main中,往树根插入节点,并将insert的返回值赋值给根节点
*若此处返回值总是上面那个return,则每次将新构造的节点返回给根节点
*树永远无法往下生长,每新插入一个节点,都把这个节点作为根
*/
return root;
}
//遍历树,将结果保存在字符串str中
void preOrder(BTNode * root, char str[]) {
str[strIdx] = root->data;
str[++strIdx] = 0; //每写入一个字符都添加结束符
if (root->lchild) {
preOrder(root->lchild, str);
}
if (root->rchild) {
preOrder(root->rchild, str);
}
}
void inOrder(BTNode * root, char str[]) {
if (root->lchild) {
inOrder(root->lchild, str);
}
str[strIdx] = root->data;
str[++strIdx] = 0;
if (root->rchild) {
inOrder(root->rchild, str);
}
}
void postOrder(BTNode * root, char str[]) {
if (root->lchild) {
postOrder(root->lchild, str);
}
if (root->rchild) {
postOrder(root->rchild, str);
}
str[strIdx] = root->data;
str[++strIdx] = 0;
}
int main()
{
int n;
while (scanf("%d", &n) != EOF) {
if (n == 0) break;
char sampleString[15];
char samplePreOrder[15];
char sampleInOrder[15];
char testString[15];
char testPreOrder[15];
char testInOrder[15];
char blank[5];
//gets_s(blank); //消除第一行的换行符的影响
//gets_s(sampleString);
scanf("%s", sampleString);
loc = 0;
BTNode * R;
//构造作为参照的二叉搜索树
R = NULL;
for (int i = 0; sampleString[i] != '\0'; i++) {
//insert(sampleString[i], R);这个不保存insert的返回值会使R一直就是空树
R = insert(sampleString[i], R);
}
strIdx = 0; //字符串下标回到0
preOrder(R, samplePreOrder);
strIdx = 0;
inOrder(R, sampleInOrder);
loc = 0; //将树清空
for (int j = 0; j < n; j++) { //循环n次,每次输出一个结果YES或NO
//构造测试数据组成的树,并存储遍历序列
//gets_s(testString);
scanf("%s", testString);
R = NULL;
//【错误3】这里忘记将树清空,如果n=2时,将把第二组testString继续往树中插入,从而造成数组越界
loc = 0;
for (int i = 0; testString[i] != '\0'; i++) {
R = insert(testString[i], R);
}
strIdx = 0;
preOrder(R, testPreOrder);
strIdx = 0;
inOrder(R, testInOrder);
//输出判断结果
if (strcmp(samplePreOrder, testPreOrder) == 0 && strcmp(sampleInOrder, testInOrder) == 0) {
printf("YES\n");
}
else {
printf("NO\n");
}
} //for
} //while
return 0;
}