算法与数据结构实验题 12.2 球迷
★实验任务
在福大里,有 n 个学生,每个的学生都有自己喜爱的球星,已知有 m 对学生喜爱的球星相同,问你这 n 个学生喜爱的球星最多有多少个。
★数据输入
输入第一行为一个正整数 n,m。
接下来 m 行,每行输入 ai,bi,表示 ai 同学和 bi 同学喜爱的球星一样
80%的数据 1<=n,m<=1000.
100%的数据 1<=n,m<=100000.
★数据输出
输出一个正整数,表示答案。
输入示例 | 输出示例 |
10 9 | 1 |
1 2 |
|
1 3 |
|
1 4 |
|
1 5 |
|
1 6 |
|
1 7 |
|
1 8 |
|
1 9 |
|
1 10 |
|
输入示例 | 输出示例 |
10 4 | 7 |
2 3 |
|
4 5 |
|
4 8 |
|
5 8 |
|
两个方法本质相同:BFS求连通性,并查集求连通性.
方法一:
这道题是典型的图的搜索算法(BFS).用邻接矩阵实现的话,时间复杂度为O(N+E).
思路:
有着相同喜欢的球星的同学连在一起,形成一个连通子块。其他的同学作为独立点(特殊子块)。求共有多少个连通子块?
依据BFS的算法,对从1-n的每一个点遍历。
#include <cstdio>
#include <vector>
#include <queue>
using namespace std;
/*
结构体+可变数组 模拟邻接矩阵
*/
struct Graph{
vector <int> v;
};
Graph g[100100]; //图的数组
int turn [100100]={0}; //BFS过程中的标记数组。
void BFS(int i)
{
int x;
queue <int> q;
q.push(i);
vector <int> :: iterator it;
//对节点i,BFS,把和i连在一起的都遍历一遍。
while(!q.empty())
{
x=q.front();
q.pop();
turn[x]++;
for (it=g[x].v.begin();it!=g[x].v.end();it++)
{
if (turn[*it]==0)
{
q.push(*it);
}
}
}
}
int main()
{
int n,m,x,y,count=0;
scanf ("%d %d",&n,&m);
for (int i=0;i<m;i++)
{
scanf ("%d %d",&x,&y);
g[x].v.push_back(y);
g[y].v.push_back(x);
}
for (int i=1;i<=n;i++)
{
if (g[i].v.empty())
{
count++;
turn[i]++;
}
else
{
if (turn[i]==0)
{
count++;
BFS(i);
}
}
}
printf ("%d\n",count);
return 0;
}
方法2:
经典并查集模型
#include <cstdio>
#include <cstring>
using namespace std;
int s[100010];
int count;
void Make_Set(void)
{
memset(s,-1,sizeof(s)); //初始化所有节点
}
int Find(int x)
{
if (s[x]<=0) //找到根节点,返回
return x;
else
return (s[x]=Find(s[x])); //路径压缩,把x到根节点的路径上的所有节点变成根的儿子
}
void Union(int root1,int root2) //按秩求并
{
root1=Find(root1);
root2=Find(root2);
if (root1==root2) //两节点连通,退出
return ;
if (s[root2]<s[root1]) //root2树更深,把root1合并到root2
s[root1]=root2;
else{
if (s[root1]==s[root2]) //相同高度,合并root1高度+1
s[root1]--;
s[root2]=root1; //root2合并到root1
}
count--; //由于两集合合并,所有子树数量-1
}
int main()
{
int n,m,a,b,ra,rb;
scanf ("%d %d",&n,&m);
count=n;
Make_Set();
for (int i=0;i<m;i++)
{
scanf ("%d %d",&a,&b);
Union(a,b);
}
printf ("%d\n",count);
return 0;
}