初识二叉树,依葫芦画瓢,主要记录一下。
题目:UVA-548
题目描述:
一棵二叉树,每个结点都有不同的权值(1~10000的正整数),给出了其中序遍历(第一行)和后序遍历(第二行),找到一个叶子,使得它到根的路径的权和最小,如果有多解,该叶子的本身的权值应尽量小。
首先是对DFS的理解。
先序遍历(根->左子树->右子树):ABDECF
中序遍历(左子树->根->右子树):DBEACF
后序遍历(左子树->右子树->根):DEBFCA
先序+中序 或 后序+中序 都可以确定一棵二叉树。(先序+后序无法确定)
以该题的后序+中序和上图为例,对任意一颗二叉树(可能是一个子树),已知后序和中序。
①取后序遍历(DEBFCA)的最后一个结点,一定是当前树的根结点,所以根为A。
②已知根为A,到中序遍历(DBEACF)中查找A的位置,然后可知DBE为左子树,CF为右子树。
(实际编写程序是得到左、右子树的结点数量,即其在中序遍历中是第几个到第几个)
③知道左、子树结点数量,即可得出后序遍历中前3个(DEB)为左子树,随后2个(FC)为右子树。
④上述操作就得到了左、右子树的后序+中序,然后对其进行相同的操作。
Ps.
①在叶结点到根结点路径求和时,应使用先序遍历,其求和顺序是:ABD、ABE、ACF。
②因为是用数组实现二叉树,各个结点权值不同,直接用权值作为结点编号,就不需要另外用数组存结点了。
以下代码:
预处理:
#include<iostream>
#include<sstream>
#include<string>
using namespace std;
const int maxv = 10010;
int in[maxv], post[maxv], Left[maxv], Right[maxv]; //in为中序,post为后序
int n,min_sum, ans; //n为结点数量,min_sum最小权和
读取:
bool read(int *a)
{
string line; //先把一行放入line中
if (!getline(cin, line))
return false; //读到EOF则返回false
stringstream ss(line); //将line创建为流
n = 0;
int x;
while (ss >> x) //从流中读出
a[n++] = x;
return true;
}
构建二叉树:
//L1~R1为当前树所有结点的中序遍历,L2~R2为当前树所有结点的后序遍历
int build_tree(int L1, int R1, int L2, int R2)
{
if (L1 > R1) //树为空
return 0;
int root = post[R2]; //取根结点
int p,cnt;
for (p = L1; p <= R1; p++) //在中序遍历中找到根结点
if (in[p] == root)
break;
cnt = p - L1; //左子树的结点数量
Left[root] = build_tree(L1, p - 1, L2, L2 + cnt - 1); //左子树的中序、后序
Right[root] = build_tree(p + 1, R1, L2 + cnt , R2-1); //右子树的中序、后序
return root; //返回当前树的根
}
Ps.这里对空树的判断,举个例子方便理解。假设一个结点其左子树为空,那么会得到p== L1 ==R1,所以递归左子树的时候L1>R1,就可以返回0(代表空树)。
DFS求最小权和:
void dfs(int u, int sum) //要把当前算得的sum传给下一层
{
sum += u;
if (!Left[u] && !Right[u]) //判断为叶结点
{
if (sum < min_sum || (sum == min_sum && u < ans))
{
ans = u;
min_sum = sum;
}
}
if (Left[u])
dfs(Left[u], sum);
if (Right[u])
dfs(Right[u], sum);
}
主函数:
int main()
{
while (read(in))
{
read(post);
int tree=build_tree(0, n - 1, 0, n - 1); //tree存放根节点
min_sum = 1000000000;
dfs(tree, 0);
cout << ans << endl;
}
return 0;
}