题目如下:
给一棵点带权(权值各不相同,都是小于10000的正整数)的二叉树的中序和后序遍
历,找一个叶子使得它到根的路径上的权和最小。如果有多解,该叶子本身的权应尽量小。
输入中每两行表示一棵树,其中第一行为中序遍历,第二行为后序遍历。
样例输入:
3 2 1 4 5 7 6
3 1 2 5 6 7 4
7 8 11 3 5 16 12 18
8 3 11 7 16 18 12 5
255
255
样例输出:
1
3
255
题目对于熟悉二叉树的人来说很简单,就是简单的重建然后DFS遍历找到最短的值就OK,但像我这种只有二叉树的简单概念,没有编程实现过的人来说就显得尤为困难。
根据我之前的一篇博文里面说的,有两个结论
1.后序遍历,每次递归的时候最后一个节点一定是该层的根节点
2.中序遍历,根节点的左边是左子树,右边是右子书
根据这个结论我们可以每次都找到当前递归层的根节点,然后搜索中序数组,找到对应的点,则该点左边是左子树,右边是右子树部分,很显然这是一个递归的过程,所以我们可以通过递归重建二叉树。
然后遍历一遍重建的二叉树,DFS(前序,中序,后序均可),这里要说的是,DFS是可以实现三种遍历的,因为这三种遍历本质上都是深度优先搜索,只是我们编程实现的时候输出的语句在不同的位置就实现了不同的遍历,有心的读者可以试试,代码里面注释掉了。
首先贴出指针实现的代码
// UVA548.cpp : 定义控制台应用程序的入口点。
//
#include <string>
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <algorithm>
using namespace std;
const int maxv = 10000 + 10;
int in_order[maxv], post_order[maxv];
int len;
struct Node
{
Node *left;
Node *right;
int data;
Node()
{
left = NULL;//注意赋初值,避免野指针
right = NULL;
}
};
bool read_list(int* a) {
string line;
if (!getline(cin, line)) return false;
stringstream ss(line);
len = 0;
int x;
while (ss >> x) a[len++] = x;
return len > 0;
}
Node *BuildTrees(Node *&T, int in[], int post[], int low1, int high1, int low2, int high2)
{
int i;
if (low1 <= high1)
{
T = new Node();
T->data = post[high2];
T->left = T->right = NULL;
i = low1;
while (in[i] != post[high2])i++;
int cnt = i - low1;
BuildTrees(T->left, in, post, low1, i - 1, low2, low2 + cnt - 1);
BuildTrees(T->right, in, post, i + 1, high1, low2 + cnt, high2 - 1);
}
return T;
}
void remove_tree(Node* u) {
if (u == NULL) return; //提前判断比较稳妥
remove_tree(u->left); //递归释放左子树的空间
remove_tree(u->right); //递归释放右子树的空间
delete u; //调用u的析构函数并释放u结点本身的内存
}
int best, best_sum; //目前为止的最优解和对应的权和
void dfs(Node* u, int sum) {
sum += u->data;
if (!u->left && !u->right) { //叶子
if (sum < best_sum || (sum == best_sum && u->data < best)) {
best = u->data;
best_sum = sum;
}
}
//cout << u->data << ' ';//在这里输出节点即是前序遍历
if (u->left)
{
dfs(u->left, sum);
}
//cout << u->data << ' ';//在这里输出节点即是中序遍历
if (u->right)
{
dfs(u->right, sum);
}
//cout << u->data << ' ';//在这里输出节点即是后序遍历
}
int main()
{
Node *root = new Node();
while (read_list(in_order)) {
read_list(post_order);
remove_tree(root);
BuildTrees(root, in_order, post_order, 0, len - 1, 0, len - 1);
best_sum = 1000000000;
dfs(root, 0);
cout << best << "\n";
}
return 0;
}
指针实现的一个弊端就是代码编写调试麻烦,可以Debug一下上面的代码,从根指针开始,一层一层的点开,点开下一层的值就看不到上一层的值,这对于我们找错来说是不利的,所以,便有了更方便调试的数组实现。
只需要将上面的Node 去掉变成int,开left 和right 还有value数组,把u->right,u->left 变成left[u],right[u]即可,但是数组实现的原理,要在纸上举个例子过几遍才能理解。
下面贴出代码
// UVA548.cpp : 定义控制台应用程序的入口点。
//
#include <string>
#include <stdio.h>
#include <iostream>
#include <sstream>
#include <algorithm>
using namespace std;
const int maxv = 10000 + 10;
int in_order[maxv], post_order[maxv];
int left_ch[maxv], right_ch[maxv], value[maxv];
//因为权值为整数且各不相同,所以可以直接用权值作为数组下标的编号,可以节省value这个数组,
//这和01背包中的二维数组优化成一维类似
int len;
const int root = 0;
int cnt = root;
void newtree() {
left_ch[root] = right_ch[root] = 0;
cnt= root;
}
int newnode() {
int u = ++cnt;
left_ch[u] = right_ch[u] = 0;
return u;
}
bool read_list(int* a) {
string line;
if (!getline(cin, line)) return false;
stringstream ss(line);
len = 0;
int x;
while (ss >> x) a[len++] = x;
return len > 0;
}
int BuildTrees(int &T, int in[], int post[], int low1, int high1, int low2, int high2)
{
int i;
if (low1 <= high1)
{
T = newnode();
value[T] = post[high2];
i = low1;
while (in[i] != post[high2])i++;
int cnt = i - low1;
BuildTrees(left_ch[T], in, post, low1, i - 1, low2, low2 + cnt - 1);
BuildTrees(right_ch[T], in, post, i + 1, high1, low2 + cnt, high2 - 1);
}
return T;
}
int best, best_sum; //目前为止的最优解和对应的权和
void dfs(int u, int sum) {
sum += value[u];
if (!left_ch[u] && !right_ch[u]) { //叶子
if (sum < best_sum || (sum == best_sum && value[u] < best)) {
best = value[u];
best_sum = sum;
}
}
//cout << value[u] << ' ';
//先序遍历(DFS)
if (left_ch[u])
{
dfs(left_ch[u], sum);
}
if (right_ch[u])
{
dfs(right_ch[u], sum);
}
}
int main()
{
while (read_list(in_order)) {
read_list(post_order);
newtree();
int index = root;
BuildTrees(index, in_order, post_order, 0, len - 1, 0, len - 1);
//记得初始化一个值
best_sum = 1000000000;
dfs(1, 0);
cout << best << "\n";
}
return 0;
}
这里我简单地说下数组实现的原理
以这组题中的测试数据为例子:
3 2 1 4 5 7 6
3 1 2 5 6 7 4
运行完上述代码后:
index(数组下标)0 1 2 3 4 5 6 7
left_ch: 0 2 3 0 0 6 0 0
right_ch: 0 5 4 0 0 7 0 0
value: 0 4 2 3 1 7 5 6
根是value[1],先取出4
然后left_ch[1]为2对应value[2] = 2,right_ch[1]为5,对应value[5] = 7,所以得到
4
2 7
然后因为上述的左树2对应的索引是2,那么它的left_ch[2] = 3对应value[3] = 3,right_ch[2] = 4 对应value[4] = 1,所以树可以变为
4
2 7
3 1
7对应在value数组中的索引是5,所以7的左子数是5,右子树是6,以此即可根据数组得到原树,这里就不展开说了。这就是数组实现二叉树的方法及其数组中存的值的意义。下面提下BFS(广度优先搜索)广度优先,可以实现树的层次遍历 ,利用队列实现,下面是uva122实现广搜中的一部分。
bool bfs(vector<int> &num)
{
queue<int> q;
num.clear();
q.push(root);
while (!q.empty())
{
int u = q.front(); q.pop();
if (have_value[u] == false) return false;
if (left_[u] != 0)q.push(left_ch[u]);
if (right_[u] != 0)q.push(right_ch[u]);
num.push_back(v[u]);
}
return true; //输入正确
}
二叉树是一种基础的数据结构,在各个公司的笔试题中经常出现,要时时温习呀。