数据结构-图图的常用算法(代码片段)

Mount256 Mount256     2022-10-22     396

关键词:

手工模拟图的各大常用算法。

文章目录

1 图的遍历算法

1.1 BFS 算法(广度优先遍历)

【性质】

  • 空间复杂度:O(|V|)(需要一个辅助队列)
  • 采用邻接表的时间复杂度:O(|V|+|E|)
  • 采用邻接矩阵的时间复杂度:O(|V|2)
  • 遍历方法与树的层序遍历相同(不再给出手工模拟算法的过程)
  • 非递归算法
  • 用邻接矩阵存储的图,其广度优先生成树唯一;用邻接表存储的图,其广度优先生成树不唯一

【核心思想】

  • 首先访问起始顶点 v
  • 由 v 出发,依次访问 v 的各个未访问的邻接顶点 w1,w2,…,wi(将它们依次存入辅助队列)
  • 由这些顶点 w1,w2,…,wi 出发(将它们依次出列),再依次访问它们的未访问的邻接顶点

【核心代码】

bool visited[MAX_VERTEX_NUM];	// 标记顶点的访问情况 
Queue Q;						// 辅助队列 

void BFSTraverse (Graph G)
	for (v = 0; v < G.vexnum; v++)
		visited[v] = FALSE;		// 初始化各顶点的访问情况为未访问 
	
	InitQueue(Q); 				// 初始化辅助数列 Q 
	
// 考虑到非连通图,为防止一次调用 BFS 函数访问不到其他连通分量,检查一遍 visited 即可继续访问未访问的顶点 
// 考虑到非强连通图,有些顶点也是访问不到的,因此检查一遍 visited 即可继续访问未访问的顶点 
	for (v = 0; v < G.vexnum; v++)		
		if (visited[v] == FALSE)
			BFS(G, v);
	


void BFS (Graph G, int v)
	visit(v);			// 访问顶点 
	visited[v] = TRUE;	// 设置该顶点为已访问过
	EnQueue(Q, v);		// 顶点 v 入队 
	
	while (!isEmpty(Q))// 队列非空时 
		DeQueue(Q, v);		// 顶点 v 出队
		// 依次访问 v 的邻接点
		// FirstNeighbor(G,v):求图 G 中顶点 v 的第一个邻接点
		// NextNeighbor(G,v,w):图 G 中顶点 w 是顶点 v 的一个邻接点,返回除 w 外顶点 v 的下一个邻接点 
		for (int w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w))
			if (visited[w] == FALSE)	// 若顶点 w 尚未访问 
				visit(w);			// 访问顶点
				visited[w] = TRUE;	// 设置该顶点为已访问过
				EnQueue(Q, w);		// 顶点 w 入队 
			
		
	

1.2 DFS 算法(深度优先遍历)

【性质】

  • 空间复杂度:O(|V|)(需要一个辅助递归工作栈)
  • 采用邻接表的时间复杂度:O(|V|+|E|)
  • 采用邻接矩阵的时间复杂度:O(|V|2)
  • 遍历方法与树的先序遍历相同(不再给出手工模拟算法的过程)
  • 递归算法
  • 用邻接矩阵存储的图,其深度优先生成树唯一;用邻接表存储的图,其深度优先生成树不唯一

【核心思想】

  • 访问起始顶点 v
  • 由顶点 v 出发,访问与其邻接的未访问的第一个顶点 w1
  • 再访问与 w1 邻接的未访问顶点 w11
  • 不断重复以上过程,直到不能再向下访问时,回退到最近被访问的顶点,继续访问它的其它邻接顶点

【核心代码】

bool visited[MAX_VERTEX_NUM];	// 标记顶点的访问情况 

void DFSTraverse (Graph G)
	for (v = 0; v < G.vexnum; v++)
		visited[v] = FALSE;		// 初始化各顶点的访问情况为未访问 
	
// 考虑到非连通图,为防止一次调用 DFS 函数访问不到其他连通分量,检查一遍 visited 即可继续访问未访问的顶点 
// 考虑到非强连通图,有些顶点也是访问不到的,因此检查一遍 visited 即可继续访问未访问的顶点 
	for (v = 0; v < G.vexnum; v++)		
		if (visited[v] == FALSE)
			DFS(G, v);
	


void DFS (Graph G, int v)
	visit(v);			// 访问结点 
	visited[v] = TRUE;	// 设置该结点为已访问过 
	// 依次访问 v 的邻接点
	// FirstNeighbor(G,v):求图 G 中顶点 v 的第一个邻接点
	// NextNeighbor(G,v,w):图 G 中顶点 w 是顶点 v 的一个邻接点,返回除 w 外顶点 v 的下一个邻接点 
	for (int w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w))
		if (visited[w] == FALSE)	// 若顶点 w 尚未访问 
			DFS(G, w);
	 

2 最短路径问题

2.1 BFS 算法(求无权图的单源最短路径)

【核心思想】

  • 注意到可以将无权图转换为根为顶点 i 的生成树。又因为广度优先生成树的高度一定小于等于深度优先生成树,所以对于广度优先生成树来说,它的根到其他顶点的距离一定是最短的。

【核心代码】

  • 新增加两个数组:dist 和 path
bool visited[MAX_VERTEX_NUM];	// 标记顶点的访问情况 
int dist[MAX_VERTEX_NUM];		// 记录从源点(V0)到该点(Vi)的最短路径长度
int path[MAX_VERTEX_NUM];		// 路径上的前驱(存储到达 Vi 的前一个结点编号)
Queue Q;						// 辅助队列 

void BFSTraverse (Graph G)
	for (v = 0; v < G.vexnum; v++)
		visited[v] = FALSE;		// 初始化各顶点的访问情况为未访问 
		dist[v] = 999999;		// 初始化路径长度 
		path[v] = -1;			// 初始化路径上的顶点 v 的前驱顶点 
	
	InitQueue(Q); 				// 初始化辅助数列 Q 
	
// 考虑到非连通图,为防止一次调用 BFS 函数访问不到其他连通分量,检查一遍 visited 即可继续访问未访问的顶点 
// 考虑到非强连通图,有些顶点也是访问不到的,因此检查一遍 visited 即可继续访问未访问的顶点 
	for (v = 0; v < G.vexnum; v++)		
		if (visited[v] == FALSE)
			BFS(G, v);
	


void BFS_Min_Dist (Graph G, int v)
	dist[v] = 0;		// 从初始顶点 v 开始遍历,它的最短路径长度设置为 0 
	visited[v] = TRUE;	// 设置该顶点为已访问过
	EnQueue(Q, v);		// 顶点 v 入队 
	
	while (!isEmpty(Q))// 队列非空时 
		DeQueue(Q, v);		// 顶点 v 出队
		// 依次访问 v 的邻接点
		// FirstNeighbor(G,v):求图 G 中顶点 v 的第一个邻接点
		// NextNeighbor(G,v,w):图 G 中顶点 w 是顶点 v 的一个邻接点,返回除 w 外顶点 v 的下一个邻接点 
		for (int w = FirstNeighbor(G, v); w >= 0; w = NextNeighbor(G, v, w))
			if (visited[w] == FALSE)	// 若顶点 w 尚未访问 
				dist[w] = dist[v] + 1;	// 路径长度加 1 
				path[w] = v; 			// 标记到达顶点 w 需要从顶点 v 过来 
				visited[w] = TRUE;		// 设置该顶点为已访问过
				EnQueue(Q, w);			// 顶点 w 入队 
			
		
	

2.2 Dijkstra 算法(求带权图的单源最短路径)

【性质】

  • 时间复杂度:O(|V|2)
  • 基于贪心策略
  • 不适用于带负权值的图
  • 不适用于带负权回路的图

以下面有向图为例:

step0. 初始状态

  • final 数组:标记各顶点是否已找到最短路径
V0V1V2V3V4
TrueFalseFalseFalseFalse
  • dist 数组:记录从源点(V0)到该点(Vi)的最短路径长度
V0V1V2V3V4
0105
  • path 数组:路径上的前驱(存储到达 Vi 的前一个结点编号)
V0V1V2V3V4
-10-1-10

step1. 第一轮

上一步的表格:

V0V1V2V3V4
finalTrueFalseFalseFalseFalse
dist0105
path-10-1-10

【1】找到上一步中:final 数组还未确定的最短路径,且在 dist 数组中最小的顶点 Vi = V4,令 final[i] = True,表示从 V0 到 Vi 的最短路径已确定。

  • final 数组:
V0V1V2V3V4
TrueFalseFalseFalseTrue

【2】检查所有邻接自 Vi = V4 的点,若其 final 值为 False,则对比 step0 中的 dist 信息,如果找到的路径比当前的信息还小,则更新其 dist 和 path 信息。

【2.1】对于 V1(原本 dist = 10, path = 0):final 值为 False,从 V0–>V4–>V1 的路径长度为 5+3=8 < 10,所以需要更新其 dist = 8(表示从 V0 到 V1 的路径长度为 8,以下类似),path = 4(表示这条路径是从 V4 结点过来的,以下类似);

【2.2】对于 V2(原本 dist = ∞, path = -1):final 值为 False,从 V0–>V4–>V2 的路径长度为 5+9=14 < ∞,所以需要更新其 dist = 14,path = 4;

【2.3】对于 V3(原本 dist = ∞, path = -1):final 值为 False,从 V0–>V4–>V3 的路径长度为 5+2=7 < ∞,所以需要更新其 dist = 7,path = 4。

  • dist 数组:
V0V1V2V3V4
081475
  • path 数组:
V0V1V2V3V4
-14440

step2. 第二轮

上一步的表格:

V0V1V2V3V4
finalTrueFalseFalseFalseTrue
dist081475
path-14440

【1】找到上一步中:final 数组还未确定的最短路径,且在 dist 数组中最小的顶点 Vi = V3,令 final[i] = True,表示从 V0 到 Vi 的最短路径已确定。

  • final 数组:
V0V1V2V3V4
TrueFalseFalseTrueTrue

【2】检查所有邻接自 Vi = V3 的点(对应 dist = 7,path = 4),若其 final 值为 False,则对比 step1 中的 dist 信息,如果找到的路径比当前的信息还小,则更新其 dist 和 path 信息。

【2.1】对于 V0:final 值为 True。

【2.2】对于 V2(原本 dist = 14,path = 4):final 值为 False,从 V0–>V4–>V3–>V2 的路径长度为 7+6=13 < 14,所以需要更新其 dist = 13,path = 3。

  • dist 数组:
V0V1V2V3V4
081375
  • path 数组:
V0V1V2V3V4
-14340

step3. 第三轮

上一步的表格:

V0V1V2V3V4
finalTrueFalseFalseTrueTrue
dist081375
path-14340

【1】找到上一步中:final 数组还未确定的最短路径,且在 dist 数组中最小的顶点 Vi = V1,令 final[i] = True,表示从 V0 到 Vi 的最短路径已确定。

  • final 数组:
V0V1V2V3V4
TrueTrueFalseTrueTrue

【2】检查所有邻接自 Vi = V1 的点(对应 dist = 8,path = 4),若其 final 值为 False,则对比 step2 中的 dist 信息,如果找到的路径比当前的信息还小,则更新其 dist 和 path 信息。

【2.1】对于 V2(原本 dist = 13,path = 3):final 值为 False,从 V0–>V4–>V1–>V2 的路径长度为 8+1=9 < 13,所以需要更新其 dist = 9,path = 1。

【2.2】对于 V4:final 值为 True。

  • dist 数组:
V0V1V2V3V4
08975
  • path 数组:
V0V1V2V3V4
-14140

step4. 第四轮

上一步的表格:

V0V1V2V3V4
finalTrueTrueFalseTrueTrue
dist08975
path-14140

【1】找到上一步中:final 数组还未确定的最短路径,且在 dist 数组中最小的顶点 Vi = V2,令 final[i] = True,表示从 V0 到 Vi 的最短路径已确定。

  • final 数组:
V0V1V2V3V4
TrueTrueTrueTrueTrue

【2】检查所有邻接自 Vi = V2 的点(对应 dist = 13,path = 3),若其 final 值为 False,则对比 step2 中的 dist 信息,如果找到的路径比当前的信息还小,则更新其 dist 和 path 信息。

已经找不到其他未访问结点,算法结束,以下为最终结果:

  • dist 数组:
V0V1V2V3V4
08975
  • path 数组:
V0V1V2V3V4
-14140

【应试】快速解答


2.3 Floyd 算法(求带权图的各顶点之间最短路径)

【性质】

  • 时间复杂度:O(|V|3)
  • 适用于带负权值的图
  • 不适用于带负权回路的图

【注】可以使用单源最短路径算法解决每对顶点最短路径问题,例如可使用 Dijkstra 算法,轮流将每个结点当成源点,时间复杂度也为 O(|V|3)

【核心代码】

for (int k = 0; k < n; k++)  // 加入 Vk 作为中转站
  for (int i = 0; i < n; i++)  // 遍历整个邻接权值矩阵,i 为行,j 为列
    for (int j = 0; j < n; j++)
        if (A[i][j] > A[i][k] + A[k][j])  // 从顶点 i 到顶点 j,经过 Vk 中转站的路径更短
            A[i][j] = A[i][k] + A[k][j];   // 更新路径长度
            path[i][j] = k;                // 记录中转顶点的编号
        
    
  

以下面有向图为例:

step0. 初始状态(不允许在其他顶点中转)

  • A(-1)(从目前来看,各顶点之间的最短路径长度)
V0V1V2
V00613
V11004
V250
  • path(-1)(两个顶点之间的中转点)
V0V1V2
V0-1-1-1
V1-1-1-1
V2-1-1-1

step1. 第一轮(允许在顶点 V0 中转)

上一步中可以发现,如果加入了 V0 中转,则有:

A-1[2][1] = ∞ > A-1[2][0] + A-1[0][1] = 11

所以应改为:

  • A(0)(从目前来看,各顶点之间的最短路径长度)
V0V1V2
V00613
V11004
V25110
  • path(0)(两个顶点之间的中转点)
V0V1V2
V0-1-1-1
V1-1-1-1
V2-10-1

step2. 第二轮(允许在顶点 V1 中转/加入顶点 V1 进行中转)

上一步中可以发现,如果在加入了 V0 中转的基础上,又加入了 V1 中转,则有:

A0[0][2] = 13 > A0[0][1] + A0[1][2] = 10

所以应改为:

  • A(1)(从目前来看,各顶点之间的最短路径长度)
V0V1V2
V00610
V11004
V25110
  • path(1)(两个顶点之间的中转点)
V0V1V2
V0-1-11
V1-1-1-1
V2-10-1

step3. 第三轮(允许在顶点 V2 中转/加入顶点 V2 进行中转)

上一步中可以发现,如果在加入了 V0、V1 中转的基础上,又加入了 V2 中转,则有:

A1[1][0] = 13 > A1[1][2] + A1[2][0] = 9

所以应改为:

  • A(2)(从目前来看,各顶点之间的最短路径长度)
V0V1V2
V00610
V1904
V25110
  • path(2)(两个顶点之间的中转点)
V0V1V2
V0-1-11
V12-1-1
V2-10-1

由于没有其他的顶点,因此算法结束。

根据以上两个矩阵:

  • 从 V0 到 V2,在矩阵 A 中获知最短路径为 10,在矩阵 path 中获知路径是 V0–>V1–>V2;
  • 从 V1 到 V0,在矩阵 A 中获知最短路径为 2,在矩阵 path 中获知路径是 V1–>V2–>V0;
  • 以此类推。

较复杂的例子

如果要找从 V0 到 V4 的最短路径:

  • 在矩阵 A 中得知 V0 到 V4 的最短路径长度为 4,在矩阵 path 中得知从 V0 到 V4 需经过 V3;
  • 在矩阵 A 中得知 V0 到 V3 的最短路径长度为 3,在矩阵 path 中得知从 V0 到 V3 需经过 V2;
  • 在矩阵 A 中得知 V0 到 V2 的最短路径长度为 1,在矩阵 path 中得知从 V0 到 V2 不需经过顶点;
  • 在矩阵 A 中得知 V2 到 V3 的最短路径长度为 2,在矩阵 path 中得知从 V2 到 V3 需经过 V1;
  • 在矩阵 A 中得知 V1 到 V3 的最短路径长度为 1,在矩阵 path 中得知从 V1 到 V3 不需要经过顶点;
  • 最终,从 V0 到 V4 的最短路径是 4,路径为 V0–>V2–>V1–>V3–>V4。

3 最小生成树

【性质】

  • 图的各边权值不相等时,其最小生成树不唯一
  • 最小生成树的权值是唯一的
  • 最小生成树的边数为顶点数减 1
  • 以下两种算法均基于贪心策略

3.1 Prim 算法

使用 Prim 算法手工构造最小生成树的过程如下:

3.2 Kruskal 算法

使用 Kruskal 算法手工构造最小生成树的过程如下:

---EOF---

数据结构-图图的定义(代码片段)

文章目录1邻接矩阵2邻接表3带权无向图4带权有向图1邻接矩阵#defineMAX50typedefcharVertexType;typedefintEdgeType;typedefstructVertexTypeVex[MAX];//顶点表EdgeTypeEdge[MAX][MAX];//边表MGraph;2邻接表顶点表结点:datafirstarc数据域边表头指针边表结点ÿ... 查看详情

数据结构—图图的定义图的存储结构(代码片段)

数据结构—图图的定义ADTGraph有向图与无向图有向图无向图图的基本术语端点和邻接点顶点的度、入度和出度完全图稠密图和稀疏图子图路径和路径长度连通、连通图、连通分量、可达强连通图和强连通分量权和网图的存储结构... 查看详情

.数据结构与算法基础(代码片段)

目录第六章.数据结构与算法基础(重点)第一节.数组与矩阵数组稀疏矩阵第二节.数据结构的定义第三节.线性表链表详解顺序存储与链式存储对比队列与栈第四节.广义表第五节.树与二叉树树的概念二叉树的分类二叉树... 查看详情

数据结构与算法的思考与历程(代码片段)

目录概述学习数据结构的初衷什么是算法数据结构的分类从逻辑角度分类从存储结构分类(又称物理结构)线性表线性表基本框架链表与数组的区别链表栈与队列栈与队列基本框架栈队列串二叉树二叉树基本框架二叉树... 查看详情

数据结构与算法图的基本结构介绍|邻接表与邻接矩阵编码实战(代码片段)

...f1a;“大数据小禅”🚀文章简介:本篇文章对基本数据结构图进行了一个概述,并使用领接矩阵与邻接表的方式来实现一个图🚀个人主页:大数据小禅图的基本结构介绍图的应用图的分类图的应用图是一种数... 查看详情

数据结构与算法图(graph)详解(代码片段)

文章目录图图的基本概念一、图的定义二、图的基本概念和术语1、有向图2、无向图3、简单图4、多重图5、完全图(也称简单完全图)6、子图7、连通、连通图和连通分量8、强连通图、强连通分量9、生成树、生成森林10、... 查看详情

chatgpt教你算法——常用的图搜索算法(代码片段)

...度优先搜索(BFS)是一种有序搜索算法,它从图的起点开始,按照图的宽度(即按照节点之 查看详情

算法笔记图结构及图的dfs和bfs介绍(代码片段)

...试中的图结构,并且还简单介绍了BFS和DFS文章目录1.图的基本介绍2.图的实现3.BFS4.DFS1.图的基本介绍基本概念:图由点的集合和边的集合构成虽然存在有向图和无向图的概念,但实际上都可以用有向图来表达边上可能... 查看详情

图图的存储图的遍历(代码片段)

图(Graph)是由顶点的有穷非空集合和顶点之间的边组成。G(V,E)V表示顶点的集合,E表示边的集合。在无向图中,边可以表示为E1=(A,D),(B,C)在有向图中,顶点v1和v2的有向边称为弧。表示为<v1,v2>v1称为弧尾,v2称为弧顶。在... 查看详情

数据结构与算法10—图的遍历(代码片段)

图的遍历1.在图中有回路,从图中某一顶点出发访问图中其它顶点时,可能又会回到出发点,而图中可能还剩余有顶点没有访问到。2.我们可以设置一个全局型标志数组visited来标志某个顶点是否被访问过,未访问的值为0,访问过... 查看详情

数据结构与算法学习笔记图(代码片段)

数据结构与算法学习笔记(8)图复习文章目录数据结构与算法学习笔记(8)图一.图的定义和基本术语二.图的类型定义三.图的存储结构1.邻接矩阵无向图的邻接矩阵有向图的邻接矩阵网(有权图)邻接矩阵的存储表示创建邻接矩阵(以无... 查看详情

邻接矩阵和邻接表存储的图的基本操作及完整代码(代码片段)

...0c;完整代码请移步我的资源中免费下载。邻接矩阵存储的图图的邻接矩阵存储结构定义:#definemaxVertexNum30//图中顶点数目的最大值typedefcharVertexType;//图中顶点的数据类型typedefintEdgeType;//边上权值的数据类型typedefstructVertexTypeVer... 查看详情

图的最短路径-----------dijkstra算法详解(tjuoj2870_thekthcity)(代码片段)

做OJ需要用到搜索最短路径的题,于是整理了一下关于图的搜索算法:图的搜索大致有三种比较常用的算法:迪杰斯特拉算法(Dijkstra算法)弗洛伊德算法(Floyd算法)SPFA算法Dijkstra算法使用了广度优先搜索解决赋权有向图或者无... 查看详情

王道数据结构6.2(图的应用)(代码片段)

图的应用一、最小生成树(一)什么是最小生成树?(二)求最小生成树算法(1)prim算法(2)Kruskal算法(克鲁斯卡尔)二、最短路径问题(一)单源最短路径问题(1)B... 查看详情

王道数据结构6.2(图的应用)(代码片段)

图的应用一、最小生成树(一)什么是最小生成树?(二)求最小生成树算法(1)prim算法(2)Kruskal算法(克鲁斯卡尔)二、最短路径问题(一)单源最短路径问题(1)B... 查看详情

数据结构学习笔记——图的遍历算法(深度优先搜索和广度优先搜索)(代码片段)

目录一、图的遍历概念二、深度优先搜索(DFS)(一)DFS算法步骤1、邻接表DFS算法步骤2、邻接矩阵DFS算法步骤(二)深度优先生成树、森林(三)DFS的空间复杂度和时间复杂度三、广度优先搜索&#x... 查看详情

数据结构图(代码片段)

...算法基本概念图是由顶点集合及顶点间的关系组成的一种数据结构:G=(V,E),其中:顶点集 查看详情

算法笔记图结构及图的dfs和bfs介绍(代码片段)

...将介绍如何应付面试中的图结构,并且还简单介绍了图的宽度优先遍历和深度优先遍历文章目录1.图的基本介绍2.图的实现3.图的宽度优先遍历(BFS)4.图的深度优先遍历(DFS)1.图的基本介绍基本概念:图... 查看详情