算法 树3 Tree Traversals Again

全部每周作业和视频思考题答案和解析 见 浙江大学 数据结构 思考题+每周练习答案汇总

题目: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.

有序二叉树遍历可以通过堆栈以非递归方式实现。例如,假设遍历6节点二叉树(键的编号从1到6)时,堆栈操作为:push(1);push(2);push(3);pop();pop();push(4);pop();pop();push(5);push(6);pop();pop()。然后,可以从这个操作序列生成一个唯一的二叉树(如图1所示)。你的任务是给出这个树的后序遍历序列。


Figure 1

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

解答:

读完这个题目以后,我觉得应该有的方法不用建树就能做出来。大家注意到这个pop的顺序是中序遍历的顺序,而push的顺序是前序遍历的顺序,而我们知道通过前序和中序就可以确定树的结构了。

中序顺序:3 2 4 1 6 5

前序顺序:1 2 3 4 5 6

前序的第一个为树根节点,在后续遍历中,根节点都是最后打印的。

#include <iostream>
#include <stack>
#include<string>
using namespace std;
stack<int> PostBack; //存最后反序遍历的结果
stack<int> MidOpt;   //用于操作的时候求中序遍历序列
int PreEdc[1000];    //存前序遍历结果
int PreNum = 0;      //前序遍历计数
int MidEdc[1000];    //存中序遍历结果
int MidNum = 0;      //中序遍历计数
int Num;
void ReadData() {
	cin >> Num;
	string opt;
	int data;
	for (int i = 0;i<Num*2;i++) {
		cin >> opt;
		if (opt == "Push") {
			cin >> data;
			PreEdc[PreNum++] = data;
			MidOpt.push(data);
		}
		else {
			MidEdc[MidNum++] = MidOpt.top();
			MidOpt.pop();

		}
	}
	//检测输入是否正确。
	//for (int i = 0;i < Num;i++) {
	//	cout << MidEdc[i] << " ";
	//}
}

首先先把读入程序和一些需要建立好的变量写好。

现在已经得到了前序和中序变量的结果。

int Count = 0;
//在A[start,end]区间里找a这个数,并返回坐标
int FindPoint(int start,int end ,int A[],int a) {
	//cout << Count++ << endl;
	for (int i = start;i <= end;i++) {
		if (A[i] == a)return i;
	}
	//cout << "error for "<< a <<endl;
	return -1;//如果没找到。当然在程序正确的情况这是不会出现的
}

这个程序的意义是从一个区间里找到某个数,然后返回该坐标。

之后通过递归算法来循环找到目标值。一开始出现了各种段错误,然后我加了一堆调试语句,为了体现整体性先贴上代码:


void FindLeafs(int startPre,int endPre,int startMid,int endMid) {
	//把前序表的第根Push进去
	PostBack.push(PreEdc[startPre]);
	//cout << PreEdc[startPre] << endl;
	// 1 4 5 6 8 2 3
	if (startPre == endPre) {
		//cout << "Leaf " << endl;
		return;
	}
	else {
		//找到根在中序表里面的位置,现在 startMid-root-1为左子树,root+1-endMid为右子树 
		int root = FindPoint(startMid, endMid, MidEdc, PreEdc[startPre]);

		//用的两组测试例子
		//3 2 4 1 6 5
		//1 2 3 4 5 6 
		// 1 2 3 7 4 8 5 6
		// 7 3 2 1 8 4 6 5
		

		//判断
		if (root == startMid) { //没有左子树
			//cout << "case 1 : ";
			//cout << " " << startPre + 1 << " " << endPre << " " << root + 1 << " " << endMid << endl;
			FindLeafs(startPre + 1, endPre, root + 1, endMid);
		}
		else if (root == endMid) { //没有右子树
			//cout << "case 2 : ";
			//cout << " " << startPre + 1 << " " << endPre << " " << startMid << " " << root - 1 << endl;
			FindLeafs(startPre + 1, endPre, startMid, root - 1);//这里一开始写错了。
		}
		else {
			//cout << "case 3 : ";
			//cout << " " << startPre + 1 + root - startMid << " " << endPre << " " << root + 1 << " " << endMid << endl;
			FindLeafs(startPre + 1 + root - startMid, endPre, root + 1, endMid);
			//cout << " " << startPre + 1 << " " << startPre + root - startMid << " " << startMid << " " << root - 1 << endl;
			FindLeafs(startPre + 1, startPre + root - startMid, startMid, root - 1);
		}


	}

}

该函数输入四个参数,分别是前序遍历数组的开始与结尾坐标,中序遍历数组的开始与结尾坐标。

我们很容易明白,每次输入的坐标中,前序列的开始与结尾间距与中序的是一样的。

例如 中序3 2 4 1 6 5  前序1 2 3 4 5 6   第一次判断输入为 FindLeafs(0,5,0,5)

第二次判断输入为左子树FindLeafs(1,3,0,2)以及右子树FindLeafs(4,5,4,5) 可以看到1-3与0-2间距相同,而且4-5与4-5间距也相同。

当开始坐标等于结尾坐标时,说明是个叶子,送进栈中。

否则就判断,如果没有左子树,除去根以外剩下部分就是右子树。反之亦然。

如果左右子树都有,则分别递归。注意先递归右边的子树,先进行递归的会先进栈,后出栈。

全部代码如下:


#include <iostream>
#include <stack>
#include<string>
using namespace std;
stack<int> PostBack; //存最后反序遍历的结果
stack<int> MidOpt;   //用于操作的时候求中序遍历序列
int PreEdc[100];    //存前序遍历结果
int PreNum = 0;      //前序遍历计数
int MidEdc[100];    //存中序遍历结果
int MidNum = 0;      //中序遍历计数
int Num;
void ReadData() {
	cin >> Num;
	string opt;
	int data;
	for (int i = 0;i<Num*2;i++) {
		cin >> opt;
		if (opt == "Push") {
			cin >> data;
			PreEdc[PreNum++] = data;
			MidOpt.push(data);
		}
		else {
			MidEdc[MidNum++] = MidOpt.top();
			MidOpt.pop();

		}
	}
	//检测输入是否正确。
	/*for (int i = 0;i < Num;i++) {
		cout << PreEdc[i] << " ";
	}
	cout << endl;
	for (int i = 0;i < Num;i++) {
		cout << MidEdc[i] << " ";
	}
	cout << endl;*/
}

int Count = 0;
//在A[start,end]区间里找a这个数,并返回坐标
int FindPoint(int start,int end ,int A[],int a) {
	//cout << Count++ << endl;
	for (int i = start;i <= end;i++) {
		if (A[i] == a)return i;
	}
	//cout << "error for "<< a <<endl;
	return -1;//如果没找到。当然在程序正确的情况这是不会出现的
}

void FindLeafs(int startPre,int endPre,int startMid,int endMid) {
	//把前序表的第根Push进去
	PostBack.push(PreEdc[startPre]);
	//cout << PreEdc[startPre] << endl;
	// 1 4 5 6 8 2 3
	if (startPre == endPre) {
		//cout << "Leaf " << endl;
		return;
	}
	else {
		//找到根在中序表里面的位置,现在 startMid-root-1为左子树,root+1-endMid为右子树 
		int root = FindPoint(startMid, endMid, MidEdc, PreEdc[startPre]);

		//用的两组测试例子
		//3 2 4 1 6 5
		//1 2 3 4 5 6 
		// 1 2 3 7 4 8 5 6
		// 7 3 2 1 8 4 6 5
		

		//判断
		if (root == startMid) { //没有左子树
			//cout << "case 1 : ";
			//cout << " " << startPre + 1 << " " << endPre << " " << root + 1 << " " << endMid << endl;
			FindLeafs(startPre + 1, endPre, root + 1, endMid);
		}
		else if (root == endMid) { //没有右子树
			//cout << "case 2 : ";
			//cout << " " << startPre + 1 << " " << endPre << " " << startMid << " " << root - 1 << endl;
			FindLeafs(startPre + 1, endPre, startMid, root - 1);//这里一开始写错了。
		}
		else {
			//cout << "case 3 : ";
			//cout << " " << startPre + 1 + root - startMid << " " << endPre << " " << root + 1 << " " << endMid << endl;
			FindLeafs(startPre + 1 + root - startMid, endPre, root + 1, endMid);
			//cout << " " << startPre + 1 << " " << startPre + root - startMid << " " << startMid << " " << root - 1 << endl;
			FindLeafs(startPre + 1, startPre + root - startMid, startMid, root - 1);
		}


	}

}

int main() {

	ReadData();
	FindLeafs(0,Num-1,0,Num-1);
	cout << PostBack.top();
	PostBack.pop();
	while (!PostBack.empty()) {
		cout  << " "<< PostBack.top();
		PostBack.pop();
	}

	system("pause");
	return 0;
}

写这个程序,在递归的里面左右坐标写错了导致出现了bug,调试用了不少时间。

注意最后打印的时候,末尾一定不要有空格,不然程序也通不过。

执行结果如下:

发布了174 篇原创文章 · 获赞 394 · 访问量 1万+

猜你喜欢

转载自blog.csdn.net/tiao_god/article/details/105062261