10.2图的存储
可以用邻接矩阵或者邻接表来存储图
邻接矩阵:二维数组,对于无向图,邻接矩阵是一个对称矩阵,二维数组占的内存比较大,只适用于顶点小于1000的题目
邻接表:使用N个链表来组成邻接表,链表比较复杂,所以使用vector来实现邻接表,比如
vector<int> Adj[N];
vector<Node> Adj[N];
这里还可以定义结构体Node的构造函数,不用定义临时变量就可以实现加边操作
10.3图的遍历
10.3.1用DFS法遍历图
PAT A1034
这题使用邻接表来存储
使用DFS解决:
PAT A1034 Head of a Gang
这题用了DFS,无向图,邻接矩阵
#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<map>
#include<climits>
#include <cstring>
#include<iostream>
using namespace std;
const int maxn = 2010;
map<int, string> intToString;//编号->姓名
map<string, int> stringToInt;//姓名->编号
map<string, int> Gang;//head->人数
int G[maxn][maxn], weight[maxn];
int n, k, numPerson;
int vis[maxn] = { 0 };//0表示未被访问,1表示被访问
//DFS访问单个连通块
void DFS(int nowVisit,int &head,int &numMember,int &totalValue)
{
numMember++;
vis[nowVisit] = 1;
if (weight[nowVisit] > weight[head])
{
head = nowVisit;
}
for (int i = 0; i < numPerson; i++)//枚举所有人
{
if (G[nowVisit][i] > 0)
{
totalValue += G[nowVisit][i];
G[nowVisit][i] = G[i][nowVisit] = 0;
if (vis[i] == 0)
{
DFS(i, head, numMember, totalValue);
}
}
}
}
void DFSTrave()
{
for (int i = 0; i < numPerson; i++)
{
if (vis[i] ==0)
{
int head = i, numMember = 0, totalValue = 0;
DFS(i,head,numMember,totalValue);
if (numMember > 2 && totalValue > k)
Gang[intToString[head]] = numMember;
}
}
}
int change(string str)
{
if (stringToInt.find(str) != stringToInt.end())
return stringToInt[str];
else
{
stringToInt[str] = numPerson;
intToString[numPerson] = str;
return numPerson++;
}
}
int main()
{
int w;
string str1, str2;
cin >> n >> k;
for (int i = 0; i < n; i++)
{
cin >> str1 >> str2 >> w;
int id1 = change(str1);
int id2 = change(str2);
weight[id1] += w;
weight[id2] += w;
G[id1][id2] += w;
G[id2][id1] += w;
}
DFSTrave();
cout << Gang.size() << endl;
map<string, int>::iterator it;
for (it = Gang.begin(); it != Gang.end(); it++)
{
cout << it->first <<" "<< it->second << endl;
}
return 0;
}
使用并查集解决:
10.3.2用BFS法遍历图
PAT A1076 Forwards on Weibo
这题因为有一个转发层数的限制,所以用BFS比DFS简单许多,在BFS有一个判断,要先把转发数加上,再判断,最后判断成功才可以设置Inq数组,然后入队
if (inq[next.id] == 0 && next.layer <= L)
{
q.push(next);
inq[next.id] = 1;
numForward++;
}
另外当输入追随者的时候需要一条从粉丝指向博主的有向边
Adj[idfollow].push_back(user);//注意这里的边在邻接表里面是怎么写的
代码如下:
#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include <cstring>
#include<vector>
#include<queue>
using namespace std;
const int maxv = 1010;
struct node {
int id;
int layer;
};
vector<node> Adj[maxv];//邻接表
int inq[maxv] = { 0 };//=0表示不在队列q中,反之表示在队列q中
int BFS(int s, int L)
{
int numForward = 0;
queue<node> q;
node start;
start.id = s;
start.layer = 0;
q.push(start);
inq[start.id] = 1;
while (!q.empty())
{
node topnode = q.front();
q.pop();
int u = topnode.id;
for (int i = 0; i < Adj[u].size(); i++)
{
node next = Adj[u][i];
next.layer = topnode.layer + 1;
if (inq[next.id] == 0 && next.layer <= L)
{
q.push(next);
inq[next.id] = 1;
numForward++;
}
}
}
return numForward;
}
int main()
{
node user;
int n, L, numfollow, idfollow;
scanf("%d %d",&n,&L);
for (int i = 1; i <=n; i++)
{
user.id = i;
scanf("%d",&numfollow);
for (int j = 1; j <= numfollow; j++)
{
scanf("%d",&idfollow);
Adj[idfollow].push_back(user);//注意这里的边是怎么写的
}
}
int numquery, s;
scanf("%d",&numquery);
for (int i = 1; i <= numquery; i++)
{
memset(inq, 0, sizeof(inq));
scanf("%d",&s);
int numForward = BFS(s, L);
printf("%d\n",numForward);
}
return 0;
}
习题
问题 A: 第一题
问题描述:该题的目的是要你统计图的连通分支数。
- 输入
每个输入文件包含若干行,每行两个整数i,j,表示节点i和j之间存在一条边。
- 输出
输出每个图的联通分支数。
- 样例输入
1 4
4 3
5 5
- 样例输出
2
邻接表做法:
这题看起来蛮简单的怎么做起来就不一样了orz,我一开始纠结题目给的编号都不是连在一起的,这样该怎么枚举呢,后来看到题解才知道可以用Adj[i].size()!=0来判断这个编号有没有出现过,另外还需要max来记录最大的编号
这部分也是必要的:
if (temp1!=temp2)
{
Adj[temp1].push_back(temp2);
Adj[temp2].push_back(temp1);
}
else
{
Adj[temp1].push_back(temp2);
}
否则会显示运行错误,不知道为啥会显示这个,我觉得加不加判断都一样
#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<climits>
#include<iostream>
#include<vector>
#include<map>
using namespace std;
const int maxn = 500010;
vector<int> Adj[maxn];
int sum=0;//表示连通块的个数
int vis[maxn] = { 0 };
//DFS访问单个连通块
void DFS(int u)
{
vis[u] = 1;
for (int i = 0; i < Adj[u].size(); i++)
{
int v = Adj[u][i];
if (vis[v] == 0)
{
DFS(v);
}
}
}
int main()
{
int temp1, temp2;
int i;
int max;
//下面构建邻接表,计算节点个数n;
while (scanf("%d %d", &temp1, &temp2)!=EOF)
{
if (temp1!=temp2)
{
Adj[temp1].push_back(temp2);
Adj[temp2].push_back(temp1);
}
else
{
Adj[temp1].push_back(temp2);
}
max = max > temp1 ? max : temp1;
max = max > temp2 ? max : temp2;
}
for (i = 0; i <=max; i++)
{
if (vis[i] == 0&&Adj[i].size()!=0)
{
sum++;
DFS(i);
}
}
printf("%d\n",sum);
return 0;
}
并查集做法:
想了想这题还可以用并查集做,思路简单不少
如果提示运行错误,错误的原因一般是数组越界
①maxn取到了数组的上界
②maxn取小了,题目输入了很多很多数让数组越界访问了
#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<set>
using namespace std;
const int maxn = 1000010;
int father[maxn];
set<int> st;
int findFather(int x)
{
int a = x;
while (x != father[x])
{
x = father[x];
}
while (a != father[a])
{
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
void Union(int a, int b)
{
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB)
{
father[faA] = faB;
}
}
void init(int n)
{
for (int i = 1; i < n; i++)
{
father[i] = i;
}
}
int main()
{
int temp1, temp2;
int i;
//因为不知道总数,所以就初始化全部的序号
init(maxn);
while (scanf("%d %d", &temp1, &temp2)!=EOF)
{
Union(temp1,temp2);
st.insert(temp1);
st.insert(temp2);
}
int ans = 0;
for (set<int>::iterator it = st.begin(); it != st.end(); it++)
{
if (father[*it] == *it) ans++;
}
printf("%d\n",ans);
return 0;
}
问题 B: 连通图
问题描述:给定一个无向图和其中的所有边,判断这个图是否所有顶点都是连通的。
- 输入
每组数据的第一行是两个整数 n 和 m(0<=n<=1000)。n 表示图的顶点数目,m 表示图中边的数目。如果 n 为 0 表示输入结束。随后有 m 行数据,每行有两个值 x 和 y(0<x, y <=n),表示顶点 x 和 y 相连,顶点的编号从 1 开始计算。输入不保证这些边是否重复。
- 输出
对于每组输入数据,如果所有顶点都是连通的,输出"YES",否则输出"NO"。
- 样例输入
4 3
4 3
1 2
1 3
5 7
3 5
2 3
1 3
3 2
2 5
3 4
4 1
7 3
6 2
3 1
5 6
0 0
- 样例输出
YES
YES
NO
DFS做法:
#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<climits>
#include<iostream>
#include<vector>
#include<map>
using namespace std;
const int maxn = 1010;
vector<int> Adj[maxn];
int vis[maxn] = { 0 };
//DFS访问单个连通块
void DFS(int u)
{
vis[u] = 1;
for (int i = 0; i < Adj[u].size(); i++)
{
int v = Adj[u][i];
if (vis[v] == 0)
{
DFS(v);
}
}
}
int main()
{
int temp1, temp2;
int i;
int n, m;
int sum;
while (scanf("%d %d", &n, &m) != EOF)
{
if (n == 0 && m == 0)
break;
else
{
for (i = 0; i < m; i++)
{
scanf("%d %d", &temp1, &temp2);
if (temp1 != temp2)
{
Adj[temp1].push_back(temp2);
Adj[temp2].push_back(temp1);
}
else
{
Adj[temp1].push_back(temp2);
}
}
sum = 0;
for (i = 1; i <= n; i++)
{
if (vis[i] == 0)
{
sum++;
DFS(i);
}
}
if (sum == 1)
printf("YES\n");
else
printf("NO\n");
}
}
return 0;
}
并查集做法:
#define _CRT_SECURE_NO_WARNINGS 1
#include <cstdio>
#include<set>
using namespace std;
const int maxn = 1010;
int father[maxn];
int findFather(int x)
{
int a = x;
while (x != father[x])
{
x = father[x];
}
while (a != father[a])
{
int z = a;
a = father[a];
father[z] = x;
}
return x;
}
void Union(int a, int b)
{
int faA = findFather(a);
int faB = findFather(b);
if (faA != faB)
{
father[faA] = faB;
}
}
void init(int n)
{
for (int i = 1; i < n; i++)
{
father[i] = i;
}
}
int main()
{
int n, m;
int temp1, temp2;
int i;
int ans;
while (scanf("%d %d", &n, &m) != EOF)
{
if (n == 0 && m == 0)
break;
else
{
init(n);
for (i = 0; i < m; i++)
{
scanf("%d %d", &temp1, &temp2);
Union(temp1, temp2);
}
ans = 0;
for (i = 1; i <= n; i++)
{
if (father[i] == i) ans++;
}
if (ans == 1)
printf("YES\n");
else
printf("NO\n");
}
}
return 0;
}