班上有 N 名学生。其中有些人是朋友,有些则不是。他们的友谊具有是传递性。如果已知 A 是 B 的朋友,B 是 C 的朋友,那么我们可以认为 A 也是 C 的朋友。所谓的朋友圈,是指所有朋友的集合。
给定一个 N ∗ N N * N N∗N 的矩阵 M,表示班级中学生之间的朋友关系。如果 M [ i ] [ j ] = 1 M[i][j] = 1 M[i][j]=1,表示已知第 i 个和 j 个学生互为朋友关系,否则为不知道。你必须输出所有学生中的已知的朋友圈总数。
思路:可以将题目转换为求图上连通分量的个数,所以需要遍历图,这个时候我们可以想到bfs,和dfs,当然,求连通分量的个数也有专门的方法,使用并查集unique find。并查集有固定模板,直接套用即可。且并查集有两个优化,路径压缩(针对find)和秩优化(针对unique/merge)
dfs
class Solution {
public:
vector<int> vis;
void dfs(int i,int N,vector<vector<int>>& M)
{
for(int j=0;j<N;j++)
{
if ( M[i][j] == 0) continue;//不存在边
if (vis[j] ==1) continue;
vis[j] = 1;
dfs(j,N,M);
}
}
int findCircleNum(vector<vector<int>>& M) {
int N=M.size();
if(N==0) return 0;
vis.assign(N,0);
int ans=0;
for(int i=0;i<N;i++)
{
if(!vis[i])
{
ans++;
vis[i]=1;
dfs(i,N,M);
}
}
return ans;
}
};
bfs
class Solution {
public:
int findCircleNum(vector<vector<int>>& M) {
int N = M.size();
if (N == 0) return 0;
vector<int> vis(N, 0);
queue<int> q;
int ans = 0;
for (int i = 0; i < N; i++)
{
if (!vis[i])
{
q.push(i);
while (!q.empty())
{
int tmp = q.front();
q.pop();
vis[tmp] = 1;
for (int j = 0; j < N; j++)
{
if (vis[j]) continue;
if (M[tmp][j] == 0) continue;//不存在边
q.push(j);
vis[j]=1;
}
}
ans++;
}
}
return ans;
}
};
unique find
主要是一个find方法和一个unique(merge)方法。
unique find朴素写法
class Solution {
public:
//uinque find 并查集
vector<int> parent;
int find(int i)
{
while(i!=parent[i])
{
i=parent[i];
}
return i;
}
void unique(int x, int y)
{
int f1=find(x);
int f2=find(y);
if(f1!=f2)
parent[f2]=f1;
}
int findCircleNum(vector<vector<int>>& M) {
int N=M.size();
if(N==0) return 0;
for(int i=0;i<N;i++)
{
parent.push_back(i);
}
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
if(M[i][j])
unique(i,j);
}
}
int res=0;
for(int i=0;i<N;i++)
{
if(parent[i]==i)
res++;
}
return res;
}
};
unique find优化
1、路径压缩(针对find),将每个节点在parent都指向真正的本元(祖宗)。在进行一次遍历即可
2、秩优化(针对unique/merge) 在何必跟两颗森林的时候,将秩小的合并到秩大的上,可以减少开销。
class Solution {
public:
//uinque find 并查集
vector<int> parent;
vector<int> ranks;
//路径压缩
int find(int i)
{
int j=i;
while(parent[i]!=i)
{
i=parent[i];
}
while(parent[j]!=i)
{
parent[j]=i;
}
return i;
}
//优化2.将秩小的合并到秩大的
void unique(int x, int y)
{
int f1=find(x);
int f2=find(y);
if(ranks[f1]==ranks[f2])//当左右森林的秩相等时,合并merage的时候源森林秩+1;
{
parent[f2]=f1;
ranks[f1]++;
}
else if(ranks[f1]>ranks[f2])
parent[f2]=f1;//
else
parent[f1]=f2;
}
int findCircleNum(vector<vector<int>>& M) {
int N=M.size();
if(N==0) return 0;
ranks.assign(N,1);
for(int i=0;i<N;i++)
{
parent.push_back(i);
}
for(int i=0;i<N;i++)
{
for(int j=0;j<N;j++)
{
if(M[i][j])
unique(i,j);
}
}
int res=0;
for(int i=0;i<N;i++)
{
if(parent[i]==i)
res++;
}
return res;
}
};