由树的中序遍历栈操作写出后序遍历结果

题目描述

PTA题目
在这里插入图片描述

思考过程

由二叉树的遍历知识知道根据中序和先序遍历可以确定后序遍历,中序和后序可以确定先序遍历,而先序和后续无法确定中序(因为先序:根左右,后序:左右根,无法分清左右子树的边界)。

思路1

模拟输入过程:直接由push和pop过程可以建立二叉树,然后再写后序遍历。

构造树的结构:静态链表

valleftright
125
234
3-1-1
4-1-1
56-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

如果仅仅依靠中序遍历结果显然是无法推出后序遍历结果的,但这里告诉了栈的信息,仔细思考会发现如下信息:

  1. 所有push操作是二叉树的先序遍历结果;
  2. pop操作是中序遍历结果;
  3. 由先序和中序结果可以递归地写出后序遍历结果。

因此便可得出后续遍历结果。这样的好处是不用建树、建栈,完全使用递归。

代码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时需要出栈并访问之。
算法如下:

  1. push操作入栈;
  2. pop操作:先从栈顶开始,pop出连续的标记为1的节点并访问,
    然后再将栈顶元素标记为1(每个节点初始标记为0);
  3. 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]);
	}
}
  • 2
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值