并查集
一、并查集概念
并查集: 并查集是一种树型的数据结构,用于处理一些不交集(Disjoint Sets)的合并及查询问题。
二、并查集的两种操作
2.1 find()判断两个元素是否属于同一个集合
int find(int parent[], int i)
{
//parent存储i节点的父节点,parent[i]==i即为根节点
if (parent[i] == i)
return i;
return find(parent, parent[i]);
}
2.2 union()合并两个不相交的元素
void Union(int parent[], int x, int y)
{
int xset = find(parent, x); //x的集合
int yset = find(parent, y); //y的集合
//若两个集合不相等
//简单粗暴将两个集合合并
if(xset != yset)
{
parent[xset] = yset;
}
}
三、并查集的优化
3.1 路径压缩
显然上述查询中,如果简单粗暴的合并,该树可能变成线性的,查询时间将变成O(n)
所以我们在查找某一结点的根节点时候,顺便把他的子孙直接与其根节点相连,是树变扁,这样极大的提高了查找效率,变为O(logn)
int find(struct subset subsets[], int i)
{
// 发现根节点并且将节点直接指向根节点,根节点指向自己
if (subsets[i].parent != i)
subsets[i].parent = find(subsets, subsets[i].parent);
return subsets[i].parent;
}
3.2 按秩优化
同上面的道理,希望树的深度尽量小,所以合并时将元素所在深度小的集合合并到元素所在深度大的集合。这里用rank(秩)代表树的高度
void Union(struct subset subsets[], int x, int y)
{
int xroot = find(subsets, x);
int yroot = find(subsets, y);
//rank小的子集连接到较大的子集
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[xroot].rank > subsets[yroot].rank)
subsets[yroot].parent = xroot;
// 如果相同连接rank+1,显然相同高度的树连接到一起,高度自然+1
else
{
subsets[yroot].parent = xroot;
subsets[xroot].rank++;
}
}
四、并查集判环
如果两个节点在同一个集合,那么说明有环
for(int e = 0; e < E; ++e)
{
int x = find(subsets, graph->edge[e].src);
int y = find(subsets, graph->edge[e].dest);
if (x == y)
return true;
Union(subsets, x, y);
}
五、完整代码
#include <iostream>
#include <cstdio>
#include <cstdlib>
using namespace std;
struct Edge
{
int src, dest;
};
struct Graph
{
// V个节点,E条边
int V, E;
struct Edge* edge;
};
struct subset
{
int parent;
int rank;
};
struct Graph* createGraph(int V, int E)
{
struct Graph* graph = (struct Graph*) malloc( sizeof(struct Graph) );
graph->V = V;
graph->E = E;
graph->edge = (struct Edge*) malloc( graph->E * sizeof( struct Edge ) );
return graph;
}
int find(struct subset subsets[], int i)
{
if (subsets[i].parent != i)
subsets[i].parent = find(subsets, subsets[i].parent);
return subsets[i].parent;
}
void Union(struct subset subsets[], int x, int y)
{
int xroot = find(subsets, x);
int yroot = find(subsets, y);
if (subsets[xroot].rank < subsets[yroot].rank)
subsets[xroot].parent = yroot;
else if (subsets[xroot].rank > subsets[yroot].rank)
subsets[yroot].parent = xroot;
else
{
subsets[yroot].parent = xroot;
subsets[xroot].rank++;
}
}
bool isCycle( struct Graph* graph )
{
int V = graph->V;
int E = graph->E;
struct subset *subsets =
(struct subset*) malloc( V * sizeof(struct subset) );
for (int v = 0; v < V; ++v)
{
subsets[v].parent = v;
subsets[v].rank = 0;
}
for(int e = 0; e < E; ++e)
{
int x = find(subsets, graph->edge[e].src);
int y = find(subsets, graph->edge[e].dest);
if (x == y)
return 1;
Union(subsets, x, y);
}
return 0;
}
int main()
{
int V = 3, E = 3;
struct Graph* graph = createGraph(V, E);
graph->edge[0].src = 0;
graph->edge[0].dest = 1;
graph->edge[1].src = 1;
graph->edge[1].dest = 2;
graph->edge[2].src = 0;
graph->edge[2].dest = 2;
if (isCycle(graph))
printf( "Contains cycle" );
else
printf( "Doesn't contain cycle" );
return 0;
}