一、邻接矩阵
- 使用一个二维数组表示图,例如graph[i][j]graph[i][j]graph[i][j]表示顶点iii和jjj之间的权值,但是对于稀疏图,以及顶点数稍微多一些的情况非常不友好,浪费很多空间,不过有时也能使用。
随便贴一道题如下
问题描述:
对于一个有向图,求从指定顶点出发能够找到的顶点数
输入
第1行:2个空格分开的整数n(2<=n<=200)n(2<=n<=200)n(2<=n<=200)和m(10<=m<=20000)m(10<=m<=20000)m(10<=m<=20000),分别表示图的顶点数和边数。
第2..m+12..m+12..m+1行:每行222个空格分开的整数iii,jjj,iii表示一条边的起点,jjj表示终点。
第m+2m+2m+2行:111个整数k(1<=k<=n)k(1<=k<=n)k(1<=k<=n),表示指定的顶点。
输出
一个整数,能够找到的最多顶点数
样例输入
5 8
3 5
4 5
4 3
2 3
5 4
4 1
4 2
2 4
1
样例输出
1
策略分析:
邻接矩阵存储,DFS即可代码
#include <iostream>
#define MAXN 205
using namespace std;
bool g[MAXN][MAXN], vst[MAXN];
int n, m, s, ans;
void dfs(int x) {
if(vst[x]) return; //减枝
for(int i = 1; i <= n; i++)
if(g[x][i]) {
g[x][i] = 0;
dfs(i);
}
vst[x] = 1; //记录一下,避免重复搜索
}
int main () {
ios::sync_with_stdio(0);
int u, v, s;
cin >> n >> m;
for(int i = 1; i <= m; i++) {
cin >> u >> v;
g[u][v] = 1;
}
cin >> s;
dfs(s);
for(int i = 1; i <= n; i++)
if(vst[i])
ans++;
cout << ans << endl;
return 0;
}
二、邻接表
- 对于一个顶点v,把所有和它相邻的顶点接在它后面,形成一个以它为头结点的链表,故有n个顶点,就有n个链表
- 对于每一个顶点的链表,一般使用STL中的vector记录
题目同上
#include <iostream>
#include <vector>
#include <queue>
#define MAXN 205
using namespace std;
bool vst[MAXN];
vector<int> g[MAXN];
int n, m, s, ans = 1;
void bfs(int x) {
queue<int> q;
q.push(x);
vst[x] = 1;
while(!q.empty()) {
int t = q.front();
q.pop();
int len = g[t].size(), u;
for(int i = 0; i < len; i++) {
u = g[t][i];
if(!vst[u]) {
vst[u] = 1;
q.push(u);
ans++;
}
}
}
}
int main () {
ios::sync_with_stdio(0);
int u, v, s;
cin >> n >> m;
for(int i = 1; i <= m; i++) {
cin >> u >> v;
g[u].push_back(v);
}
cin >> s;
bfs(s);
cout << ans << endl;
return 0;
}
三、链式前向星
- 记录了与每一个顶点相连的边的信息,方便快捷
/*
链式前向星存图
*/
struct Edge {
//其中to表示当前边指向的终点,w表示该边的权值,next表示与该条边同起点的下一条边的存储位置
int to, next, w;
}edge[MAXN];
// 其中head[i]表示的是以i为起点的,最后输出进head的那条边的编号(也被叫做第一条) 一般初始化-1
int head[MAXN];
// 其中cnt表示当前这条边的编号,因为这条边的起点是u所以head[u] = cnt,更新连接顶点的编号
inline void add(int u, int v, int cost) {
edge[++cnt].to = v;
edge[cnt].w = cost;
edge[cnt].next = head[u];
head[u] = cnt;
}
for(int i = head[s]; i != -1; i = edge[i].next[i]) //链式前向星搜边
考虑这样一组数据,每一行两个数据分别表示这两个顶点相连接(为了简便,暂不考虑权值)
1 2
2 3
3 4
1 3
4 1
1 5
4 5
如果使用链式前向星存储,那么会有这样的结果:
edge[1].to=2,  edge[1].next=head[1]=−1,  head[1]=1edge[1] .to = 2,\,\,edge[1].next=head[1]=-1,\,\,head[1]=1edge[1].to=2,edge[1].next=head[1]=−1,head[1]=1
edge[2].to=3,  edge[2].next=head[2]=−1,  head[2]=2edge[2] .to = 3,\,\,edge[2].next=head[2]=-1,\,\,head[2]=2edge[2].to=3,edge[2].next=head[2]=−1,head[2]=2
edge[3].to=4,  edge[3].next=head[3]=−1,  head[3]=3edge[3] .to = 4,\,\,edge[3].next=head[3]=-1,\,\,head[3]=3edge[3].to=4,edge[3].next=head[3]=−1,head[3]=3
edge[4].to=3,  edge[4].next=head[1]=1,  head[1]=4edge[4] .to = 3,\,\,edge[4].next=head[1]= 1,\,\,head[1]=4edge[4].to=3,edge[4].next=head[1]=1,head[1]=4
edge[5].to=1,  edge[5].next=head[4]=−1,  head[4]=5edge[5] .to = 1,\,\,edge[5].next=head[4]=-1,\,\,head[4]=5edge[5].to=1,edge[5].next=head[4]=−1,head[4]=5
edge[6].to=5,  edge[6].next=head[1]=4,  head[1]=6edge[6] .to = 5,\,\,edge[6].next=head[1]=4,\,\,head[1]=6edge[6].to=5,edge[6].next=head[1]=4,head[1]=6
edge[7].to=5,  edge[7].next=head[4]=5,  head[i]=7edge[7] .to = 5,\,\,edge[7].next=head[4]=5,\,\,head[i]=7edge[7].to=5,edge[7].next=head[4]=5,head[i]=7
存储完毕后,其中head数组被保存如下
head[1]=6head[1]=6head[1]=6
head[2]=2head[2]=2head[2]=2
head[3]=3head[3]=3head[3]=3
head[4]=7head[4]=7head[4]=7
- 其中head[i]head[i]head[i]表示的是以i为起点的边的编号(边的编号就是在顺序输入边的时候产生的,就是cntcntcnt,第一行的边,编号就是111,第二行就是222,依次往后)
- 那么我们可以试着找一下看下是否正确,对于head[1]head[1]head[1],表示的是与起点111相连的最后存进去的一条边,head[1]head[1]head[1]的值为666,因此与起点1连接的是第666条边,可以看到输出的时候第666行为1    51 \,\,\,\,515,这条边的信息被存在edge[6]edge[6]edge[6]中,然后我们继续往后找,edge[6].next=4edge[6].next = 4edge[6].next=4,说明下一次要访问的是第4条边,连接的是1    31 \,\,\,\,313顶点,不断继续找,这样就可以快速找到顶点1为起点的所有边。同理其他顶点也是如此(可以自行画图理解)
- 因此,对于建图的过程,每一次添加一条新的边的时候,关键在于把相同起点的这条边的上一条边记录在head数组中,同时对于新来的这条边,更新head数组(因为每一次添加边的时候都包含了当前顶点的信息,所以要不断更新head数组,才能倒着回去找到每一条边)