王道机试5.2 并查集 含练习
例5.1 畅通工程
#include<stdio.h>
using namespace std;
int tree[1000];
int findroot(int x)
{
if(tree[x]==-1)
return x;
else
{
int ret=findroot(tree[x]);
tree[x]=ret;
return ret;
}
}
int main()
{
int m,n;
while(scanf("%d",&n)!=EOF&&n!=0)
{
scanf("%d",&m);
for(int i=1;i<=n;i++)
tree[i]=-1;
for(int i=0;i<m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
int ra=findroot(a);
int rb=findroot(b);
if(ra!=rb)
tree[ra]=rb;
}
int ans=0;
for(int i=0;i<=n;i++)
{
if(tree[i]==-1)
ans++;
}
printf("%d\n",ans-1);
}
return 0;
}
练1:连通图
#include<stdio.h>
using namespace std;
int tree[1000];
int findroot(int x)
{
if(tree[x]==-1)
return x;
else
{
int ret=findroot(tree[x]);
tree[x]=ret;
return ret;
}
}
int main()
{
int m,n;
while(scanf("%d%d",&n,&m)!=EOF)
{
if(m==0&&n==0)
break;
for(int i=1;i<=n;i++)
tree[i]=-1;
for(int i=0;i<m;i++)
{
int a,b;
scanf("%d%d",&a,&b);
int ra=findroot(a);
int rb=findroot(b);
if(ra!=rb)
tree[ra]=rb;
}
int ans=0;
for(int i=0;i<=n;i++)
{
if(tree[i]==-1)
ans++;
}
if(ans>1)
printf("NO\n");
else
printf("YES\n");
}
return 0;
}
练3:Head of a Gang
使用并查集。
建立了两个结构体,一个person用于保存每个人的名字、权重、所在集合的根节点下标。一个gang用于保存结果,每个帮派的头目及帮派的成员。之所以需要第二个结果结构体是因为我在并查集的过程中无法保证根节点为头目,输出时没办法按名字升序输出,所以用一个结果结构体数组sort一下再输入。在建立一个sum数组保存每个集合的总权重,用于与k比较。
牛客网代码来源
https://www.nowcoder.com/questionTerminal/a31b1ea6c87647bc86e382acaf21c53b?f=discussion
#include<stdio.h>
#include<string.h>
#include<algorithm>
#define N 26
using namespace std;
typedef struct Person{
char name[5];
int weight;
int parents;//parents保存集合的根节点下标,根节点的parent保存集合成员个数
}Person;
typedef struct gang{
char head[5];
int number;
}gang;//结果结构体保存头目和帮派人数
Person p[N];
int sum[N];
bool cmp(gang a,gang b){
return strcmp(a.head,b.head)<0;
}//字典排序函数
int FindRoot(int x){
if(p[x].parents<0) return x;//parents小于0代表是根节点,且数值为成员个数
else{
p[x].parents=FindRoot(p[x].parents);
return p[x].parents;
}
}//并查集函数
int main(){
int n,k;
while(scanf("%d%d",&n,&k)!=EOF){
getchar();//用于消除缓冲区回车防止字符串输入出错
int i;
for(i=0;i<N;i++){
p[i].parents=-1;
p[i].weight=0;
sum[i]=0;
}//初始化
while(n--){
char name1[5],name2[5];
int weight,no1,no2,p1,p2;
scanf("%s%s%d",name1,name2,&weight);
getchar();//用于消除缓冲区同上
no1=name1[0]-'A';
no2=name2[0]-'A';//由名字找到这个人下标
strcpy(p[no1].name,name1);//记得要保存该人的名字
p[no1].weight+=weight;//每遇到这个人就要加上一次权重
strcpy(p[no2].name,name2);
p[no2].weight+=weight;
p1=FindRoot(no1);
p2=FindRoot(no2);
if(p1!=p2){//不是同一个集合要合并
p[p1].parents+=p[p2].parents;
p[p2].parents=p1;
sum[p1]=sum[p1]+sum[p2]+weight;
}//合并的过程,顺便加上集合的总权重
else
sum[p1]+=weight;//如果是同一个集合也要加上权重
}
int ans=0;//保存帮派数目
gang g[10];//结果数组,26个人帮派最多有8个,10的数组够了
for(i=0;i<N;i++){
if(p[i].parents<-2&&sum[i]>k){//人数超过2人,总权重大于k则为帮派
g[ans].number=-p[i].parents;//保存下帮派人数
int parent=i;//根节点存一下,其实有点多余,可以直接用i
int max=i;//权重最大者下标,初始为根节点
for(int j=0;j<N;j++){
if(FindRoot(j)==parent&&p[j].weight>p[max].weight)
max=j;//如果属于此集合且权重更大,替换一下
}
strcpy(g[ans].head,p[max].name);//把头目名字存进来
ans++;//接着存下一个帮派
}
}
printf("%d\n",ans);//输入帮派数目
sort(g,g+ans,cmp);//按字典sort一下
for(i=0;i<ans;i++)
printf("%s %d\n",g[i].head,g[i].number);//输出头目名字和帮派人数
}
return 0;//over,哈哈
}