2017牛课堂第三季第三课第一题-并查集用于二叉数最近公共节点查询
思路:
首先,直接二叉树遍历,是肯定做不到的
利用并查集,维护当前查询的根节点,能顾保证实现。
具体的,首先设定好节点并查集,即在初始阶段,father[i]都指向自己。
其次,在对query 进行处理,要求每一个节点的查询,都产生对应的相关序列。例如
有(1,2)(1,3) 两个查询对,那么query[1]={2,3},query[2]={1},query[3]={1}.通过这种方式能够在遍历的时候一次性全部处理,从而提高效率。比如 遍历到节点1 的时候,就会对 2和3 进行查询,至于如何查询,继续往下看。
初始化后
1.我们已经得到了一个并查集,每个节点首先指向自己
2.query序列中,每个节点,都有对应的相关序列捆绑着,如果遍历到,可以一次性全部处理掉。
3.得到一个rootmap,这个rootmap用于保存每个并查集的 根节点。
接下来开始遍历,通过如下方式进行遍历:
利用后序遍历,那么保证遍历一直往下进行,从子节点开始跳出递归。
假如遍历到当前节点,首先要对其左子树进行遍历,当左子树结束后,根节点和左子树的并查集开始合并,并令两者的并查集的father 的root 为 当前root,即rootmap[father [root]]=root。 对右子树也是如此 。这样做的目的是不断记录当前遍历时的根节点,通过并查集能够快速找到当前的根节点,其father 的根节点 就是这个并查集所有节点的root。
然后开始处理所有相关的查询节点,对每个相关节点:
如果其所在的并查集的father 的根节点在rootmap 中已经注册,那么可以直接通过从rootmap 中直接找到
如果没有,说明相关节点还没遍历到,就什么都不做,让相关节点被遍历到的时候,再去计算(上面初始化的 2)
最后遍历完就能得到结果
代码:
#include<iostream>
#include<cstdio>
#include<ctime>
#include<map>
#include<vector>
#include<cassert>
const int MAX = 100000;
const int INF = 0x3f3f3f3f;
using namespace std;
//定义好二叉树结构
struct node{
int val;
node *left = NULL;
node *right = NULL;
node(int val){
this->val = val;
this->left = NULL;
this->right = NULL;
}
};
//定义并查集的节点father 数组 这里用map
map<node*,node*> father;
//定义每个node指针对应的 层数
map<node*,int> level;
//输入树的根节点,对每一个并查集节点进行初始化 包括 其父亲节点和 层级
void pre_order_make(node * root){
if(root==NULL) return;
father[root] = root;
level[root] = 0;
pre_order_make(root->left);
pre_order_make(root->right);
}
//并查集初始化,输入树的根
void set_init(node * root){
father.clear();
level.clear();
pre_order_make(root);
}
//返回对应的父亲节点
node * findFather(node * x){
node * fa_id = father[x];//得到父亲节点
if(fa_id != x){
fa_id = findFather(fa_id);
}
return father[x] = fa_id;
}
//判断两个节点是否在同一集合
bool same(node* a, node* b){
return father[a] == father[b];
}
//合并,注意要判空,因为空节点不存在查找的树形中
void unions(node* a,node* b){
//dont forget it
if(a==NULL || b ==NULL) return;
if (same(a,b)) return;
node* a_f = father[a];
node* b_f = father[b];
//分配层级
if(level[a_f]<level[b_f]){
father[a_f] = b_f;
}else{
father[b_f] = a_f;
if(level[a_f]==level[b_f])
level[a_f]++;
}
}
//根据先序和中序序列构造二叉树
// root 树根的引用,因为要指针的内容,所以用到引用
//pre 前序数组
//mid 中序数组
//size 数组长度
void create_tree_pre_mid(node * &root, int *pre, int *mid, int size){
if(size<=0) return;
root = new node(pre[0]);
int idx = -1;
for(int i=0;i<size;i++){
if (pre[0] == mid[i]) {
idx = i; break;
}
}
assert(idx!=-1);
create_tree_pre_mid(root->left, pre+1, mid, idx);
create_tree_pre_mid(root->right, pre+1+idx, mid+1+idx, size-idx-1);
}
//查找某节点
node* find_node(node * root, int val){
if(root == NULL) return NULL;
if(root->val == val) return root;
node* temp = find_node(root->left,val);
if(temp !=NULL) return temp;
temp = find_node(root->right, val);
return temp;
}
//打印先序序列
void print_node(node *root){
if(root==NULL) return;
printf("%d ",root->val);
print_node(root->left);
print_node(root->right);
}
//先序序列长度
int N=6;
int ans[1000];
//先序序列
int pre[] = {1,3,4,6,5,2};
//中序序列
int mid[] = {4,3,6,5,1,2};
//查询 q1[i] 和 q2[i] 为一对查询
int q1[] = {1,3,2,4,5,6};
int q2[] = {4,5,6,1,2,3};
// 节点对应查询 的第几次询问
map<node*,vector<int> > index_map;
// 节点对应查询 的相对应值
map<node*,vector<node*> > quary;
// 对应所在并查集中的 根 所对应的 树的根
map<node*,node*> roots;
//初始化查询
void initquary(node* root, int N){
quary.clear();
index_map.clear();
roots.clear();
for(int i=0;i<N;i++){
node * q1_ = find_node(root, q1[i]);
assert(q1_ != NULL);
node * q2_ = find_node(root, q2[i]);
assert(q2_ != NULL);
//针对每一个节点,都加所有与之相关的节点都压入map中,在查询中使用
quary[q1_].push_back(q2_);
quary[q2_].push_back(q1_);
//下标也同理
index_map[q1_].push_back(i);
index_map[q2_].push_back(i);
}
}
void func(node * root){
if (root == NULL ) return;
// 先将左子树进行合并和计算
func(root->left);
//将左子树和根进行合并
unions(root,root->left);
//将 左子树和根 这个集合的根设为 当前根;
roots[findFather(root)] = root;
//对右子树同理
func(root->right);
//对右子树同理
unions(root,root->right);
//对右子树同理
roots[findFather(root)] = root;
//拿出和这个根相关的所有查询节点
vector<node*> quas = quary[root];
//以及对应是第几个查询,方便输出ans[]
vector<int> idxs = index_map[root];
//对相关节点进行遍历
for(int i=0;i<quas.size();i++){
int idx = idxs[i];//得到id
node * p = quas[i];//得到这节点的指针
node * p_father = findFather(p); //得到这个节点所对应的并查集的 父亲
if (roots.find(p_father)!=roots.end()){//查询这个节点对应的并查集的根节点有没有
//如果存在的话,说明这个节点先于当前 root节点被遍历过,
//所以可以直接根据其 并查集的父亲 得到对应这个并查集的根节点 root2
//这个root2 一定是当前root 和该查询节点 p 的最近公共根节点
ans[idx] =roots[p_father]->val;
}
}
}
int main(){
// tree init
node * root = NULL;
create_tree_pre_mid(root, pre, mid, N);
// set init
set_init(root);
assert(root !=NULL);
print_node(root);
initquary(root,N);
func(root);
printf("\n"); //打印最近公共节点
for(int i=0;i<N;i++){
printf("%d ",ans[i]);
}
printf("\n");
return 0;
}