不相交集合数据结构(并查集)小结
不相交集合数据结构保持一组不相交的动态集合,集合中每一个元素有一个对象表示,或称其为代表。其主要操作有三个,
Make_Set(x) ; 对集合x进行初始化,每一个元素就是一个对象。
Union(X,Y) ; 将包含集合X,Y的动态集合合并为一个新的集合;
Find_Set(x) ; 返回包含x的集合的代表;
不相交集合的一个应用,确定无向连通图中的连通子图的个数。可以采用链表形式,数组形式,和有根树的方式表示。
基本过程是 Connected_Components (G) //G 表示无向图
Connected_Components (G)
{
for each vertex v ∈ V(G); // V(G) 表示G中的所有点定点
do Make_Set(v);
for each edge(u,v)∈ E(G); //E(G) 表示G中的所有边
do if(Find_Set(u) != Find_Set(v))
then Union(u,v);
}
链表表示法:
用链表表示不相交集合是指 每一个集合都用一个链表来表示,每个链表的第一个对象就是作为它所在的集合的代表,
集合中的每一个对象包含一个集合成员,一个指向代表的指针,一个指向下一个集合成员对象的指针。
每个链表都有一个头指针head,和一个尾指针tail.
基本结构如下:
typedef struct Vertex //链表对象
{
int data; //集合成员
struct Vertex *represent; //集合的代表
struct Vertex *next; //指向下一个集合成员的指针
}Vex;
typedef struct //链表集合
{
Vex *head;
Vex *tail;
int weight; //按权值优化时使用
}DisSet[MAX_VERTEX_NUM + 1]; //MAX_VERTEX_NUM 表示最大的顶点数
void Make_Set(int x) //建立一个新的集合
{
Vex *p;
p = (Vex *)malloc(sizeof(Vex));
p->represent = p;
p->data = x;
p->next = NULL;
DSGraph[x].head = p;
DSGraph[x].tail = p;
DSGraph[x].weight = 1; //按权值优化时使用
}
Vex * Find_Set(int x) //返回一个指针,指向包含x的集合的代表
{
Vex *p;
p = DSGraph[x].head;
return p->represent;
}
void Union_Set(int x, int y) //将包含x ,y 的动态集合合并为一个集合
{
Vex *p;
if(Find_Set(x) != Find_Set(y))
{
DSGraph[x].tail->next = DSGraph[y].head;
p = DSGraph[y].head;
while(p->next)
{
p->represent = DSGraph[x].head->represent;
p = p->next;
}
p->represent = DSGraph[x].head->represent;
DSGraph[x].tail = p;
}
}
Connected_Components (G) //G 即DSGraph
{
for each vertex v ∈ V(G); // V(G) 表示G中的所有点定点
do Make_Set(v);
for each edge(u,v)∈ E(G); //E(G) 表示G中的所有边
do if(Find_Set(u) != Find_Set(v))
then Union(u,v);
}
由于此方法效率不是很高,我们可以通过这个一种按权值优化的启发方式,其主要思想是在关于怎么合并,之前的链表表示,合并时,没有规定合并的顺序,
很有可能出现这样的情况,把一个大的集合与一个小的集合合并时,把大的集合的代表全部改为小的集合的代表,这样就会是的效率低下,当才用按权值合并时,
先比较权值,把权值小的集合的代表改为权值大的集合的代表,这样需要改的代表指针就会少很多,效率就会有所提高。
下面给出代码:
void Union_Set(int x, int y) //将包含x ,y 的动态集合合并为一个集合
{
int max,min,start;
Vex *p,*q;
if(Find_Set(x) != Find_Set(y))
{
p = Find_Set(x);
q = Find_Set(y);
if(DSGraph[p->data].weight < DSGraph[q->data].weight)
{
max = y;
min = x;
DSGraph[q->data].weight += DSGraph[p->data].weight;
DSGraph[q->data].tail->next = DSGraph[p->data].head;
start = q->data;
}
else
{
max = x;
min = y;
DSGraph[p->data].weight += DSGraph[q->data].weight;
DSGraph[p->data].tail->next = DSGraph[q->data].head;
start = p->data;
}
p = DSGraph[min].head;
while(p->next)
{
p->represent = DSGraph[max].head->represent;
p = p->next;
}
p->represent = DSGraph[max].head->represent;
DSGraph[start].tail = p;
}
}
数组方式
void Disjoint_Set() //并查集
{
int V[MAX_VERTEX_NUM + 1];
part = 0;
Min = vertex; //vertex 节点数,Min 连通子图数
memset(V,0,sizeof(V)); //初始化 等同于Make_Set(x);
for each edge(a,v)∈ E(G); //E(G) 表示G中的所有边
{
if(V[a] == 0 && V[b] == 0) //一条新边,化为同一个集合
{
part ++;
V[a] = V[b] = part;
Min --;
}
else
{
if(V[a] != 0 && V[b] == 0) //一点已在某个集合中,将另一点也归为这个集合中
{
V[b] = V[a];
Min --;
}
else
{
if(V[a] == 0 && V[b] != 0) //一点已在某个集合中,将另一点也归为这个集合中
{
V[a] = V[b];
Min --;
}
else
{
if(V[a] != V[b]) //两个店都已在某个集合中,且这两个集合不相同,需要合并。并修改集合代表
{
k = V[a];
for(j = 1; j <= City; j ++)
{
if(k == V[j])
{
V[j] = V[b]; //修改集合代表
}
}
Min --;
}
}
}
}
}
}
此方法比链表好快,效率更高。
有根树 :
每棵树表示一个集合,树的每个节点都包含集合的一个成员,还有一个指向其父节点的“指针”,并且采用按秩合并和路劲压缩的优化。
效率最高。
Make_Set(x) //初始化
p[x] = x; //表示x的父节点
rank[x] = 0; //表示x节点秩
Find_Set(x) //路劲压缩 可以使得每一个节点直接指向根节点
if(x != p[x])
then p[x] = Find_Set(p[x]);
return p[x];
Union(x,y) //按秩合并
if(Find_Set(x) != Find_Set(y))
then u = Find_Set(x);
v = Find_Set(y);
if(rank[u] > rank[v])
then p[v] = u;
rank[u] += rank[v];
else
p[u] = v;
rank[v] += rank[u];
Connected_Components (G)
{
for each vertex v ∈ V(G); // V(G) 表示G中的所有点定点
do Make_Set(v);
for each edge(u,v)∈ E(G); //E(G) 表示G中的所有边
do if(Find_Set(u) != Find_Set(v))
then Union(u,v);
}
浙公网安备 33010602011771号