【九度OJ】题目1078-二叉树遍历

题目

这道题和后面的两道题,题目1201题目1009,主要内容是对递归的实现,在逻辑上,递归是容易理解的,这种实现方式和我们思考的方式是相吻合的。但是在转换为计算机语言时,要明确告知计算机应该从哪里开始启用新一层的递归,带着什么参数去启用新一层的递归,最终返回什么值。所谓的一层递归,界限在哪里,递归函数体中的形参和逻辑流程中的实参要区分清楚。这些问题都是在实现递归时需要好好琢磨的。

思路

  • 手工解决重构二叉树时,先在“先序序列”中确定根,再在“中序序列”中确定左右子树。
  • 转换为代码,用递归来实现上述过程。用结构体存放树。 用结构体来定义节点,用静态数组存放树。
    • 之所以用静态数组来存放树,是因为这样简化了内存操作。标准做法是动态申请、动态释放,但是这样做需要更小心。
    • 按照初始状态实现第一个递归实例
    • “递归总是以一个函数的形态出现”,实现第一个递归实例时,并没有意识到这一点,于是先在main中写出了递归的函数体,而后才抽出来写成新的递归函数体build()
    • 按照思路中直观地实现,给函数传递的参数都是“字符串”,包括先序序列、中序序列、左子序列、右子序列。
    • 然后就不知道怎么进行递归了:怎样自己(build)调用自己(build)呢?被困在这一步。

实现递归

  • 递归函数build的参数必须十分讲究:这些参数必须便于描述清楚“某次调用,将操作哪些数据。”
    • 反思:上面被困住的原因是,build的参数选择不当,每次都要传入字符串,而且会拆分字符串,第二层递归build调用了子串A,那么余下的子串B怎么处理?暂存?
    • 正确做法是,原来的数据本身在内存中一个地方不能动(不可传入递归做修改),能很方便地移动的是下标、指针。
  • 递归的参数一般都是“下标”或者“指针”这类索引性质的变量,不能是数据本身!
    • 反思:递归函数的参数是变量,上面实现第一个递归实例时的想法并没有这么明确
    • 只是想着先把第一次递归的操作实现出来。
    • 下一步的思路应该是,将这次实现中用到的递归函数的参数都“抽象出来、提取出来”——形成形式参数
    • 递归函数体中的形式参数是每一层递归实例中要用来接收实参的变量,如何从具体实现中选取合适的提取对象(形参的候选者)是关键。
  • 当递归函数的形式参数是下标或指针时,调用新一层递归,主要是如何对这些下标和指针进行运算,来传入正确的实参
    • 先具体:从第一次递归实例进入第二次递归实例,需要如何移动下标,加减下标,可以用一些具体数字计算
    • 后抽象:将上面用的数字,逐个抽象为公式
    • 类似于数学归纳。

调试中的小问题

  • strlen()在标准C++中的头文件是cstring,在Virsual Studio中包含string也可使用,但在标准C++中却编译出错。
  • if判断中,是否添加一个等号,都至关重要,可能就是一个等号,逻辑出错,而且不是语法错误,要选择全面的测试数据、调试很久才能发现。

完整代码

#include <stdio.h>
#include <string>
//二叉树结点定义
typedef struct BTNode {
char data;
BTNode * lchild;
BTNode * rchild;
} BTNode;
//二叉树定义
/*若用静态数组就不需要这个
typedef struct BTree {
BTNode * root;
int nodeNum;
} BTree;
*/
BTNode tree[50];
int loc; //用于保存静态数组中已分配出去的元素的个数
char pre[30];
char in[30];
//char ltemp[30];这是旧思路中使用的
//char rtemp[30];
//该函数根据前序和中序将序列分解
/*递归写的不好,传入的参数不对,返回值也不对
*传入子串,修改子串,是直观的,与思路相吻合的,但是不利于递归
*下面用下标来作为参数传入该递归函数,以下标来表示子串,便于递归操作
void splice(char & preOrder, char & inOder, char & leftTemp, char & rightTemp) {
int i;
for (i = 0; inOder[i] != preOrder[0]; i++);
for (int j = 0; j < i; j++) {
leftTemp[j] = preOrder[j];
}
i++;
for (int j = 0; preOrder[i] != 0; i++, j++) {
rightTemp[j] = preOrder[i];
}
}
*/
//如果是动态申请内存就是为新节点申请内存,这里只需要loc++就表示占用预分配的数组中一个位置,产生一个新节点
BTNode * creat() {
tree[loc].lchild = tree[loc].rchild = NULL;
return &tree[loc++];
}
//构建二叉树
BTNode * build(int s1, int e1, int s2, int e2) {
BTNode * ret = creat(); //最终要返回的指针
ret->data = pre[s1];
int rootIdx;
//【错误1】for (int i = s2; i < e2; i++) { 若子树中仅有一个节点,没有=直接不进入循环,造成错误
for (int i = s2; i <= e2; i++){ //找到根节点在中序序列中的下标
if (pre[s1] == in[i]) {
rootIdx = i;
break;
}
}
if (s2 != rootIdx) {
ret->lchild = build(s1 + 1, s1 + (rootIdx - s2), s2, rootIdx - 1); //【重要】递归的核心在此处的参数如何计算
}
if (rootIdx != e2) {
ret->rchild = build(s1 + (rootIdx - s2) + 1, e1, rootIdx + 1, e2);
}
return ret;
}
//后序遍历
void postOrder(BTNode *root) {
if (root->lchild)
postOrder(root->lchild);
if (root->rchild)
postOrder(root->rchild);
printf("%c", root->data);
}
int main()
{
while (scanf("%s%s", pre, in) != EOF) {
int start1 = 0, start2 = 0;
int end1 = strlen(pre) - 1, end2 = strlen(in) - 1;
loc = 0;
BTNode * BTRoot = build(start1, end1, start2, end2); //一次调用就构建了二叉树
postOrder(BTRoot);
printf("\n");
}
return 0;
}
上一篇:MES制造执行系统启动篇


下一篇:Linux 注意