题目描述
PTA题目
思考过程
由二叉树的遍历知识知道根据中序和先序遍历可以确定后序遍历,中序和后序可以确定先序遍历,而先序和后续无法确定中序(因为先序:根左右,后序:左右根,无法分清左右子树的边界)。
思路1
模拟输入过程:直接由push和pop过程可以建立二叉树,然后再写后序遍历。
构造树的结构:静态链表
val | left | right |
---|---|---|
1 | 2 | 5 |
2 | 3 | 4 |
3 | -1 | -1 |
4 | -1 | -1 |
5 | 6 | -1 |
6 | -1 | -1 |
后序遍历:这里直接用最简单的递归,后面改写为用栈。
代码1
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//树节点
typedef struct node {
int val;
int left;
int right;
}Node;
//栈节点
typedef struct node1 {
Node* node;
struct node1 *next;
}Node1;
//栈
typedef struct stack {
Node1*top;
int len;
}Stack;
void Push(Stack*s,Node1* node) {
Node1* temp = s->top->next;
s->top->next = node;
node->next = temp;
s->len++;
}
Node* Pop(Stack*s){
Node1* temp = s->top->next;
if (temp == NULL) {
return NULL;
}
Node* node = temp->node;
s->top->next = temp->next;
free(temp);
s->len--;
return node;
}
Node* getTop(Stack*s) {
Node1* temp = s->top->next;
if (temp == NULL) {
return NULL;
}
return temp->node;
}
void postorderTraversal(Node*tree,int m) {
if (m == -1) {
return;
}
postorderTraversal(tree, tree[m].left);
postorderTraversal(tree, tree[m].right);
if (m > -1) {
if (m == 0) {
printf("%d", tree[m].val);
}
else {
printf("%d ", tree[m].val);
}
}
}
int main() {
int n;
scanf("%d", &n);
Node* tree = (Node*)malloc(sizeof(Node)*n);
Stack* stack = (Stack*)malloc(sizeof(Stack));
stack->len = 0;
stack->top = (Node1*)malloc(sizeof(Node1));
stack->top->next = NULL;
int cursor = 0;
Node* popNode=NULL;
int isPop = 0;
//建树
for (int i = 0; i < 2*n; i++) {
char s[5];
int val;
scanf("%s", s);
if (!strcmp(s, "Push")) {
scanf("%d", val);
tree[cursor].val = val;
tree[cursor].left = -1;
tree[cursor].right = -1;
//建立与父节点联系
if (i > 0) {
if (!isPop) {
Node* top = getTop(stack);
top->left = cursor;
}
else {
popNode->right = cursor;
isPop = 0;
}
}
Node1* node1 = (Node1*)malloc(sizeof(Node1));
node1->node = &tree[cursor];
node1->next = NULL;
//入栈
Push(stack, node1);
cursor++;
}
else if (!(strcmp(s, "Pop"))) {
popNode= Pop(stack);
isPop = 1;
}
}
//显示树
for (int i = 0; i < n; i++) {
int val, left, right;
val = tree[i].val;
left = tree[i].left;
right = tree[i].right;
printf("%d %d %d\n", val, left, right);
}
//后序遍历
postorderTraversal(tree,0);
}
附:栈实现二叉树后序遍历
关于树的后续遍历实在想了很久,由于要保存根节点直到右子树访问完毕才能访问根节点,为了避免重复访问根节点右子树造成死循环,于是想着用两个堆栈去实现,不过发现在实现细节上相当麻烦,也没找到控制规律,于是参考csdn上的思路,发现只要做个标记就好了,于是重新给栈节点添加一个tag属性做标记,发现代码很容易写了。
void postorderTraversal(Node*tree, int m) {
Stack* stack = (Stack*)malloc(sizeof(Stack));
stack->len = 0;
stack->top = (Node1*)malloc(sizeof(Node1));
stack->top->next = NULL;
while (m!=-1||!isEmpty(stack)) {
while (m != -1) {
Node * temp = &tree[m];
Node1* node1 = (Node1*)malloc(sizeof(Node1));
node1->node = temp;
node1->tag = 0;
node1->next = NULL;
Push(stack, node1);
m = temp->left;
}
if (!isEmpty(stack)){
Node1* node1 = getTop(stack);
m = node1->node->right;
if (m!= -1&& !node1->tag) {
node1->tag = 1;
}
else {
int val = node1->node->val;
printf("%d ", val);
Pop(stack);
m = -1;
}
}
}
}
栈结构改为如下
//栈节点
typedef struct node1 {
Node* node;
int tag;
struct node1 *next;
}Node1;
//栈
typedef struct stack {
Node1*top;
int len;
}Stack;
思路2
如果仅仅依靠中序遍历结果显然是无法推出后序遍历结果的,但这里告诉了栈的信息,仔细思考会发现如下信息:
- 所有push操作是二叉树的先序遍历结果;
- pop操作是中序遍历结果;
- 由先序和中序结果可以递归地写出后序遍历结果。
因此便可得出后续遍历结果。这样的好处是不用建树、建栈,完全使用递归。
代码2
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void getPostOrder(int preorder[], int prestart, int preend, int inorder[], int instart, int inend, int postorder[], int n);
int main() {
int n;
scanf("%d", &n);
int *inorder = (int*)malloc(sizeof(int)*n);
int *preorder = (int*)malloc(sizeof(int)*n);
int *postorder = (int*)malloc(sizeof(int)*n);
int countin = 0, countpre = 0;
int *popIndex = (int*)malloc(sizeof(int)*n);//指示需要在preor种pop到的位置
int count = 0;
for (int i = 0; i < 2*n; i++) {
char s[5];
int val;
scanf("%s", s);
if (!strcmp(s, "Push")) {
scanf("%d", &val);
popIndex[count++] = countpre;
preorder[countpre++] = val;
}
else if (!(strcmp(s, "Pop"))) {
int index = popIndex[--count];
val = preorder[index];
inorder[countin++] = val;
}
}
/*for (int i = 0; i < n; i++) {
printf("%d ", preorder[i]);
}
printf("\n");
for (int i = 0; i < n; i++) {
printf("%d ", inorder[i]);
}
printf("\n");*/
getPostOrder(preorder,0,n-1,inorder,0,n-1,postorder,n-1);
for (int i = 0; i < n; i++) {
printf("%d ", postorder[i]);
}
}
void getPostOrder(int preorder[], int prestart, int preend, int inorder[], int instart, int inend, int postorder[], int n) {
//处理根节点
int root = preorder[prestart];
postorder[n] = root;
/*if (prestart == preend) {
return;
}*/
//在中序遍历中找到根节点位置
int rootIndex;
for (rootIndex = instart; rootIndex <= inend; rootIndex++) {
if (inorder[rootIndex] == root) {
break;
}
}
//左右子树均存在
if (prestart + rootIndex - instart + 1 <= preend&&
prestart + 1 <= prestart + rootIndex - instart) {
//处理右子树
getPostOrder(preorder, prestart + rootIndex - instart + 1, preend, inorder, rootIndex + 1, inend, postorder, n - 1);
//处理左子树
getPostOrder(preorder, prestart + 1, prestart + rootIndex - instart, inorder, instart, rootIndex - 1, postorder, n -1 - (inend-rootIndex ));
}
//左子树存在右子树无
if (prestart + rootIndex - instart + 1> preend &&
prestart + 1 <= prestart + rootIndex - instart) {
//处理左子树
getPostOrder(preorder, prestart + 1, prestart + rootIndex - instart, inorder, instart, rootIndex - 1, postorder, n - 1);
}
//右子树存在左子树无
if (prestart + rootIndex - instart + 1 <= preend &&
prestart + 1 > prestart + rootIndex - instart) {
//处理右子树
getPostOrder(preorder, prestart + rootIndex - instart + 1, preend, inorder, rootIndex + 1, inend, postorder, n - 1);
}
}
思路3
由于中序遍历的pop操作是左子树、根、右子树,要将其转换为后序遍历的左子树、右子树、根只需要控制根节点最后访问即可。
那么什么时候根节点应该要访问呢?
根必定是在整个子树访问完了之后才能访问。在先序遍历中,对A节点的父节点B做pop时代表A所在子树访问完毕。于是,这个时候才能访问A。由于中序遍历pop顺序为左子树、根、右子树,因此,在对栈中某个节点第二次执行pop时需要出栈并访问之。
算法如下:
- push操作入栈;
- pop操作:先从栈顶开始,pop出连续的标记为1的节点并访问,
然后再将栈顶元素标记为1(每个节点初始标记为0); - pop,push结束后,若栈不为空,从栈顶依次弹出节点并访问。
代码3
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<limits.h>
#define ERROR INT_MIN;
//栈节点
typedef struct node1 {
int val;
int tag;
struct node1 *next;
}Node1;
//栈
typedef struct stack {
Node1*top;
int len;
}Stack;
Stack * initStack() {
Stack* stack = (Stack*)malloc(sizeof(Stack));
stack->len = 0;
stack->top = (Node1*)malloc(sizeof(Node1));
stack->top->next = NULL;
return stack;
}
void Push(Stack*s, Node1* node) {
Node1* temp = s->top->next;
s->top->next = node;
node->next = temp;
s->len++;
}
int Pop(Stack*s) {
Node1* temp = s->top->next;
if (temp == NULL) {
return ERROR;
}
int val = temp->val;
s->top->next = temp->next;
free(temp);
s->len--;
return val;
}
Node1* getTop(Stack*s) {
Node1* temp = s->top->next;
if (temp == NULL) {
return NULL;
}
return temp;
}
int isEmpty(Stack* stack) {
return stack->len == 0;
}
int main() {
int n;
scanf("%d", &n);
int *printNum = (int*)malloc(sizeof(int)*n);
int count = 0;
Stack* stack = initStack();
for (int i = 0; i < 2*n; i++) {
char s[5];
int val;
scanf("%s", s);
if (!strcmp(s, "Push")) {
scanf("%d", &val);
Node1* node1 = (Node1*)malloc(sizeof(Node1));
node1->val = val;
node1->tag = 0;
node1->next = NULL;
Push(stack, node1);
}
else if (!(strcmp(s, "Pop"))) {
Node1* temp = getTop(stack);
if (!temp->tag) {
temp->tag = 1;
}
else {
while (getTop(stack)->tag) {
printNum[count++] = Pop(stack);
}
if (!isEmpty(stack)) {
Node1* temp = getTop(stack);
temp->tag = 1;
}
}
}
}
while (!isEmpty(stack)) {
printNum[count++] = Pop(stack);
}
for (int i = 0; i < n; i++) {
printf("%d ", printNum[i]);
}
}