注意:本章主要讲解图的存储图的遍历

1. 图的定义、构成和术语

图(Graph),我们可以把它定义成一个二元组 es下线节点及添加新节点 es ingest节点_子图。其中 es下线节点及添加新节点 es ingest节点_算法_02 代表 es下线节点及添加新节点 es ingest节点_子图_03,即顶集,顶集中的元素称为顶点(Vertex),写作 es下线节点及添加新节点 es ingest节点_子图_04es下线节点及添加新节点 es ingest节点_有向图_05 代表 es下线节点及添加新节点 es ingest节点_数据结构_06,即边集,边集中的元素称为边(Edge),写作 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_07es下线节点及添加新节点 es ingest节点_有向图_08 无交集es下线节点及添加新节点 es ingest节点_有向图_05 中的元素也是二元组,为 es下线节点及添加新节点 es ingest节点_数据结构_10,且 es下线节点及添加新节点 es ingest节点_算法_11

图还有一种三元组的定义,这里不再赘述,感兴趣的读者可以自行查阅。

以上定义可能较为形式化,较难理解。读者一会可以通过下面的例子来理解。

图还分为有向图无向图——也就是说,有向图的边有方向,即有向边,无向图的边无方向,即无向边。特殊地,如果一个图的边带有权值,则称为带权图

以下分别展示了有向图、无向图、无向带权图。同时我会用形式语言、通俗语言和举例来帮助大家更好理解。

es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_12


es下线节点及添加新节点 es ingest节点_算法_13


es下线节点及添加新节点 es ingest节点_数据结构_14

  • es下线节点及添加新节点 es ingest节点_算法_15点集 es下线节点及添加新节点 es ingest节点_数据结构_16,称作图 es下线节点及添加新节点 es ingest节点_算法_15阶(Order)
    一个图中顶点的个数,就是一个图的阶。
    es下线节点及添加新节点 es ingest节点_数据结构_18 的阶都是 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_19
  • 当存在图 es下线节点及添加新节点 es ingest节点_子图_20其中 es下线节点及添加新节点 es ingest节点_数据结构_21,则 es下线节点及添加新节点 es ingest节点_算法_22 称作图 es下线节点及添加新节点 es ingest节点_子图子图(Sub-Graph)
    从一个图中“抠”下来的图(即两个图具有包含关系),则被包含的图是另一个图的子图。
    es下线节点及添加新节点 es ingest节点_数据结构_18 分别都是各自的子图。
  • 当存在图 es下线节点及添加新节点 es ingest节点_算法_22 是图 es下线节点及添加新节点 es ingest节点_算法_15子图,且 es下线节点及添加新节点 es ingest节点_算法_27,则称为图 es下线节点及添加新节点 es ingest节点_算法_22 是图 es下线节点及添加新节点 es ingest节点_算法_15生成子图(Spanning Sub-Graph)
    一个图是另一个图的子图,且这两个图顶点数相同,则这个子图是另一个图的生成子图。
    es下线节点及添加新节点 es ingest节点_有向图_30 中,若去掉了 es下线节点及添加新节点 es ingest节点_有向图_31,则所产生的图就是原图的生成子图。
  • 与一个顶点 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_32 相关联的边的条数,称为 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_32度(Degree),记作 es下线节点及添加新节点 es ingest节点_数据结构_34
    在一个图中的一个顶点,和它相连的边的条数,就是它的度。
    es下线节点及添加新节点 es ingest节点_算法_35 中,es下线节点及添加新节点 es ingest节点_子图_36 的度为 es下线节点及添加新节点 es ingest节点_子图_37es下线节点及添加新节点 es ingest节点_算法_35 的度为 es下线节点及添加新节点 es ingest节点_算法_35es下线节点及添加新节点 es ingest节点_有向图_30 的度为 es下线节点及添加新节点 es ingest节点_算法_41
  • 对于有向图,与一个顶点 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_32 相关联的边中,以 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_32终点的边的个数,称为 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_32入度(In-Degree),这些边称为 入边(In-Edge);与一个顶点 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_32 相关联的边中,以 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_32起点的边的个数,称为 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_32出度(Out-Degree),这些边称为出边(Out-Edge)
    有向图中的一个顶点,所有指向它的边的个数,就是它的入度,这些边是入边;除了这些边以外和它相连的边的条数,就是它的出度,这些边是出边。
    es下线节点及添加新节点 es ingest节点_有向图_30 中,es下线节点及添加新节点 es ingest节点_子图_36 的入度为 es下线节点及添加新节点 es ingest节点_算法_41,出度为 es下线节点及添加新节点 es ingest节点_有向图_30es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_19 的入度为 es下线节点及添加新节点 es ingest节点_算法_41,出度为 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_54
  • 若一条边的两个顶点相同,则这条边是一个自环(Loop)(并查集的初始化其实就是自环)。
  • 图中的一条闭合的路径,称为环(Circuit)

以上是一些基本的概念,其他大多数概念可以通过逐步的学习理解。
另外注意:树是一种特殊的图。

2. 图的存储

和 「数据结构详解·一」树的初步 的第二部分相同。
无向图连接 es下线节点及添加新节点 es ingest节点_算法_55,就是 g[u][v]=g[v][u]=1;;有向图就是 g[u][v]=1;
但是这里再补充一部分内容。

2-1. 边表

不常用。主要在 Kruskal(一种最小生成树算法,将在以后讲到)等算法中用到。
直接用结构体存,es下线节点及添加新节点 es ingest节点_算法_56。对于无向图,表示 es下线节点及添加新节点 es ingest节点_子图_57;对于有向图,表示 es下线节点及添加新节点 es ingest节点_数据结构_58

struct node{
	int u,v;
}g[100005];

int main()
{
	int n,m;
	cin>>n>>m;
	for(int i=1;i<=m;i++)
	{
		cin>>a[i].u>>a[i].v;
	}
	//...
}

对于寻找与结点 es下线节点及添加新节点 es ingest节点_子图_59 相连的边,需要 es下线节点及添加新节点 es ingest节点_子图_60

2-2. 带权图的存储

邻接矩阵只要将 g[u][v]=1; 变为 g[u][v]=w; 即可,但是缺点是无法存重边(比如,输入中给出了 u=1 v=2 w=3u=1 v=2 w=-2,意味着 es下线节点及添加新节点 es ingest节点_算法_61 间有两条边)。另外,矩阵初始化要变为 es下线节点及添加新节点 es ingest节点_子图_62es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_63

邻接表只要用 pair 或结构体来代替 vector 中的内容(对于 es下线节点及添加新节点 es ingest节点_算法_64es下线节点及添加新节点 es ingest节点_有向图_65

边表和邻接表类似,修改结构体的内容即可。

3. 图的遍历

所有代码均为邻接表存储。

3-1. 深度优先遍历(DFS)

和 「数据结构详解·一」树的初步 es下线节点及添加新节点 es ingest节点_es下线节点及添加新节点_66 类似,但是由于图的特性,我们要记录 es下线节点及添加新节点 es ingest节点_算法_67

void dfs(int p)//p 为当前节点编号
{
	if(f[p]) return;//走过了
	cout<<p<<' ';
	f[p]=1;
	for(auto i:g[p])
	{
		dfs(i);
	}
}

3-2. 广度优先遍历(BFS)

和 「数据结构详解·一」树的初步 es下线节点及添加新节点 es ingest节点_数据结构_68 类似,但是由于图的特性,我们要记录 es下线节点及添加新节点 es ingest节点_算法_67

queue<int>q;
void bfs()
{
	q.push(root);//root 为遍历的起始节点
	while(!q.empty())
	{
		int x=q.front();
		q.pop();
		if(f[x]) continue;
		f[x]=1;
		cout<<x<<' ';
		for(auto i:g[x])
		{
			q.push(i);
		}
	}
}

4. 例题详解

4-1. Luogu P5318 【深基18.例3】查找文献

只要将本文章的 es下线节点及添加新节点 es ingest节点_算法_70

4-2. Luogu P3916 图的遍历

如果我们直接暴力对于每个点查找,那是必定超时的。
题目问的是每个点所到达编号最大的点,那我们可以先反向建图,然后编号从大到小搜索,第一次搜索到的点就是答案(因为第二次搜到时编号因为是越来越小,因此不会比之前大)。
那这样的话,每个点只会遍历的到一次,时间复杂度由 es下线节点及添加新节点 es ingest节点_有向图_71 变为了 es下线节点及添加新节点 es ingest节点_算法_72
参考代码:

#include<bits/stdc++.h>
using namespace std;

vector<int>g[100005];
int n,m,ans[100005];

void dfs(int fr,int p)
{
	if(ans[p]) return;//搜过了
	ans[p]=fr;
	for(auto i:g[p])
	{
		dfs(fr,i);
	}
}

int main()
{
	cin>>n>>m;
	while(m--)
	{
		int u,v;
		cin>>u>>v;
		g[v].push_back(u);//反向建图
	}
	for(int i=n;i>=1;i--)
	{
		dfs(i,i);
	}
	for(int i=1;i<=n;i++)
	{
		cout<<ans[i]<<' ';
	}
 	return 0;
}