1.并查集基础
并查集是一种很不一样的树形结构,用来解决一类连接问题
连接问题
问图上的两点是否相连
连接问题的应用
1 .网络中节点的连接状态
网络是一个抽象的概念:用户之间形成的网络
2.数学中集合类的实现:合并两个集合、判断是否属于同一个集合等
连接问题和路径问题的区别在于,连接问题只需要回答两个点是否连接,而路径问题需要将两个点连接的路径输出。所以连接问题比路径问题要回答的问题少
并查集这个数据结构需要实现:
对于一组数据,主要支持:
union(p,q) 将元素p和元素q所在的两个集合并成一个集合
find(p) 查找元素p在哪个集合
isConnected(p,q) 查找元素p,元素q是否是连接的(是否在一个集合中)
2.基础实现 Quick Union
将每个元素指向一个父亲节点,说明该元素和父亲节点相连,如果自己就是根节点,则指向自己
如果元素1想与【2,3】相连,指向这个集合的父亲节点2即可
如果集合【5,6,7】想与【2,3】相连,父亲节点5指向【2,3】集合的父亲节点2即可
数据表示 parent
使用parent数组存储指向的父亲节点 parent[i]代表数据i指向的父亲节点。初始化都指向自己
连接操作
将9与4的根节点连接在一起,判断4,9是否连接,看他们是否有同一个根节点
每次都将元素连接到根节点的目的是为了使得整个构成的结构不至于太长,查找根节点的时候不需要浪费太多的时间
代码实现
#include <iostream>
using namespace std;
class UnionFind{
private:
int* parent;//存储i指向的父亲节点
int count;
public:
UnionFind(int count)
{
this->count = count;
parent = new int[count];
for(int i=0; i<count; i++)
parent[i] = i; //让i的父亲节点指向自己
}
//查找一个节点的根节点
int find(int p)
{
while(parent[p] != p)
p = parent[p];
return p;
}
//判断p,q是否属于同一个集合,是否相连,即判断p,q的根节点是否相同
bool isConnected(int p, int q)
{
return find(p) == find(q);
}
//合并p,q所指的两个集合,即将p的根节点连接上q的根节点
void unionElements(int p, int q)
{
int pRoot = find(p);
int qRoot = find(q);
if(pRoot==qRoot) return;
parent[pRoot] = qRoot;
}
};
int main()
{
UnionFind u(10);
cout<<u.find(1)<<endl;
u.unionElements(3,4);
u.unionElements(5,6);
u.unionElements(7,8);
u.unionElements(4,6);
cout<<u.find(3)<<endl;
cout<<u.isConnected(3,6)<<endl;
return 0;
}
3.路径压缩优化
在find的过程中,将当前节点p遍历的每一个节点的parent[p]都指向根节点,这样就会使得树的节点层数维持在2,这样可以大大的优化查找根节点的效率
代码
//使用路径压缩进行优化,在查找的过程中,让当前查找节点所遍历到的每一个点都直接指向根节点
int find(int p)
{
if( p!=parent[p] )
parent[p] = find( parent[p] );
return parent[p];
}
4.解题模板
在实际解题的过程中,实现一个类太麻烦了。可以直接定义一个parent数组,将find函数封装好即可
#include <iostream>
using namespace std;
#define max 1000000
int parent[max]; //定义一个parent数组,parent[i]表示i元素所对应的集合为parent[i]
int find( int p ) //路径压缩的find算法
{
if( parent[p] != p ) return parent[p] = find( parent[p] ); //如果根不是自己,那么就递归调用find函数找到根并连接
return parent[p]; //否则直接返回
}
int main()
{
int n;
cin >> n;
for (int i = 1; i <= n; i++ )
{
parent[i] = i; //初始化
}
int m;
cin >> m;
for (int i = 1; i <= m; i++)
{
int x,y;
cin >> x >> y;
int rootx = find(x);
int rooty = find(y);
parent[rootx] = rooty; //由于进行了路径压缩,所以直接随意的把一个集合接到另一个上
}
return 0;
}