图论〔Graph Theory〕是数学的一个分支。它以图为研究对象。图论中的图是由若干给定的点及连接两点的线所构成的图形,这种图形通常用来描述某些事物之间的某种特定关系,用点代表事物,用连接两点的线表示相应两个事物间具有这种关系。

Concept of Graph

Storage and Traversal of Graphs

所谓遍历(Traversal),是指沿着某条搜索路线,依次对图中每个顶点均做一次访问。

邻接矩阵

使用一个二维数组 a[i][j] 为0或1表示i -> j这条边是否存在。
如果是赋权图,a[i][j]可以直接存储i -> j的边权。

邻接矩阵在稀疏图上效率很低(尤其是在点数较多的图上,空间无法承受),所以一般只会在稠密图上使用邻接矩阵

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
#include <bits/stdc++.h>
using namespace std;
const int N = 1e5+5, M = 2e5+5;

int n, m, u, v, c, a[N][N];

int main()
{
//无向图赋权
cin>>n>>m;
memset(a, 0, sizeof(a));
for(;m;--m)
{
cin>>u>>v>>c;
a[u][v] = a[v][u] = c;
}
//邻接矩阵遍历 O(n^2)
for(int u = 1; u<=n; ++u)
{
for(int v = 1; v<=n; ++v)
{
printf("%d->%d : %d\n", u, v, a[u][v]);
}
}
}

邻接表

使用一个支持动态增加元素的数据结构构成的数组,如 vector a[n + 1] 来存边,其中 a[u] 存储的是点u的所有出边的相关信息(终点、边权等)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
//无向赋权图
int n,m,u,v,c;
struct Edge
{
int v,c;
Edge(int _v,int _c){v=_v,c=_c;} //结构体函数
};
vector<Edge> a[N];

int main()
{
cin>>n>>m;
memset(a,0,sizeof(a));
for(;m;--m)
{
cin>>u>>v>>c;
a[u].push_back(Edge(v,c)); //引用结构体函数
a[v].push_back(Edge(u,c));
}
}

//遍历O(n+m)
//(写法1)
for(int i=1;i<=n;i++) //起点
{
for(auto [v,w]:a[i]) //遍历vector里面函数的所有变量,把a[i]中的变量取出来,赋给v,w
{
cout<<i<<>"->"<<v<<" "<<w<<endl;
}
}

//(写法2)
for(int u=1;u<=n;++u)
{
for(int i=0;i<a[u].size();++i)
{
int v=a[u][i].v,c=a[u][i].c;
printf("%d->%d : %d\n",u,v,c);
}
}
// for(auto x:b[i]) 可以遍历一个vector<node> b
// x -> [v,c] == int v=x.v , c=x.c;

//(写法3)深度优先搜索
void dfs(int u)
{
vis[u] = 1;
printf("%d visited!\n", u);
for(int i = 0; i<a[u].size(); ++i)
{
int v = a[u][i].v, c = a[u][i].c;
if(vis[v])continue;
dfs(v);
}
}

//(写法4)广度优先搜索
void bfs(int st)
{
queue<int> Q;
Q.push(st);
memset(vis, 0, sizeof(vis));
while(!Q.empty())
{
int u = Q.front();
vis[st]=1;
printf("%d visited!\n", u);
Q.pop();
for(int i = 0; i<a[u].size(); ++i)
{
int v = a[u][i].v, c = a[u][i].c;
if(vis[v])continue;
Q.push(v);vis[v] = 1;
}
}
}

板子

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
//邻接表无向图的赋权与遍历
#include<bits/stdc++.h>
using namespace std;

struct node
{
int v,c;
};
vector<node> a[10];

void add(int u,int v,int c)
{
node temp;
temp.v=v;
temp.c=c;
a[u].push_back(temp);
}

void solve()
{
int n=10;

for(int i=1;i<=5;i++)
{
int u,v,c;
cin>>u>>v>>c;
add(u,v,c);
add(v,u,c);
}
for(int i=1;i<=n;i++)
{
for(auto[v,c]:a[i]) //利用auto从结构体中取出v和c
{
cout<<i<<"->"<<v<<" "<<c<<endl;
}
}
}

signed main()
{
ios::sync_with_stdio(0);
cin.tie(0);
cout.tie(0);
return 0;
}

链式前向星

可以理解为链表实现的邻接表。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
int n,m,u,v,c,cnt,head[N];
struct node
{
int nxt,to,cost;
node(){}
node(int x,int v,int c){nxt=x,to=v,cost=c;}
}e[M];
void Add_Edge(int u,int v,int c)
{
e[cnt]=node(head[u],v,c);
head[u]=cnt++;
}

int main()
{
cin>>n>>m;
memset(head,-1,sizeof(head));
for(;m;--m) // Storage
{
cin>>u>>v>>c;
Add_Edge(u,v,c);
Add_Edge(v,u,c);
}
for(int u=1;u<=n;++u) //Traversal
{
for(int i=head[u];~i;i=e[i].nxt)
{
int v=e[i].to,c=e[i].cost;
printf("%d->%d : %d\n",u,v,c);
}
}
return 0;
}

DFS遍历

1
2
3
4
5
6
7
8
9
10
11
12
//链式前向星
void dfs(int u)
{
vis[u]=1;
printf("%d visited!\n",u);
for(int i=head[u];~i;i=e[i].nxt)
{
int v=e[i].to,c=e[i].cost;
if(vis[v]) continue;
dfs(v);
}
}

BFS遍历

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//链式前向星
void bfs(int st)
{
queue<int> Q;
Q.push(st);
memset(vis,0,sizeof(vis));
while(!Q.empty())
{
int u=Q.front();
vis[u]=1;
printf("%d visited!\n",u);
Q.pop();
for(int i=head[u];~i;i=e[i].nxt)
{
int v=e[i].to,c=e[i].cost;
if(vis[v]) continue;
Q.push(v);
vis[v]=1;
}
}
}

Thanks to SZUACM