图结构与基本搜索
本章介绍图的基本概念、存储方式以及两种基本图搜索算法——深度优先搜索(DFS)和广度优先搜索(BFS),涵盖图的定义、分类、存储结构及其在代码中的实现,并提供DFS和BFS的典型模板代码。
图
由顶点(节点)和边组成的一种非线性数据结构,用于表示对象之间的关系,顶点代表对象,边代表对象之间的连接或关系。
有向图与无向图
图的稀疏与稠密
概念大全:
图:由顶点(V)与连接顶点的边(E)构成
无向图:每条边没有方向的定义
有向图:每条边有方向,例如表达路的话,一条边就表达一条单向车道
完全图:任意两个顶点之间都有边(对于有向图则两个方向的边都有)
稀疏图:边较少的图
稠密图:边较多的图
顶点的度:与该点关联的边的个数,如果是有向图则分“出度”和“入度”
路径:连续的边构成的顶点序列
路径长度:路径的边长度之和(如果没长度可以是边数量之和)
回路(环):第一个顶点和最后一个顶点相同的路径
简单路径:除起点和终点外,其余顶点皆不相同
简单回路:起点终点相同,其他顶点皆不相同
连通图:图中任意两个顶点之间都能找到一个路径
子图:如果图B的顶点和边都是图A的顶点和边的子集,则B是A的子图
连通分量(强连通分量):图B是图A的子图且图B是连通图(即A的连通子图),A中任意不在B中的顶点加入B后,B不再连通,这样的B是A的连通分量
极小连通子图:图B是图A的连通子图,如果B任意删除一条边后都不再连通,则B是A的极小连通子图
生成树:包含图 G 的所有顶点的极小连通子图,是图G的生成树
生成森林:非连通图的各个连通分量的生成树的集合
最小生成树:边有边权(或长度或其他量),边权之和最小的生成树
图的存储方式
在代码中存这个图:
顺序存储(邻接矩阵)
用一个二维数组存储点与点的关系
n
个顶点, e
条边
对于无边权的图,g[i][j] == 1
表示顶点 i
与顶点 j
有边,否则无边
对于有边权的图,可以额外用 w[i][j]
表示边的权值
无向图则 g[i][j] == g[j][i]
适合稠密图
链式存储,竞赛通常用链式前向星
n
个顶点,e
条边
每一个顶点, 都用一个链表表示与其相连的边
适合稀疏图
对于无向图,则两个方向各建一条有向边
知识点:在面对一道图问题时,一定要先分析图的稠密程度,再决定用邻接矩阵还是链式前向星。
Tip:大多问题都可以用链式前向星,在一些显著稠密、卡常数的问题中,考虑用邻接矩阵,邻接矩阵代码也相对好写一些。
链式前向星模板参考
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 const int maxN = 1100 ;const int maxM = 110000 ;int first[maxN]; int nex[maxM]; int u[maxM]; int v[maxM]; int w[maxM]; int tp; void AddEdge (int s, int e, int weight) { nex[tp] = first[s]; first[s] = tp; u[tp] = s; v[tp] = e; w[tp] = weight; tp ++; } void DbEdge (int s, int e, int weight) { AddEdge (s, e, weight); AddEdge (e, s, weight); } bool vis[maxN] = {0 };void DFS (int now) { for (int i = first[now]; i != -1 ; i = nex[i]) { if (!vis[v[i]]) { vis[v[i]] = true ; DFS (v[i]); } } }
棋盘类图问题
迷宫、下棋等问题,节点即格点或格子,与其相关的是上下左右四个方向,或者加上斜向共8个方向,用邻接矩阵或链式前向星建图就太麻烦了,可以直接用xy
坐标来表达节点,增减xy
来找相邻节点。
基本的搜索
深度优先搜索(Deep First
Search, DFS)
深度优先搜索,用栈(递归的形式)
一条路走到底,往回一步,再一条路走到底,往回一步...一般用于求全部解、求一些很“深”的解
访问起始点v;
若v的第1个邻接点没访问过,深度遍历此邻接点;
若当前邻接点已访问过,再找v的第2个邻接点重新遍历;
如果所有邻接点都已访问,则返回上一个访问的顶点。
在网格上DFS典型模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 const int maxn = 110 ;int dx[4 ] = {-1 , 1 , 0 , 0 };int dy[4 ] = {0 , 0 , -1 , 1 };int graph[maxn][maxn];bool visited[maxn][maxn];int endX, endY;int DFS (int x, int y) { if (x == endX && y == endY) return true ; for (int i = 0 ; i < 4 ; i++) { int nextX = x + dx[i], nextY = y + dy[i]; if (graph[nextX][nextY] && !visited[nextX][nextY]) { visited[nextX][nextY] = true ; if (DFS (nextX, nextY)) return true ; } } return false ; }
在图上DFS典型模板
1 2 3 4 5 6 7 8 9 10 11 12 13 const int maxn;int g[maxn][maxn]; bool vis[maxn];void DFS (int now, int n) { if (vis[now]) return ; vis[now] = true ; for (int i = 0 ; i < n; i++) { if (g[now][i]) DFS (i, n); } }
广度优先搜索(Breath First
Search, BFS)
广度优先搜索是一种分层的搜索过程,每向前走一步可能访问一批顶点,不像深度优先搜索那样有回退的情况,因此,广度优先搜索不是一个递归的过程,其算法也不是递归的,就近访问,一圈圈外扩一般用于求最短的路、最近的解。
在访问了起始点v之后,将 v 的尚未访问的邻接点放入访问队列;
在队列中出队尚未访问的顶点,进行访问
直到所有顶点都被访问
在网格上BFS典型模板
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 struct Node { int x, y; Node () { x = y = 0 ; } Node (int x_, int y_) { x = x_; y = y_; } }; int BFS (int startX, int startY) { std::queue<Node> q; q.push (Node (startX, startY)); visited[startX][startY] = true ; while (!q.empty ()) { Node now = q.front (); q.pop (); if (now.x == endX && now.y == endY) return true ; for (int i = 0 ; i < 4 ; i++) { int nextX = now.x + dx[i], nextY = now.y + dy[i]; if (graph[nextX][nextY] && !visited[nextX][nextY]) { visited[nextX][nextY] = true ; q.push (Node (nextX, nextY)); } } } return false ; }
在图上BFS典型模板
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 const int maxn;int g[maxn][maxn]; bool vis[maxn];void BFS (int start, int n) { queue<int > q; q.push (start); vis[start] = true ; while (!q.empty ()) { int now = q.front (); q.pop (); for (int i = 0 ; i < n; i++) { if (g[now][i] && !vis[i]) { vis[i] = true ; q.push (i); } } } }