并查集
并查集是一种树型的数据结构,用于处理一些不相交集合的合并及查询问题。
假设有M个球,然后给出N条约束条件(每个条件以Ma,Mb表示ab两个球放在同一个盒子里),让你求需要多少个盒子,以及每个盒子里球的编号。
在数据规模比较小时,可以考虑用图来表示。每个连通子图里是一个盒子里的所有小球
但是在规模比较大的时候,图法非常低效。这时候需要考虑使用并查集
并查集的核心为:
- 对两个已有集合的合并 (合并)
- 获取一个元素所在的集合 (查找)
- 初始化集合
查找:
如果用一个变量来唯一的标识集合,那么此变量的值就是查找的结果
我们把这个变量起名为parent
合并:
假设被合并的集合为V,合并到U上
合并时,需要修改V的parent的值。
为了提高效率,我们并不立刻修改V的所有元素的parent的值,而是修改元素e到V路径上的元素的parent的值
初始化:
将parent指向自身
class DS
{
public:
DS(int x) //初始化
{
val = x;
parent=this;
}
void contact(DS* b)//合并
{
DS* v = b->find();
DS* u = this->find();
v->parent = u;
}
DS* find() //查找
{
return this==parent?parent:parent=parent->find();
}
int val;
DS* parent;
};
我们需要一个通过x获取DS的途径
map<int,DS*> forest;
插入关系
int a,b;
cin >> a >> b;
if(forest.count(a)<1)
{
forest.insert(make_pair(a,new DS(a))); //如果不存在则初始化集合
}
if(forest.count(b)<1)
{
forest.insert(make_pair(b,new DS(b)));//如果不存在则初始化集合
}
(forest.find(a)->second)->contact(forest.find(b)->second);
判断是否在一个集合
bool is_in_same_set(int a,int b) //判断是否在一个集合
{
auto pair_a = forest.find(a);
auto pair_b = forest.find(b);
DS* parent_a = nullptr;
DS* parent_b = nullptr;
if(pair_a!=forest.end())
parent_a = pair_a->second->find();
if(pair_b!=forest.end())
parent_b = pair_b->second->find();
return (parent_a!=nullptr && parent_a==parent_b);
}
对于刚才那道小球的题目,上面的写法输出每个集合很不方便。
#include<bits/stdc++.h>
template<typename T,const bool _contain_dat> class DS
{
public:
static std::map<T,DS<T,_contain_dat>*> mapper;
const static bool pointer_only = !_contain_dat;
DS(T x)
{
parent = this;
if(!pointer_only)
{
S = new std::set<T>();
S->insert(x);
}
mapper[x] = this;
}
~DS()
{
if(!pointer_only)
{
delete S;
}
}
static void add(T a,T b)//将两个元素所在的集合合并为一个集合
{
if(!contain(a))
mapper[a] = new DS(a);
if(!contain(b))
mapper[b] = new DS(b);
mapper[a]->contact(mapper[b]);
}
static bool contain(T x) //检查元素x是否存在某个集合中
{
return mapper.count(x)>0;
}
static bool sameSet(T a,T b)//判断两个元素是否属于同一集合
{
DS *ds_a = nullptr;
DS *ds_b = nullptr;
if(contain(a))
{
ds_a = mapper[a]->get_parent();
}
if(contain(b))
{
ds_b = mapper[b]->get_parent();
}
return ((ds_a!=nullptr)&&(ds_a==ds_b));
}
static void show_sets() //打印所有的集合
{
if(pointer_only)
return;
for(auto map_ele:mapper)
{
DS* ds = map_ele.second;
if(ds->get_parent()==ds)
{
std::cout<<"{";
for(auto ele:*(ds->get_set()))
{
std::cout<<ele<<",";
}
std::cout<<"}"<<std::endl;
}
}
}
std::set<T>* get_set()
{
return this->get_parent()->S;
}
private:
void contact(DS* b) //将b加入当前集合
{
if(b==this)
return;
DS* u = this->get_parent();
DS* v = b->get_parent();
if(!pointer_only)
{
std::set<T>* uSet = u->get_set();
std::set<T>* vSet = v->get_set();
uSet->insert(vSet->begin(),vSet->end());
delete vSet;
//vSet = nullptr;
}
v->parent = u;
}
DS* get_parent()
{
return this==parent?parent:parent=parent->get_parent();
}
std::set<T>* S;
DS* parent;
};
template<typename T,const bool dat> std::map<T,DS<T,dat>*> DS<T,dat>::mapper;
int main()
{
unsigned m,b1,b2;
std::cin >> m;
while(m--)
{
std::cin >> b1 >> b2;
DS<unsigned,true>::add(b1,b2);
}
DS<unsigned,true>::show_sets();
return 0;
}
上面的代码增加了一个set<T>来存储具体的集合
下面是简单的测试:
输入
4
5 1
2 6
4 7
1 7
输出
{2,6,}
{1,4,5,7,}
PS:其实有更简洁的方法实现,不过思路也类似,具体来说是使用一个
map<T,T> forest,初始化forest[T]=T,然后修改forest[T]的内容就相当于修改Parent了。感兴趣可以自行百度