03-树3 Tree Traversals Again(25分)
题目描述
An inorder binary tree traversal can be implemented in a non-recursive way with a stack. For example, suppose that when a 6-node binary tree (with the keys numbered from 1 to 6) is traversed, the stack operations are: push(1); push(2); push(3); pop(); pop(); push(4); pop(); pop(); push(5); push(6); pop(); pop(). Then a unique binary tree (shown in Figure 1) can be generated from this sequence of operations. Your task is to give the postorder traversal sequence of this tree.
Input Specification:
Each input file contains one test case. For each case, the first line contains a positive integer N (≤30) which is the total number of nodes in a tree (and hence the nodes are numbered from 1 to N). Then 2N lines follow, each describes a stack operation in the format: “Push X” where X is the index of the node being pushed onto the stack; or “Pop” meaning to pop one node from the stack.
Output Specification:
For each test case, print the postorder traversal sequence of the corresponding tree in one line. A solution is guaranteed to exist. All the numbers must be separated by exactly one space, and there must be no extra space at the end of the line.
Sample Input:
6
Push 1
Push 2
Push 3
Pop
Pop
Push 4
Pop
Pop
Push 5
Push 6
Pop
Pop
Sample Output:
3 4 2 6 5 1
题目链接
解题思路
1. 理解题意,获取隐含信息
- 本题的大意是一棵二叉树的中序遍历可以通过堆栈的入栈及出栈的操作来实现,我们需要了解使用堆栈进行非递归遍历的操作过程,可以参考 C实现二叉树及相关操作(非递归),在这篇文章中详细描述了这个过程;
- 本题需要根据输入的堆栈操作的顺序获取中序遍历的序列,只要知道中序遍历和先(后)序遍历就能唯一确定一颗二叉树,但是题目中隐含地告诉了先序遍历的序列,先序遍历的序列其实就是 Push 元素的顺序;
2. 数据的输入
第一行包含一个正整数N(≤30),表示树中的节点总数(因此节点编号从1到N)。接下来的2N行描述了栈操作,每行的格式为:“Push X”,其中X是要推入栈的节点的索引;或者"Pop",意味着从栈中弹出一个节点。我们需要通过输入得到这棵二叉树的中序遍历以及先序遍历序列。
- 对于先序遍历:很简单,元素 Push 的顺序就是先序遍历的顺序,使用 pre[ ] 来存储;
- 对于中序遍历:稍微复杂一点,与先序遍历相反,使用一个简易的栈来模拟数据输入的栈操作,最后元素 pop 的顺序就是中序遍历的顺序,使用 in[ ] 来存储;
具体实现如下:
int main() {
int N; // 节点个数
scanf("%d", &N);
int pre[N], in[N], stk[N];
// 初始化栈元素为 -1
memset(stk, -1, sizeof(int) * N);
// 代表输入字符串,Push 或者 Pop
char input[SIZE];
int preIdx = 0, inIdx = 0, top = -1;
for (int i = 0; i < 2 * N; i ++) {
scanf("%s", input);
if ( !strcmp(input, "Push") ) {
scanf("%d", &pre[preIdx]);
stk[++top] = pre[preIdx];
preIdx ++;
}
else if ( !strcmp(input, "Pop") ) {
in[inIdx++] = stk[top--];
}
}
/* 下面先不用管,后面再详细说明 */
BinTree *tree = CreateBinTree(pre, N, in, N);
postOrderTraversal(tree);
DestroyBinTree(tree);
return 0;
}
3. 二叉树的创建
在完成上面的步骤之后,就要利用得到的先序遍历序列和中序遍历序列来生成一棵二叉树了,这一步是最难的,我们自己想很容易得到,但是要将其转化为代码就困难得多。具体思路如下:
- 首先我们知道,先序遍历的第一个元素就是这棵树的根结点,查找这个元素在中序遍历序列中的位置,其左边就是这棵树的左子树的中序遍历序列,右边就是这棵树的右子树的中序遍历序列;
- 创建树的过程其实都是判断各个节点之间亲子关系的过程,对于每个节点都是判断其与上一个节点之间的关系;
这里看着会比较迷,这个过程确实难以用文字说明,下面我会展示详细的代码说明。
首先我们需要实现一个栈,存储的元素是二叉树的结点:如下
typedef struct Stack {
BinTree **data;
int top;
int capacity;
} Stack;
Stack *createStack(int size) {
Stack *obj = (Stack *)malloc(sizeof(Stack));
obj->data = (BinTree **)malloc(sizeof(BinTree *) * size);
for (int i = 0; i < size; i++) {
obj->data[i] = NULL;
}
obj->top = -1;
obj->capacity = size;
return obj;
}
bool isEmpty(Stack *stk) {
return stk->top == -1;
}
void Push(Stack *stk, BinTree *val) {
if (stk->top + 1 == stk->capacity) {
return;
}
stk->data[++stk->top] = val;
}
BinTree *Pop(Stack *stk) {
if (isEmpty(stk)) {
return NULL;
}
BinTree *temp = stk->data[stk->top--];
return temp;
}
BinTree *getTop(Stack *stk) {
if (isEmpty(stk)) {
return NULL;
}
return stk->data[stk->top];
}
void destroyStack(Stack *stk) {
free(stk->data);
free(stk);
}
下面是一个用于根据给定的前序(preorder)和中序(inorder)遍历序列创建二叉树的函数。以下是该函数操作的详细流程:
初始化:
- 分配内存创建二叉树的根节点
tree
。- 初始化
idx
为 0,这是前序遍历数组的索引。- 设置
tree
的左右子节点为NULL
。- 将前序遍历的第一个元素(根节点的值)赋值给
tree->val
。创建栈:
- 创建一个栈
stk
,用于辅助构建二叉树。初始化栈操作:
- 将根节点
tree
推入栈stk
。在中序数组中找到根节点的位置:
- 使用
findPos
函数在中序遍历数组in
中找到根节点的索引rootIdx
。遍历前序数组:
- 使用
for
循环遍历前序数组,从第二个元素开始(因为第一个元素是根节点)。为当前前序节点创建新节点:
- 对于前序数组的每个元素,分配内存创建一个新的
BinTree
节点node
。确定当前节点是左子节点还是右子节点:
- 使用
findPos
函数在中序数组中找到当前前序节点的位置pos
。- 如果
pos
小于rootIdx
,则当前节点是左子节点,将其赋值给temp->left
并更新temp
和rootIdx
。- 如果
pos
大于或等于rootIdx
,则当前节点是右子节点。处理右子节点:
- 如果当前节点是右子节点,弹出栈中的节点,直到找到一个节点,其在中序遍历中的索引大于或等于
pos
。- 将当前节点赋值给
temp->right
并更新temp
和rootIdx
。将新节点推入栈:
- 无论当前节点是左子节点还是右子节点,都将其推入栈
stk
。循环结束条件:
- 当前序遍历数组的所有元素都被访问过后,循环结束。
销毁栈:
- 使用
destroyStack
函数销毁栈stk
。返回根节点:
- 返回构建好的二叉树的根节点
tree
。
int findPos(int value, int *arr, int arrSize) {
for (int i = 0; i < arrSize; i ++) {
if ( arr[i] == value ) {
return i;
}
}
return -1;
}
BinTree *CreateBinTree(int pre[], int preSize, int in[], int inSize) {
// 创建结点存放 pre[] 的第一个元素,代表根结点
BinTree *tree = (BinTree *) malloc( sizeof(BinTree) );
int idx = 0, rootIdx = 0;
tree->left = tree->right = NULL;
tree->val = pre[idx];
// 创建栈并将结点入栈
Stack *stk = createStack(preSize);
BinTree *temp = tree;
Push(stk, temp);
// rootIdx 代表 pre[idx] 在 in[] 中的位置
rootIdx = findPos(pre[idx], in, inSize);
// 遍历整个先序序列
for ( idx = 1; idx < preSize; idx ++ ) {
// 查找当前元素在中序序列中的位置
int pos = findPos(pre[idx], in, inSize);
BinTree *node = (BinTree *) malloc( sizeof(BinTree) );
node->left = node->right = NULL;
node->val = pre[idx];
// 表示当前元素的位置在上一个元素的左边,直接尾插到上一个结点的左孩子位置
if ( pos < rootIdx ) {
temp->left = node;
temp = node;
rootIdx = pos;
Push(stk, node);
}
// 表示当前元素可能是栈中任意元素的右孩子,要判断其是那个结点的右孩子,就是将栈中元素一次弹出,判断当前元素的位置在哪一个元素的左边,表示该元素是栈中上一个元素的右孩子,将其尾插到这个结点的右孩子位置
else {
temp = Pop(stk);
while ( !isEmpty(stk) ) {
rootIdx = findPos(getTop(stk)->val, in, inSize);
if ( pos < rootIdx ) {
break;
}
temp = Pop(stk);
}
temp->right = node;
temp = node;
rootIdx = pos;
Push(stk, temp);
}
}
destroyStack(stk);
return tree;
}
这个函数的核心思想是利用前序和中序遍历的特点来重建二叉树。前序遍历的第一个节点总是树的根节点,而中序遍历中的根节点位置可以帮助我们确定树的结构。通过栈,我们可以在构建过程中跟踪当前的节点和待处理的子树。
图解
以题目测试用例进行说明:
- 找到根结点 1 并入栈
- pos < rootIdx,插入 2 作为 1 的左孩子结点
- 将 2 入栈,再次判断 pos 与 rootIdx 之间的关系
- 发现 pos > rootIdx
- 一直出栈,直到找到 pos < rootIdx 或栈为空为止
- 找到位置,插入右孩子
- 结点入栈
- pos > rootIdx,不断出栈,直到栈为空,此时 temp 为 1
- 插入 5 作为 1 的右孩子结点
- 将 5 入栈
此时 pos < rootIdx,直接插入作为 5 的左孩子结点
4. 结果的输出
题目要求输出这棵二叉树的后序遍历结果,但要求末尾不能有空格,这里我是用的是后序遍历的非递归方法,具体参见 C实现二叉树及相关操作(非递归) ,下面是详细代码:
void postOrderTraversal(BinTree *tree) {
Stack *stk = createStack(MAXSIZE);
Stack *stk_Bin = createStack(MAXSIZE);
BinTree *temp = NULL;
Push(stk, tree);
while ( !isEmpty(stk) ) {
temp = Pop(stk);
Push(stk_Bin, temp);
if (temp->left != NULL) {
Push(stk, temp->left);
}
if (temp->right != NULL) {
Push(stk, temp->right);
}
}
for (int i = 0; !isEmpty(stk_Bin); i ++) {
if ( i > 0 ) {
printf(" ");
}
printf("%d", Pop(stk_Bin)->val);
}
destroyStack(stk);
destroyStack(stk_Bin);
}
5. 二叉树的销毁
不要忘记销毁二叉树
void DestroyBinTree(BinTree *tree) {
if ( tree != NULL ) {
DestroyBinTree(tree->left);
DestroyBinTree(tree->right);
free(tree);
}
}
全部代码
下面附有测试用例
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <string.h>
#define SIZE 5
#define MAXSIZE 31
typedef struct BinTree {
int val;
struct BinTree *left;
struct BinTree *right;
} BinTree;
typedef struct Stack {
BinTree **data;
int top;
int capacity;
} Stack;
Stack *createStack(int size) {
Stack *obj = (Stack *)malloc(sizeof(Stack));
obj->data = (BinTree **)malloc(sizeof(BinTree *) * size);
for (int i = 0; i < size; i++) {
obj->data[i] = NULL;
}
obj->top = -1;
obj->capacity = size;
return obj;
}
bool isEmpty(Stack *stk) {
return stk->top == -1;
}
void Push(Stack *stk, BinTree *val) {
if (stk->top + 1 == stk->capacity) {
return;
}
stk->data[++stk->top] = val;
}
BinTree *Pop(Stack *stk) {
if (isEmpty(stk)) {
return NULL;
}
BinTree *temp = stk->data[stk->top--];
return temp;
}
BinTree *getTop(Stack *stk) {
if (isEmpty(stk)) {
return NULL;
}
return stk->data[stk->top];
}
void destroyStack(Stack *stk) {
free(stk->data);
free(stk);
stk->data = NULL;
stk = NULL;
}
int findPos(int value, int *arr, int arrSize) {
for (int i = 0; i < arrSize; i ++) {
if ( arr[i] == value ) {
return i;
}
}
return -1;
}
BinTree *CreateBinTree(int pre[], int preSize, int in[], int inSize) {
BinTree *tree = (BinTree *) malloc( sizeof(BinTree) );
int idx = 0, rootIdx = 0;
tree->left = tree->right = NULL;
tree->val = pre[idx];
Stack *stk = createStack(preSize);
BinTree *temp = tree;
Push(stk, temp);
rootIdx = findPos(pre[idx], in, inSize);
for ( idx = 1; idx < preSize; idx ++ ) {
int pos = findPos(pre[idx], in, inSize);
BinTree *node = (BinTree *) malloc( sizeof(BinTree) );
node->left = node->right = NULL;
node->val = pre[idx];
if ( pos < rootIdx ) {
temp->left = node;
temp = node;
rootIdx = pos;
Push(stk, node);
}
else {
temp = Pop(stk);
while ( !isEmpty(stk) ) {
rootIdx = findPos(getTop(stk)->val, in, inSize);
if ( pos < rootIdx ) {
break;
}
temp = Pop(stk);
}
temp->right = node;
temp = node;
rootIdx = pos;
Push(stk, temp);
}
}
destroyStack(stk);
return tree;
}
void postOrderTraversal(BinTree *tree) {
Stack *stk = createStack(MAXSIZE);
Stack *stk_Bin = createStack(MAXSIZE);
BinTree *temp = NULL;
Push(stk, tree);
while ( !isEmpty(stk) ) {
temp = Pop(stk);
Push(stk_Bin, temp);
if (temp->left != NULL) {
Push(stk, temp->left);
}
if (temp->right != NULL) {
Push(stk, temp->right);
}
}
for (int i = 0; !isEmpty(stk_Bin); i ++) {
if ( i > 0 ) {
printf(" ");
}
printf("%d", Pop(stk_Bin)->val);
}
destroyStack(stk);
destroyStack(stk_Bin);
}
void DestroyBinTree(BinTree *tree) {
if ( tree != NULL ) {
DestroyBinTree(tree->left);
DestroyBinTree(tree->right);
free(tree);
}
}
int main(int argc, const char *argv[]) {
int N;
scanf("%d", &N);
int pre[N], in[N], stk[N];
memset(stk, -1, sizeof(int) * N);
char input[SIZE];
int preIdx = 0, inIdx = 0, top = -1;
for (int i = 0; i < 2 * N; i ++) {
scanf("%s", input);
if ( !strcmp(input, "Push") ) {
scanf("%d", &pre[preIdx]);
stk[++top] = pre[preIdx];
preIdx ++;
}
else if ( !strcmp(input, "Pop") ) {
in[inIdx++] = stk[top--];
}
}
BinTree *tree = CreateBinTree(pre, N, in, N);
postOrderTraversal(tree);
DestroyBinTree(tree);
return 0;
}
// inorder 3 2 4 1 6 5
// root 1
// left 3 2 4
// right 6 5
// preorder 1 2 3 4 5 6
/*
*
*
测试用例
6
Push 1
Push 2
Push 3
Pop
Pop
Push 4
Pop
Pop
Push 5
Push 6
Pop
Pop
4
Push 1
Push 2
Pop
Pop
Push 3
Pop
Push 4
Pop
3
Push 1
Push 2
Push 3
Pop
Pop
Pop
5
Push 1
Push 2
Push 3
Pop
Pop
Pop
Push 4
Pop
Push 5
Pop
* */