2017牛课堂第三季第三课第一题-并查集用于二叉数最近公共节点查询

2017牛课堂第三季第三课第一题-并查集用于二叉数最近公共节点查询_子树

2017牛课堂第三季第三课第一题-并查集用于二叉数最近公共节点查询_#include_02

2017牛课堂第三季第三课第一题-并查集用于二叉数最近公共节点查询_并查集_03

思路:

首先,直接二叉树遍历,是肯定做不到的

利用并查集,维护当前查询的根节点,能顾保证实现。

具体的,首先设定好节点并查集,即在初始阶段,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; 
}