算法描述:
并查集(union-find sets)是一种简单的用途广泛的集合. 并查集是若干个不相交集合,能够实现较快的合并和判断元素所在集合的操作,应用很多,如其求无向图的连通分量个数、最小公共祖先、带限制的作业排序,还有最完美的应用:实现Kruskar算法求最小生成树。
算法实现:
//并查集判断是否存在环
#include <iostream>
#include <stdlib.h>
#include <string.h>
using namespace std;
//图中的边
struct Edge
{
int start;//边的起点
int end;//边的终点
};
//图结构体
struct Graph
{
// V顶点个数, E边的个数
int V,E;
// 图就是边的集合!!!!!
struct Edge* edge;
};
//创建一个图
struct Graph* createGraph(int V, int E)
{
struct Graph* graph=(struct Graph*) malloc(sizeof(struct Graph));
graph->V = V;
graph->E = E;
//生成graph->E条边的图
graph->edge = (struct Edge*) malloc(graph->E*sizeof(struct Edge));
return graph;
};
//查找元素i所在集合(根节点)
int find(int parent[], int i)
{
//一维数组parent[] 来记录子集合
if(parent[i]==-1)
{//i的根节点是其自身
return i;
}
//查找i的根节点所在集合
return find(parent, parent[i]);
}
//合并集合
void join(int parent[], int x, int y)
{
int xset=find(parent,x);
int yset=find(parent,y);
if(xset != yset)
{
parent[xset]=yset;
}
}
//检测是否有环
int isCycle(struct Graph* graph)
{
int *parent = (int*) malloc(graph->V * sizeof(int));
//初始化所有集合(每个节点)
//parent[n] 的每个元素都为-1,共有n个子集合,表示集合只有当前顶点一个元素
memset(parent, -1, sizeof(int) * graph->V);
//遍历所有边
for(int i=0;i<graph->E;++i)
{
int x = find(parent, graph->edge[i].start);
int y = find(parent, graph->edge[i].end);
if(x==y)
{//如果在一个集合就找到了环
return 1;
}
join(parent, x, y);
}
return 0;
}
int main()
{
/* 创建图
0
| \
| \
1----2 */
struct Graph* graph = createGraph(3,3);
// 添加边 0-1
graph->edge[0].start=0;
graph->edge[0].end=1;
// 添加边 1-2
graph->edge[1].start=1;
graph->edge[1].end=2;
// 添加边 0-2
graph->edge[2].start=0;
graph->edge[2].end=2;
if (isCycle(graph))
{
cout<<"graph contains cycle"<<endl;
}
else
{
cout<<"graph doesn't contain cycle"<<endl;
}
return 0;
}
九度1024 畅通工程
//使用并查集实现了kruskal算法求最小花费树
#include <iostream>
#include<stdio.h>
#include<stdlib.h>
using namespace std;
typedef struct node
{
int start;
int end;
int cost;
}Node;
int father[101];
int rank[101];
void Make_Set(int M)
{
for(int i=1;i<=M;i++)//村庄从1开始编号
{
father[i]=i;
rank[i]=0;
}
}
int Find_Set(int x)
{
if(x != father[x])
{
father[x]=Find_Set(father[x]);
}
return father[x];
}
int Union(int x,int y)
{
x=Find_Set(x);
y=Find_Set(y);
if(x==y) return 0;//若没有合并,则返回0
if(rank[x]>rank[y])
{
father[y]=x;
rank[x] += rank[y];
}
else
{
if(rank[x]==rank[y])
{
++rank[y];
}
father[x]=y;
}
return 1;//若进行了合并,则返回1
}
int cmp(const void *p, const void *q)
{
Node * p1=(Node *)p;
Node * q1=(Node *)q;
return p1->cost - q1->cost;//从小到大
}
int main()
{
int N,M;
int ans;
int cnt;
Node road[5000];
while(cin>>N>>M&&N)
{
for(int i=0;i<N;i++)
{
scanf("%d%d%d",&road[i].start,&road[i].end,&road[i].cost);
}
qsort(road,N,sizeof(Node),cmp);//将N条道路的花费值从小到大排序
Make_Set(M);
ans=0;
cnt=0;
for(int i=0;i<N;i++)
{
if(cnt==M-1)//按照树的定义,M个点有M-1条连线
break;
if(Union(road[i].start,road[i].end))
{
++cnt;
ans += road[i].cost;
}
}
if(cnt==M-1)//实现了kruskal算法求最小花费树
{
printf("%d\n",ans);
}
else
{
printf("?\n");
}
}
return 0;
}