- 这是《算法笔记》的读书记录
- 本文参考自10.3节
〇、图的基本概念
-
定义:图由顶点(vertex)和边(edge)组成。每条边的两段都必须是两个顶点(可以是同一个点)。记顶点集合为
V
,边集合为E
,可以用G(V,E)
记录一个图- 有向图:所有边都有方向,只能沿着边的方向在图中移动
- 无向图:所有边都没有方向(或者说是双向的),可以把无向图的每条边看作由正向和负向的两条有向边组成
-
图的数据结构表示
- 邻接表:好写,但是空间复杂度高,适用于定点数小于1000的题目
- 邻接矩阵:写起来比较麻烦,但是空间复杂度低,一般用
vector
数组实现
-
图的相关概念
- 顶点的度:度指和此顶点连接的边的条数,对于有向图来说,顶点出边的条数称为此顶点的出度;顶点入边的条数称为此顶点的入度
- 权值:顶点和边都可以有一定的属性,量化的属性称为权值。边和点分别有边权和点权,具体根据题目背景设定
- 连通分量:无向图中,如果两个顶点间相互可达(可以是通过一定的路径到达),就称这两个顶点连通。若图
G(V,E)
的任意两个顶点都连通,则称G为连通图;否则称G为非连通图,且其中的极大连通子图为连通分量 - 强连通分量:有向图中,如果两个顶点间相互可达(各自通过一条有向路径),就称这两个顶点强连通。若图
G(V,E)
的任意两个顶点都强连通,则称G为强连通图;否则称G为非强连通图,且其中的极大强连通子图为强连通分量 - 连通块:为了表示简便,以下把所有连通分量和强连通分量统称为连通块
-
关于图的遍历:
- 遍历图,等价于遍历图中的所有连通块
- 从图中的任意结点开始BFS或DFS遍历,可以访问到其所能到达的所有点
- 对于连通图和强连通图,因为整个图就是一个连通块,所以BFS或DFS一遍即可完成遍历
- 对于非连通图和非强连通图,通常的做法是设置一访问标记数组,遍历图的点集,从没有做过访问标记的点开始BFS或DFS搜索即可,搜索过程中不断给访问过的点打上标记。
一、DFS遍历图
-
关于DFS算法:【算法笔记】8.1 深度优先搜索DFS
-
DFS遍历图:以 “深度” 作为第一关键词,每次都是沿着路径走到不能前进时才回退到最近的岔路口。
-
示例:下图是一个非强连通图,V0入度为0,如果从其他点开始DFS,至少需要DFS两遍(如从V2开始),而从V0开始则能访问到全部顶点。从V0开始DFS,遍历顺序为:
v0->v1->v3->v4->v5->v2
-
实例代码(邻接表表示,V0开始遍历)
/* DFS遍历有向图 */ #include<iostream> #include<vector> using namespace std; const int MAXN = 1000; //最大顶点数 bool vis[MAXN] = { false}; //访问标记数组 typedef struct NODE{ int v; //连接到的端点 int w; //边权,这里其实没用 NODE(int _v,int _w):v(_v),w(_w){ } }NODE; vector<NODE> Adj[MAXN]; //邻接表 void DFS(int u,int depth) { vis[u] = true; cout<<"visit:"<<u<<" depth:"<<depth<<endl; for(int i=0;i<Adj[u].size();i++) { int v = Adj[u][i].v; if(!vis[v]) DFS(v,depth+1); } } int main() { int N,M; //顶点数、边数 cin>>N>>M; int start,end; //有向边的起点和终点 for(int i=0;i<M;i++) { cin>>start>>end; Adj[start].push_back(NODE(end,0)); } DFS(0,0); //DFS遍历0号点所在的连通块,初始深度为0 /* //DFS遍历所有连通块 for(int i=0;i<N;i++) { if(!vis[i]) DFS(i,0); } */ return 0; } /* 5 9 0 1 0 2 1 3 1 4 2 1 2 4 4 3 4 5 5 3 */
-
输出如下,可见是以先走到最深,遇到阻碍再回头的方式遍历的
扫描二维码关注公众号,回复: 12423727 查看本文章visit:0 depth:0 visit:1 depth:1 visit:3 depth:2 visit:4 depth:2 visit:5 depth:3 visit:2 depth:1
二、BFS遍历图
-
关于BFS算法:【算法笔记】8.2 广度优先搜索BFS
-
BFS遍历图:以 “广度” 作为第一关键词,每次以扩展的方式向外访问顶点。和BFS一般写法一样,这里要用一个队列,通过反复去除队首顶点,使此顶点可达的未加入过队列的顶点全部入队,直到队列为空时遍历结束
-
示例:下图是一个非强连通图,V0入度为0,如果从其他点开始BFS,至少需要BFS两遍(如从V2开始),而从V0开始则能访问到全部顶点。从V0开始BFS,遍历顺序为:
v0->v1->v2->v3->v4->v5
-
示例代码(邻接表表示,V0开始遍历)
#include <iostream> #include <queue> using namespace std; const int MAXN = 1000; bool inqueue[MAXN] = { false}; //标记节点是否已经入过队 typedef struct NODE { int v; //连接到的端点 int w; //权值,这里其实没用 int layer; //顶点层次 NODE(int _v,int _w,int _layer):v(_v),w(_w),layer(_layer){ } }NODE; vector<NODE> Adj[MAXN]; //以s为起点,遍历s所在的连通块 void BFS(int s) { queue<NODE> q; NODE start = NODE(s,0,0); q.push(start); inqueue[s] = true; while(!q.empty()) { NODE top = q.front(); q.pop(); cout<<"visit:"<<top.v<<" depth:"<<top.layer<<endl; int u = top.v; for(int i=0;i<Adj[u].size();i++) { NODE next = Adj[u][i]; next.layer = top.layer+1; if(!inqueue[next.v]) { q.push(next); inqueue[next.v] = true; } } } } int main() { int N,M; //顶点数、边数 cin>>N>>M; int start,end; for(int i=0;i<M;i++) { cin>>start>>end; Adj[start].push_back(NODE(end,0,0)); } BFS(0); //BFS遍历0号点所在的联通块 return 0; } /* 5 9 0 1 0 2 1 3 1 4 2 1 2 4 4 3 4 5 5 3 */
-
输出,可见是按深度层次一路下遍历的
visit:0 depth:0 visit:1 depth:1 visit:2 depth:1 visit:3 depth:2 visit:4 depth:2 visit:5 depth:3