【九度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;
} 
posted @ 2016-02-29 15:39  tambura  阅读(462)  评论(0编辑  收藏  举报