PAT-A1151题解
还是一样,贴出自己的垃圾代码
#include<iostream>
#include<vector>
#include<unordered_map>
using namespace std;
const int MAX_N = 10010;
int n,m;
int preorder[MAX_N],inorder[MAX_N];
//建立静态二叉树
struct tree_node
{
int parent,lchild,rchild;
};
unordered_map<int,tree_node> tree;
struct node
{
node* lchild;
node* rchild;
node* parent;
int value;
};
int create_tree(int pre_left,int pre_right,int in_left,int in_right,int p){
if(pre_left > pre_right){
return -1;
}
int root = preorder[pre_left];
int mid_num;//确定中序遍历的中间结点。
for(int i = in_left;i<=in_right;i++){
if(root == inorder[i]){
mid_num = i;
break;
}
}
//int left_num = mid_num - in_left;
int lchild = create_tree(pre_left+1,pre_left+mid_num-in_left,in_left,mid_num-1,root);
int rchild = create_tree(pre_left+mid_num-in_left+1,pre_right,mid_num+1,in_right,root);
tree[root] = {p,lchild,rchild};
return root;
}
void pre_traverse(int root){
if(root == -1) return;
printf("%d ",root);
pre_traverse(tree[root].lchild);
pre_traverse(tree[root].rchild);
}
bool find(int index){
if(tree[index].lchild == 0 && tree[index].rchild == 0)return false;
else return true;
}
int main(){
cin>>m>>n;
for(int i = 1;i<=n;i++){
scanf("%d",&inorder[i]);
}
for(int o = 1;o<=n;o++){
scanf("%d",&preorder[o]);
}
int root = create_tree(1,n,1,n,-1);
// pre_traverse(root);
int node1,node2;
cin>>node1>>node2;
}
题意
如果用LCA的算法来解,这应该就是一道模板题。然而LCA算法不一定所有人都学过。LCA算法更适用于完整的二叉树,而这里题目中要求的两个结点是否有最近公共祖先,实际上我们不一定要把完整的二叉树所有结点的公共祖先都维护一遍。
代码(注释版本)
#include <iostream>
#include <vector>
#include <map>
#include <unordered_map>
using namespace std;
map<int, int> pos;//pos用于映射结点的值和它的下标之间的关系
vector<int> in, pre;
void lca(int inl, int inr, int preRoot, int a, int b) {
if (inl > inr) return;//inl和inr代表中序遍历数组中的下标索引
//inRoot表示从inl-inr中序遍历中根节点的下标,aIn表示a在中序遍历中的下标,bIn表示b在终须巴黎中的下标
int inRoot = pos[pre[preRoot]], aIn = pos[a], bIn = pos[b];
//如果两个结点都在根节点的左边
if (aIn < inRoot && bIn < inRoot)
//就往左边递归
lca(inl, inRoot-1, preRoot+1, a, b);
//如果两个分别在根节点的左右两边就可以判断现在的根节点就是最近公共祖先(LCA)
else if ((aIn < inRoot && bIn > inRoot) || (aIn > inRoot && bIn < inRoot))
printf("LCA of %d and %d is %d.\n", a, b, in[inRoot]);
//同上,如果都在右边,就往右子树递归
else if (aIn > inRoot && bIn > inRoot)
lca(inRoot+1, inr, preRoot+1+(inRoot-inl), a, b);
//如果发现aIn是根节点,就说明bIn肯定是aIn的子节点
else if (aIn == inRoot)
printf("%d is an ancestor of %d.\n", a, b);
//同上
else if (bIn == inRoot)
printf("%d is an ancestor of %d.\n", b, a);
}
int main() {
int m, n, a, b;//a、b用于指示所求的两个点
scanf("%d %d", &m, &n);
in.resize(n + 1), pre.resize(n + 1);
for (int i = 1; i <= n; i++) {
scanf("%d", &in[i]);
//由于是从1开始的,所以发现pos[value] = 0就表示这个点不存在
//这样做可以方便后续的判断。
pos[in[i]] = i;
}
for (int i = 1; i <= n; i++) scanf("%d", &pre[i]);
for (int i = 0; i < m; i++) {
scanf("%d %d", &a, &b);
if (pos[a] == 0 && pos[b] == 0)
printf("ERROR: %d and %d are not found.\n", a, b);
else if (pos[a] == 0 || pos[b] == 0)
printf("ERROR: %d is not found.\n", pos[a] == 0 ? a : b);
else
lca(1, n, 1, a, b);
}
return 0;
}
通过柳神的代码,我也总结出了一个船新的通过二叉树的先序遍历和中序遍历构建二叉树的算法:
template <typename ElemType>
node<ElemType>* create(int pre_root_pos,int in_l_pos,int in_r_pos){
if(in_l_pos >in_r_pos){
return NULL;
}
node<ElemType> *root = new node<ElemType>;
root->value = pre[pre_root_pos];
//其中pos_mapping是一个映射函数
int in_root_pos = pos_mapping[pre[pre_root_pos]];
int left_num = in_root_pos-in_l_pos;
root->lchlid = create<ElemType>(pre_root_pos+1,in_l_pos,in_root_pos-1);
root->rchild = create<ElemType>(pre_root_pos+left_num+1,in_root_pos+1,in_r_pos);
return root;
}
也就是说,对于先序遍历的数组,我们只需要知道它的根节点下标就行了,然后借助pos_mapping这个map集合进行下标的映射,就可以减少时间复杂度。
自己的代码(把变量名的命名做的更加符合逻辑,这样可以减少注释):
#include<iostream>
#include<unordered_map>
#include<vector>
#include<cstdio>
using namespace std;
const int MAX_N = 10010;
int pre[MAX_N],in[MAX_N];
unordered_map<int ,int > pos_mapping;//用于从值到下标的映射
void lca(int in_left_pos,int in_right_pos,int pre_root_pos,int a,int b)
{
if(in_left_pos>in_right_pos) return;//递归边界。
int in_root_pos = pos_mapping[pre[pre_root_pos]];//找出根节点在中序遍历中的位置
int l_num = in_root_pos - in_left_pos;
int a_pos = pos_mapping[a],b_pos = pos_mapping[b];//找出a、b两个点在中序遍历中的位置
if((a_pos < in_root_pos && b_pos > in_root_pos) || (a_pos > in_root_pos && b_pos < in_root_pos)){
printf("LCA of %d and %d is %d.\n",a,b,in[in_root_pos]);
}
else if(a_pos < in_root_pos && b_pos < in_root_pos){
lca(in_left_pos,in_root_pos-1,pre_root_pos+1,a,b);
}
else if(a_pos > in_root_pos && b_pos > in_root_pos){
lca(in_left_pos + 1,in_right_pos,pre_root_pos+l_num+1,a,b);
}
else if(a_pos == in_root_pos){
printf("%d is an ancestor of %d.\n",a,b);
}
else if(b_pos == in_root_pos){
printf("%d is an ancestor of %d.\n",b,a);
}
}
template<typename ElemType>
struct node
{
ElemType value;
node* lchlid;
node* rchild;
};
template <typename ElemType>
node<ElemType>* create(int pre_root_pos,int in_l_pos,int in_r_pos){
if(in_l_pos >in_r_pos){
return NULL;
}
node<ElemType> *root = new node<ElemType>;
root->value = pre[pre_root_pos];
//其中pos_mapping是一个映射函数
int in_root_pos = pos_mapping[pre[pre_root_pos]];
int left_num = in_root_pos-in_l_pos;
root->lchlid = create<ElemType>(pre_root_pos+1,in_l_pos,in_root_pos-1);
root->rchild = create<ElemType>(pre_root_pos+left_num+1,in_root_pos+1,in_r_pos);
return root;
}
template <typename ElemType>
void pre_traverse(node<ElemType>* root){
if(root == NULL){
return;
}
cout<<root->value<<" ";
pre_traverse(root->lchlid);
pre_traverse(root->rchild);
}
int main(){
int n,m,a,b;
cin>>m>>n;
for(int i = 1;i<=n;i++){
scanf("%d",&in[i]);
pos_mapping[in[i]] = i;
}
for(int i= 1;i<=n;i++){
scanf("%d",&pre[i]);
}
node<int>* root = create<int>(1,1,n);
//pre_traverse<int>(root);
for(int k = 1;k<=m;k++){
cin>>a>>b;
if(pos_mapping[a] == 0 && pos_mapping[b] == 0){
printf("ERROR: %d and %d are not found.\n",a,b);
}
else if(pos_mapping[a] == 0 || pos_mapping[b] == 0){
printf("ERROR: %d is not found.\n",pos_mapping[a] == 0?a:b);
}
else{
lca(1,n,1,a,b);
}
}
return 0;
}
总结:
这道题非常巧妙地考察了树的相关性质,非常值得我们去仔细研究。首先它利用了中序遍历和先序遍历构建二叉树的算法,其次,还要求我们在规定的时间复杂度内完成相应的计算,要事先排除掉很多干扰选项,以节省时间。
有兴趣的同学更应该去仔细研究一下LCA的完整算法,相信会有更加深刻的理解。