信息学奥赛一本通 1364:二叉树遍历(flist)

【题目链接】

ybt 1364:二叉树遍历(flist)

【题目考点】

1. 二叉树

【解题思路】

解法1:递归 构造子树的中序遍历序列和层次遍历序列

层次遍历序列第一个元素,一定是整棵树的根结点。在中序遍历序列中找到该根结点元素,其左边就是左子树的中序遍历序列,右边就是右子树的中序遍历序列。
接下来我们需要构造左右子树的层次遍历序列。
易知左子树、右子树的层次遍历序列是原树层次遍历序列的子序列
(子序列:在一个序列中,顺序取出部分元素(可以不连续),按原顺序排列得到的序列)
该字符串中只有小写英文字母,而且没有重复字符。我们可以使用散列思想,根据左右子树的中序遍历序列预处理出一个数组bool isLeft[128]isLeft[i]表示ASCII码为i的字母是否属于左子树。如一个字符不属于左子树,那么就属于右子树。
遍历层次遍历序列

  • 如果该字符属于左子树,则在左子树的层次遍历序列末尾添加该字符
  • 如果该字符属于右子树,则在右子树的层次遍历序列末尾添加该字符

得到左右子树的层次遍历序列后,递归调用本函数,构造出左右子树。得到左右子树根结点的地址。
申请结点作为当前树的根结点,设根结点的左右孩子,返回根结点地址。
主函数中调用该建树函数,传入中序遍历序列和层次遍历序列,即可得到整棵树的根结点地址。而后进行先序遍历。

复杂度分析:

  • 时间复杂度:想象生成后的树,对于每个结点,设以该结点为根结点的子树的结点数(也就是该子树的中序、层次遍历序列的长度)为 l l l。在生成以该结点为根的子树时有
    • 遍历该子树中序遍历序列找根结点位置的过程,复杂度 O ( l ) O(l) O(l)
    • 预处理该子树中序遍历序列,得到isLeft数组的过程,复杂度 O ( l ) O(l) O(l)
    • 遍历该子树的层次遍历序列,构造左右子树层次遍历序列的过程,复杂度 O ( l ) O(l) O(l)

因此,生成一棵子树的递归调用的时间复杂度为 O ( l ) O(l) O(l)
树中每层各结点中序遍历序列合在一起就是完整的中序遍历序列。
因此树中每层各结点的 l l l的加和为 n n n,树的层数为 O ( l o g n ) ∼ O ( n ) O(logn)\sim O(n) O(logn)O(n),因此时间复杂度为 O ( n l o g n ) ∼ O ( n 2 ) O(nlogn)\sim O(n^2) O(nlogn)O(n2)

  • 空间复杂度:树的每层结点在递归调用时,参数都是完整的中序遍历序列,空间复杂度为 O ( n ) O(n) O(n),树的层数也就是递归深度为 O ( l o g n ) ∼ O ( n ) O(logn)\sim O(n) O(logn)O(n),因此空间复杂度为 O ( n l o g n ) ∼ O ( n 2 ) O(nlogn)\sim O(n^2) O(nlogn)O(n2)
    如果每个结点中不使用string类对象表示中序和层次遍历序列,而是存储一个全局的字符数组的起始、终止下标来表示中序和层次遍历序列,那么每次调用的空间复杂度为 O ( 1 ) O(1) O(1),整体空间复杂度为 O ( n ) O(n) O(n)
解法2:使用队列,遍历层次遍历序列

观察层次遍历序列和中序遍历序列的关系。
层次遍历序列第一个元素,一定是整棵树的根结点。在中序遍历序列中找到该根结点元素,其左边就是左子树的中序遍历序列,右边就是右子树的中序遍历序列。
层次遍历序列的下一个元素就是左子树的根结点,可以将左子树的中序遍历序列拆为两部分。再下一个元素就是右子树的根结点,可以将右子树的中序遍历序列再拆为两部分。继续做同样的事情。
为了使每次在层次遍历序列中取到的元素对应中序遍历序列的根结点,那么每次得到的中序遍历序列也得像做层次遍历一样,不断入队。

具体做法为:

  1. 设变量lev_i准备遍历层次遍历序列,初始时lev_i为0。中序遍历序列作为字符串,入队。
  2. 出队一个中序遍历序列。
  3. lev_i指向的元素是刚刚出队的中序遍历序列的根结点元素,取该元素左侧序列和该元素右侧序列分别入队。在这一过程中构建树结点之间的关系。
  4. lev_i指向层次遍历序列的下一个元素。

当队列为空,遍历完整个层次遍历序列后,树就建好了。对该树做先序遍历。

复杂度分析:
设中序、层次遍历序列长度为n,也就是树的总结点数为n。

  • 时间复杂度:想象生成后的树,每个结点中都包含了以这个结点为根的子树的中序遍历序列,在生成这棵子树时一定有遍历该中序遍历序列找根结点位置的过程。
    树的每层所有结点的中序遍历序列合在一起是整棵树的中序遍历序列。每层结点在遍历中序遍历序列找根结点的过程的复杂度为 O ( n ) O(n) O(n),树的层数为 O ( l o g n ) ∼ O ( n ) O(logn)\sim O(n) O(logn)O(n),因此时间复杂度为 O ( n l o g n ) ∼ O ( n 2 ) O(nlogn)\sim O(n^2) O(nlogn)O(n2)
  • 空间复杂度:树的每层结点都存储了完整的整棵树的中序遍历序列,为 O ( n ) O(n) O(n),树的层数为 O ( l o g n ) ∼ O ( n ) O(logn)\sim O(n) O(logn)O(n),因此空间复杂度为 O ( n l o g n ) ∼ O ( n 2 ) O(nlogn)\sim O(n^2) O(nlogn)O(n2)
    如果每个结点中不使用string类对象表示中序遍历序列,而是存储一个全局的字符数组的起始、终止下标来表示中序遍历序列,那么每个结点的空间复杂度为 O ( 1 ) O(1) O(1),整体空间复杂度为 O ( n ) O(n) O(n)

【题解代码】

解法1:递归 构造子树的中序遍历序列和层次遍历序列
#include 
using namespace std;
#define N 30 
struct Node
{
	char val;
	int left, right;
};
Node node[N];
int p;
int createTree(string sm, string sl)//以中序遍历序列sm, 层次遍历序列sl构建二叉树 
{
	if(sm == "" || sl == "")
		return 0;
	int j;
	for(j = 0; j < sm.length(); ++j)
		if(sm[j] == sl[0])
			break;
	string sm_left = sm.substr(0, j), sm_right = sm.substr(j+1), sl_left, sl_right;
	bool isLeft[128] = {};//isLeft[i]:ASCII码为i的字符是不是属于左子树 
	for(int i = 0; i < sm_left.length(); ++i)
		isLeft[sm_left[i]] = true;
	for(int i = 1; i < sl.length(); ++i)//略去根结点sl[0],从sl[1]开始遍历 
	{
		if(isLeft[sl[i]])
			sl_left.push_back(sl[i]);
		else
			sl_right.push_back(sl[i]);
	}
	int np = ++p;
	node[np].val = sl[0];
	node[np].left = createTree(sm_left, sl_left);
	node[np].right = createTree(sm_right, sl_right);
	return np;
}
void preOrder(int r)
{
	if(r == 0)
		return;
	cout << node[r].val;
	preOrder(node[r].left);
	preOrder(node[r].right);
} 
int main()
{
	string s_mid, s_lev;//s_mid:中序遍历序列 s_lev:层次遍历序列 
	cin >> s_mid >> s_lev;
	int root = createTree(s_mid, s_lev);
	preOrder(root);
    return 0;
}
解法2:使用队列,遍历层次遍历序列
#include 
using namespace std;
#define N 105
struct QNode
{
	string s;//中序遍历序列 
	int p;//该中序遍历序列对应的子树的根结点地址 
	QNode(){}
	QNode(string a, int b):s(a), p(b){}
};
struct Node
{
	char val;
	int left, right;
};
Node node[N];
string s_lev, s_mid;//s_lev:层次遍历序列 s_mid:中序遍历序列 
int p, lev_i;//p:node数组中已分配的最后一个结点的下标 lev_i:s_lev的下标 
void createTree()
{
	queue<QNode> que;
	que.push(QNode(s_mid, ++p));//根结点的地址为1 
	while(que.empty() == false)
	{
		QNode u = que.front();
		que.pop();
		int i;
		for(i = 0; i < u.s.length(); ++i)//寻找中序遍历序列中根结点元素的下标
			if(u.s[i] == s_lev[lev_i])
				break;
		node[u.p].val = s_lev[lev_i++];//lev_i++:下一次在层次遍历序列中看下一个字符 
		string sl = u.s.substr(0, i), sr = u.s.substr(i+1);//sl:左子树的中序遍历序列 sr:右子树的中序遍历序列 
		if(sl.length() > 0)//如果存在左子树 
		{
			int lp = ++p;//给左子树根结点分配地址 
			node[u.p].left = lp; 
			que.push(QNode(sl, lp));//取u.s中ri左侧字符串作为左子树的中序遍历序列 
		}
		if(sr.length() > 0)//如果存在右子树 
		{
			int rp = ++p;//给右子树根结点分配地址 
			node[u.p].right = rp;//设根结点的左右子树 
			que.push(QNode(sr, rp));//取u.s中ri右侧字符串作为右子树的中序遍历序列 
		}
	}
}
void preOrder(int r)
{
	if(r == 0)
		return;
	cout << node[r].val;
	preOrder(node[r].left);
	preOrder(node[r].right);
} 
int main()
{
	cin >> s_mid >> s_lev;
	createTree();
	preOrder(1);
	return 0;
}

你可能感兴趣的:(信息学奥赛一本通题解,c++)