图的存储、构造、遍历、应用(代码+注释+图片说明)

图的存储与遍历 邻接矩阵(二维数组)即是:先用一 个数组将定点保存,然后采用矩阵来表示节点与节点之间的关系。矩阵可以存储0或者1来表示两个顶点之间是否连通,也可以存储权值。. 用邻接矩阵存储图是能够快速知道两个顶点是否连通,缺陷是如果顶点比较多,边比较少时,矩阵中存储了大量值为0的矩阵,比较浪费空间,并且要求两个节点之间的路径不是很好求。例如:上面的邻接矩阵中,如果两个顶点之间不连通的话,用无穷大来表示。上面的是有向图,无向图的矩阵是对称的。邻接表:使用数组表示顶点的集合,使用链表表示边的关系。 阅读详情

一、图的存储模式(这里讲述两种常用的)

(1)邻接矩阵

//邻接矩阵
#define inf 0x7fffffff
vector<bool> visit;

typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型

typedef struct {
	vector<VertexType> vex;//顶点表
	vector<vector<ArcType>> arc;//邻接矩阵 边表
	int vexNum, arcNum;//当前顶点数 边数
}MGraph;

(2)邻接表

//邻接表
#define MVNum 100//最大顶点数
vector<bool> visit;

typedef char VerTexType;
typedef int OtherInfo;

typedef struct ArcNode {
	int adjvex;//所指向的顶点的位置
	struct ArcNode* nextarc;//指向下一条边的指针
	OtherInfo info;//和边相关的其他信息
	ArcNode(int i) :adjvex(i), nextarc(NULL), info(NULL) {};
}ArcNode;

typedef struct VNode {
	VerTexType data;//顶点信息
	ArcNode* firstarc;//指向第一条依附该顶点的边的指针
}VNode, AdjList[MVNum];//AdjList表示邻接表类型

typedef struct {
	AdjList vertices;//顶点
	int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;

二、图的两种构造方式

(1)邻接矩阵构造

#include<iostream>
#include<vector>
#include <algorithm>
#include<iterator>
using namespace std;
#define inf 0x7fffffff

typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型

typedef struct {
	vector<VertexType> vex;//顶点表
	vector<vector<ArcType>> arc;//邻接矩阵 边表
	int vexNum,arcNum;//当前顶点数 边数
}MGraph;

//构造无向网:无向图权值为0或1即可
//			  有向网只赋值G[i][j]
void create_Graph(MGraph& G) {
	cin >> G.vexNum >> G.arcNum;//图的顶点数和边数

	//顶点信息
	G.vex.resize(G.vexNum);
	for (int i = 0; i < G.vexNum; i++) cin >> G.vex[i];

	//初始化边信息
	vector<vector<ArcType>> arc(G.vexNum,vector<ArcType>(G.vexNum,inf));//赋值为无穷大
	G.arc = arc;

	//边信息
	VertexType vex1, vex2;
	int weight;
	for (int k = 0; k < G.arcNum; k++) {
		cin >> vex1 >> vex2 >> weight;//两顶点 权值
		int i = find(G.vex.begin(), G.vex.end(), vex1)-G.vex.begin();//vex1 vex2在图中的位置
		int j = find(G.vex.begin(), G.vex.end(), vex2) - G.vex.begin();
		G.arc[i][j] = G.arc[j][i] = weight;//权值
	}

}

int main() {
	MGraph G;
	create_Graph(G);
	return 0;
}

(2)邻接表构造

#include<iostream>
using namespace std;
#define MVNum 100//最大顶点数

typedef char VerTexType;
typedef int OtherInfo;

typedef struct ArcNode {
	int adjvex;//所指向的顶点的位置
	struct ArcNode* nextarc;//指向下一条边的指针
	OtherInfo info;//和边相关的其他信息
	ArcNode(int i) :adjvex(i), nextarc(NULL), info(NULL) {};
}ArcNode;

typedef struct VNode {
	VerTexType data;//顶点信息
	ArcNode* firstarc;//指向第一条依附该顶点的边的指针
}VNode,AdjList[MVNum];//AdjList表示邻接表类型

typedef struct {
	AdjList vertices;//顶点
	int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;

//创建无向图
void CreateUDG(ALGraph& G){
	cin >> G.vexnum >> G.arcnum;//输入图的顶点数 边数
	for (int i = 0; i < G.vexnum; i++) {//建立顶点表
		cin >> G.vertices[i].data;//输入顶点值
		G.vertices[i].firstarc = NULL;//初始化表头节点的指针域
	}
	VerTexType vex1, vex2;
	for (int k = 0; k < G.arcnum; k++) {//输入各边 构造邻接表
		cin >> vex1 >> vex2;//输入一条边依附的两个顶点
		int i = Locate(G, vex1);
		int j = Locate(G, vex2);
		ArcNode* p1 = new ArcNode(j);//生成新节点头插入边表
		p1->nextarc = G.vertices[i].firstarc;
		G.vertices[i].firstarc = p1;
		ArcNode* p2 = new ArcNode(i);
		p2->nextarc = G.vertices[j].firstarc;
		G.vertices[j].firstarc = p2;
	}

}

//找下标
int Locate(ALGraph G, VerTexType vex) {
	for (int i = 0; i < G.vexnum; i++) {
		if (G.vertices[i].data = vex) return i;
	}
	return -1;
}

int main() {
	ALGraph G;
	CreateUDG(G);
	return 0;
}

三、图的遍历(BFS、DFS)

这里给出一个无向图:
在这里插入图片描述

(1)邻接矩阵深度、广度优先遍历

#include<iostream>
#include<vector>
#include <algorithm>
#include<iterator>
#include<queue>
using namespace std;
 
//邻接矩阵
#define inf 0x7fffffff
vector<bool> visit;

typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型

typedef struct {
	vector<VertexType> vex;//顶点表
	vector<vector<ArcType>> arc;//邻接矩阵 边表
	int vexNum, arcNum;//当前顶点数 边数
}MGraph;

//构造无向网:无向图权值为0或1即可
//			  有向网只赋值G[i][j]
void CreateUDG(MGraph& G) {
	cin >> G.vexNum >> G.arcNum;//图的顶点数和边数

	//顶点信息
	G.vex.resize(G.vexNum);
	for (int i = 0; i < G.vexNum; i++) cin >> G.vex[i];

	//初始化边信息
	vector<vector<ArcType>> arc(G.vexNum, vector<ArcType>(G.vexNum, inf));//赋值为无穷大
	G.arc = arc;

	//边信息
	VertexType vex1, vex2;
	int weight;
	for (int k = 0; k < G.arcNum; k++) {
		cin >> vex1 >> vex2 >> weight;//两顶点 权值
		int i = find(G.vex.begin(), G.vex.end(), vex1) - G.vex.begin();//vex1 vex2在图中的位置
		int j = find(G.vex.begin(), G.vex.end(), vex2) - G.vex.begin();
		G.arc[i][j] = G.arc[j][i] = weight;//权值
	}

}

//从顶点出发 深度优先遍历图
void DFS(MGraph G,int v) {
	cout << G.vex[v]<<" ";
	visit[v] = true;
	for (int i = 0; i < G.vexNum; i++) {
		if (G.arc[v][i] != inf && !visit[i]) {//查找尚未访问的邻节点
			DFS(G,i);
		}
	}
}

//深度优先遍历
void DFSTraverse(MGraph G) {
	visit.resize(0);//这里两次resize是为了保证初始化为false
	visit.resize(G.vexNum);//初始化访问节点数组
	for (int i = 0; i < G.vexNum; i++) {//这里for循环的是防止该图不是连通图
		if (!visit[i]) DFS(G, i);
	}
}

//广度优先遍历
void BFS(MGraph G) {
	visit.resize(0);//这里两次resize是为了保证初始化为false
	visit.resize(G.vexNum);//初始化访问节点数组
	queue<VertexType>Q;
	for (int i = 0; i < G.vexNum; i++) {//这里for循环的意义同样是防止是非连通图
		if (!visit[i]) {//如果节点未被访问
			Q.push(G.vex[i]);//添加节点
			visit[i] = true;
			while (!Q.empty()) {
				cout << Q.front()<< " ";
				int k = find(G.vex.begin(), G.vex.end(), Q.front()) - G.vex.begin();
				Q.pop();
				for (int j = 0; j < G.vexNum; j++) {
					if (!visit[j]&&G.arc[k][j]!=inf) {
						Q.push(G.vex[j]);
						visit[j] = true;
					}
				}
			}
		}
	}
}
int main() {
	MGraph G;
	CreateUDG(G);
	cout << "DFS:";
	DFSTraverse(G);
	puts("");
	cout << "BFS:";
	BFS(G);
	return 0;
}

这里带权值:

5 5
A B C D E
A B 1
A C 1
B D 1
B E 1
D E 1
DFS:A B D E C
BFS:A B C D E

(2)邻接表深度、广度优先遍历

#include<iostream>
#include<vector>
#include<queue>
using namespace std;

//邻接表
#define MVNum 100//最大顶点数
vector<bool> visit;

typedef char VerTexType;
typedef int OtherInfo;

typedef struct ArcNode {
	int adjvex;//所指向的顶点的位置
	struct ArcNode* nextarc;//指向下一条边的指针
	OtherInfo info;//和边相关的其他信息
	ArcNode(int i) :adjvex(i), nextarc(NULL), info(NULL) {};
}ArcNode;

typedef struct VNode {
	VerTexType data;//顶点信息
	ArcNode* firstarc;//指向第一条依附该顶点的边的指针
}VNode, AdjList[MVNum];//AdjList表示邻接表类型

typedef struct {
	AdjList vertices;//顶点
	int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;

//找下标
int Locate(ALGraph G, VerTexType vex) {
	for (int i = 0; i < G.vexnum; i++) {
		if (G.vertices[i].data == vex) return i;
	}
	return -1;
}

//创建无向图
void CreateUDG(ALGraph& G) {
	cin >> G.vexnum >> G.arcnum;//输入图的顶点数 边数
	for (int i = 0; i < G.vexnum; i++) {//建立顶点表
		cin >> G.vertices[i].data;//输入顶点值
		G.vertices[i].firstarc = NULL;//初始化表头节点的指针域
	}
	VerTexType vex1, vex2;
	for (int k = 0; k < G.arcnum; k++) {//输入各边 构造邻接表
		cin >> vex1 >> vex2;//输入一条边依附的两个顶点
		int i = Locate(G, vex1);
		int j = Locate(G, vex2);
		ArcNode* p1 = new ArcNode(j);//生成新节点头插入边表
		p1->nextarc = G.vertices[i].firstarc;
		G.vertices[i].firstarc = p1;
		ArcNode* p2 = new ArcNode(i);
		p2->nextarc = G.vertices[j].firstarc;
		G.vertices[j].firstarc = p2;
	}

}

//从顶点出发 深度优先遍历图
void DFS(ALGraph G,int v) {
	cout << G.vertices[v].data << " ";
	visit[v] = true;
	ArcNode* p = G.vertices[v].firstarc;
	while (p->nextarc) {//查找尚未访问的邻节点
		if (!visit[p->adjvex]) DFS(G, p->adjvex);
		p = p->nextarc;
	}
}

//深度优先遍历图
void DFSTraverse(ALGraph G) {
	visit.resize(0);//这里两次resize是为了保证初始化为false
	visit.resize(G.vexnum);//初始化访问节点数组
	for (int i = 0; i < G.vexnum; i++) {//这里for循环的意义也是防止图为非连通图
		if (!visit[i]) DFS(G, i);
	}
}

//广度优先遍历
void BFS(ALGraph G) {
	visit.resize(0);//这里两次resize是为了保证初始化为false
	visit.resize(G.vexnum);//初始化访问节点数组
	queue<VerTexType> Q;//队列存储节点
	for (int i = 0; i < G.vexnum; i++) {//这里for循环的意义同样是防止是非连通图
		if (!visit[i]){
			Q.push(G.vertices[i].data);//添加节点
			visit[i] = true;
			while (!Q.empty()) {
				cout << Q.front() << " ";
				int k = Locate(G, Q.front());
				Q.pop();
				ArcNode* p = G.vertices[k].firstarc;
				while (p->nextarc) {//查找尚未访问的邻节点入队列
					if (!visit[p->adjvex]) {
						Q.push(G.vertices[p->adjvex].data);
						visit[p->adjvex] = true;
					}
					p = p->nextarc;
				}
			}
		}
	}
	
}

int main() {
	ALGraph G;
	CreateUDG(G);
	cout << "DFS:";
	DFSTraverse(G);
	puts("");
	cout << "BFS:";
	BFS(G);
	return 0;
}

这里没带权值:

5 5
A B C D E
A B
A C
B D
B E
D E
DFS:A C B E D
BFS:A C B E D

四、图的应用(最小生成树、最短路径、拓扑排序、关键路径)

1.最小生成树

一个连通图的生成树是指一个极小连通子图,它含有图中的全部顶点,但只有足以构成一棵树的 n-1 条边。在一个连通网的所有生成树中,各边代价之和最小的那棵生成树称为该连通网 最小代价生成树(MST),简称为 最小生成树。利用普里姆(Prim)算法和克鲁斯卡尔(Kruscal)算法可以生成一个连通网的最小代价生成树,它们都是基于贪心算法策略。

1.1 普里姆(Prim)算法

(1)基本步骤

假设N=(V,{E})是连通网,TE为最小代价生成树中边的集合。
① 初始U={u0}(u0∈V),TE=空集;
② 在所有u∈U,v∈V-U的边中选一条代价最小的边(u0,v0)并入集合TE,同时将v0并入U;
③ 重复②,直到U=V为止,此时必然存在n-1条边。

通俗点说就是:从一个顶点出发,在保证不形成回路的前提下,每找到并添加一条最短的边,就把当前形成的连通分量当做一个整体或者一个点看待,然后重复“找最短的边并添加”的操作。

这里有兄弟问过我为什么不需要判断环,哥们,咱动动脑子想想,形成环要什么条件?这里是两个集合,要形成环,两个顶点必须在一个集合里面,Prim算法显然不是的。

示例:在这里插入图片描述

(2)代码实现+注释
#include<iostream>
#include<vector>
using namespace std;
#define inf 0x7fffffff

typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型

typedef struct {
	vector<VertexType> vex;//顶点表
	vector<vector<ArcType>> arc;//邻接矩阵 边表
	int vexNum, arcNum;//当前顶点数 边数
}MGraph;

//构造无向网:无向图权值为0或1即可
//			  有向网只赋值G[i][j]
void CreateUDG(MGraph& G) {
	cin >> G.vexNum >> G.arcNum;//图的顶点数和边数

	//顶点信息
	G.vex.resize(G.vexNum);
	for (int i = 0; i < G.vexNum; i++) cin >> G.vex[i];

	//初始化边信息
	vector<vector<ArcType>> arc(G.vexNum, vector<ArcType>(G.vexNum, inf));//赋值为无穷大
	G.arc = arc;

	//边信息
	VertexType vex1, vex2;
	int weight;
	for (int k = 0; k < G.arcNum; k++) {
		cin >> vex1 >> vex2 >> weight;//两顶点 权值
		int i = find(G.vex.begin(), G.vex.end(), vex1) - G.vex.begin();//vex1 vex2在图中的位置
		int j = find(G.vex.begin(), G.vex.end(), vex2) - G.vex.begin();
		G.arc[i][j] = G.arc[j][i] = weight;//建立对称弧
	}

}

//Prim算法生成最小生成树
//时间复杂度:O(n*n)即O(v*v)
void MiniSpanTree_Prim(MGraph G){
	int j, n = G.vexNum-1, mincost;//j用来记录下标 n用来记录循环遍历 mincost记录最小权值
	//path保存相关顶点下标 这里从v0开始 即下标零开始 解释:path[i] = j——> 第i个顶点与第j个顶点的弧 这里首先初始化所有点与顶点0的弧
	vector<int> path(G.vexNum, 0);
	//lowcost保存顶点间边的权值 这里从v0开始 即下标零开始 初始化arc[0](将v0顶点与之组成的边的权值存入数组)lowcost[i] = w, path[i] = j 
	//即顶点i和顶点j之间弧权值为w
	//就是保存已加入树的点 距离此点的最小权值
	vector<int> lowcost(G.arc[0]);
	//初始化第一个权值为0,即v0加入生成树
	//lowcost的值为0,在这里就是此下标的顶点已经加入生成树,也相当于一个标记
	lowcost[0] = 0;
	//这里也可以写一个标记数组标记节点是否已经加入生成树中
	//如vector<int> isJoin(G.vexNum, false);
	while (n--) {
		mincost = inf;//初始化最小权值为inf(无穷大)
		for (int i = 1; i < G.vexNum; i++) {
			if (lowcost[i]!=0&&lowcost[i] < mincost) {//如果权值不为0且权值小于mincost 找除了已经存入树的顶点的最小权值
				mincost = lowcost[i];
				j = i;//记录当前下标
			}
		}
        cout<<j<<" ";
		cout << G.vex[path[j]] << " " << G.vex[j] << endl;//打印当前顶点边中权值最小边的两顶点,即新加入生成树的一条边
		lowcost[j] = 0;//将当前顶点权值设置为0 表示此顶点已经加入生成树

		//再次循环遍历,找到未加入的节点以及与下标为k的结点相连的结点的权值与前面的比较,更小则进行覆盖
		for (int i = 1; i < G.vexNum; i++) {//若下标为k顶点各边权值小于此前这些顶点未被加入生成树的边权值则覆盖原本lowcost并且建立弧关系
			//lowcost[i] = w, path[i] = j 
			//即顶点i和顶点k之间弧权值为j
			//就是保存已加入树的点 距离此点的最小权值
			if (lowcost[i] != 0&&G.arc[j][i] < lowcost[i]) {//这里lowcost[i]!=0没有实际意义 我这里加上就是提醒大家这里第i个顶点已经被加入 不会形成环 它是往外面找的
				lowcost[i] = G.arc[j][i];//将较小权值覆盖上一次权值 存入lowcost
				path[i] = j;//弧建立关系 即下标为k的顶点与顶点i联系
			}
		}

	}
}

int main() {
	MGraph G;
	CreateUDG(G);
	MiniSpanTree_Prim(G);
	return 0;
}
6 10
A B C D E F
A B 6
C D 6
B C 3
D E 2
A E 5
A F 1
B F 5
C F 6
D F 4
E F 5
A F
F D
D E
F B
B C

1.2 克鲁斯卡尔(Kruscal)算法

(1)基本步骤

假设N=(V,{E})是连通网,将N中的边按权值从小到大的顺序排列。
① 将n个顶点看成n个集合。
② 按权值从小到大的顺序选择边,所选边应满足两个顶点不在同一个顶点集合内,将该边放到生成树边的集合中,同时将该边的两个顶点所在的顶点集合合并。
③ 重复②直到所有的顶点都在同一顶点集合内。
示例:
在这里插入图片描述

(2)代码实现+注释
#include<iostream>
#include<vector>
#include<algorithm>
using namespace std;

typedef char VexType;

//边存储结构
typedef struct Edge {
	int begin;//起点序号
	int end;//尾点序号
	int weight;//权值
}Edge;

//图存储结构
typedef struct EdgeGraph {
	vector<VexType> vex;//顶点信息
	vector<Edge> arc;//边信息
	int vexnum, arcnum;//顶点数 边数

}EdgrGraph;

//权值升序
bool cmp(Edge e1,Edge e2) {
	return e1.weight < e2.weight;
}

//创建网
void Create_EdgeGraph(EdgeGraph& G) {
	cin >> G.vexnum >> G.arcnum;//输入顶点数和边数
	G.vex.resize(G.vexnum);//初始化顶点数组大小
	G.arc.resize(G.arcnum);//初始化边数组大小
	for (int k = 0; k < G.vexnum; k++) {
		cin >> G.vex[k];//输入顶点信息
	}
	VexType vex1, vex2;//两顶点
	int weight;//权值
	for (int k = 0; k < G.arcnum; k++) {//保存边信息
		cin >> vex1 >> vex2 >> weight;//输入边信息:两顶点 + 权值
		G.arc[k].begin = find(G.vex.begin(), G.vex.end(), vex1) - G.vex.begin();
		G.arc[k].end = find(G.vex.begin(), G.vex.end(), vex2) - G.vex.begin();
		G.arc[k].weight = weight;
	}
}

//Kruskar算法生成最小生成树
void MiniSpanTree_Kruskal(EdgeGraph G) {
	sort(G.arc.begin(), G.arc.end(), cmp);//按照权值升序
	vector<bool> vexset(G.vexnum, false);//节点是否已经存入树中
	for (int k = 0; k < G.arcnum; k++) {
		int i = G.arc[k].begin;
		int j = G.arc[k].end;
		if (!(vexset[i]&&vexset[j])) {//两顶点不在同一集合 不会形成环
			cout << G.vex[i] << " " << G.vex[j] << endl;
			vexset[i] = vexset[j] = true;
		}
	}
}

int main() {
	EdgeGraph G;
	Create_EdgeGraph(G);
	MiniSpanTree_Kruskal(G);
	return 0;
}
6 10
A B C D E F
A B 16
B C 5
B D 6
C D 6
D E 18
A E 19
A F 21
D F 14
E F 33
B F 11
B C
B D
B F
A B
D E

在这里插入图片描述

2.最短路径

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

(1)邻接矩阵

#include<bits/stdc++.h>
using namespace std;
#define inf 0x7fffffff

typedef char VertexType;//图的顶点类型
typedef int ArcType;//边的权值类型

typedef struct {
    vector<VertexType> vex;//顶点表
    vector<vector<ArcType>> arc;//邻接矩阵 边表
    int vexNum, arcNum;//当前顶点数 边数
}MGraph;

vector<int> path;
vector<int> d;

//BFS算法求解单源最短路径(无权连通图/所有边权值都相同的连通图)
//求顶点u到其他顶点的最短路径
void BFS_MIN_Distance(MGraph G, int u) {
    //标记访问
    vector<bool> visit(G.vexNum, false);
    //初始化path(先驱)和d(距离)
    path.resize(G.vexNum);
    d.resize(G.vexNum);
    for (int i = 0; i < G.vexNum; ++i) {
        d[i] = inf;     //初始化路径长度
        path[i] = -1;   //节点先驱
    }
    d[u] = 0;
    visit[u] = true;    //标记访问
    queue<int> q;
    q.push(u);
    while (!q.empty()) {
        //队头元素u出队
        u = q.front();
        q.pop();
        for (int j = 0; j < G.vexNum; j++) {
            if (!visit[j] && G.arc[u][j] != inf) {  //访问u尚未被访问过的邻接点
                d[j] = d[u] + 1;    //路径长度加一
                path[j] = u;        //j前驱为u
                q.push(j);       //顶点j入队
                visit[j] = true;    //设为访问标记
            }
        }
    }
}

int main() {
    MGraph G;
    CreateUDG(G);
    int u = 0;
    cin>>u;
    BFS_MIN_Distance(G, u-1);//这里u-1是因为节点从一开始,数组下标从0开始
    for(int i:d){
        cout<<i<<" ";
    }
    cout<<endl;
    for(int i:path){
        cout<<i<<" ";
    }
    return 0;
}
8 10
1 2 3 4 5 6 7 8
1 5
1 2
2 6
6 3
6 7
3 7
3 4
7 4
7 8
4 8
2
1 0 2 3 2 1 2 3 
1 -1 5 2 0 1 5 6(注意这里path整体减一是因为数组下标是从0开始的) 

在这里插入图片描述
(2)邻接表

//邻接表
#define MVNum 100//最大顶点数
#define inf 0x7fffffff
vector<int> path;
vector<int> d;

typedef char VerTexType;
typedef int OtherInfo;

typedef struct ArcNode {
	int adjvex;//所指向的顶点的位置
	struct ArcNode* nextarc;//指向下一条边的指针
	OtherInfo info;//和边相关的其他信息
	ArcNode(int i) :adjvex(i), nextarc(NULL), info(NULL) {};
}ArcNode;

typedef struct VNode {
	VerTexType data;//顶点信息
	ArcNode* firstarc;//指向第一条依附该顶点的边的指针
}VNode, AdjList[MVNum];//AdjList表示邻接表类型

typedef struct {
	AdjList vertices;//顶点
	int vexnum, arcnum;//图的顶点数和弧数
}ALGraph;


//BFS算法求解单源最短路径(无权连通图/所有边权值都相同的连通图)
//求顶点u到其他顶点的最短路径
void BFS_MIN_Distance(ALGraph G, int u) {
    //标记访问
    vector<bool> visit(G.vexnum, false);
    //初始化path(先驱)和d(距离)
    path.resize(G.vexnum);
    d.resize(G.vexnum);
    for (int i = 0; i < G.vexnum; ++i) {
        d[i] = inf;     //初始化路径长度
        path[i] = -1;   //节点先驱
    }
    d[u] = 0;
    visit[u] = true;    //标记访问
    queue<int> q;
    q.push(u);
    while (!q.empty()) {
        //队头元素u出队
        u = q.front();
        q.pop();
        ArcNode* p = G.vertices[u].firstarc;
        while(p){
            int j = p->adjvex;
            if(!visit[j]){
                d[j] = d[u] + 1;    //路径长度加一
                path[j] = u;        //j前驱为u
                q.push(j);       //顶点j入队
                visit[j] = true;    //设为访问标记
            }
            p = p->nextarc;
        }
    }

}


int main() {
    ALGraph G;
    CreateUDG(G);
    int u = 0;
    cin>>u;
    BFS_MIN_Distance(G, u-1);//这里u-1是因为节点从一开始,数组下标从0开始
    for(int i:d){
        cout<<i<<" ";
    }
    cout<<endl;
    for(int i:path){
        cout<<i<<" ";
    }
    return 0;
}
8 10
1 2 3 4 5 6 7 8
1 5
1 2
2 6
6 3
6 7
3 7
3 4
7 4
7 8
4 8
2
1 0 2 3 2 1 2 3 
1 -1 5 6 0 1 5 6 (注意这里path整体减一也是因为数组下标是从0开始的) 
图的定义、存储及遍历(算法笔记) 图,直观来理解就是地图,是一种逻辑结构为非线性的数据结构,是一种多对多的数据结构,每一个顶点都可以作为起点和终点,将图拆分开就是由若干棵树构成的森林。1.图的定义:图由顶点和边构成,每条边的两个端点必须是图的两个顶点,采用G(V,E)表示图G的顶点集为V、边集为E。一般来说,图可分为有向图和无向图。有向图的所有边都有方向,即确定了顶点到顶点的一个指向:而无向图的所有边都是双向的,即无向边所连接的两个顶点可以互相到达。 阅读详情

相关推荐

数据结构-图的遍历代码

广度优先搜索 1.准备工作:创建一个visited数组,用来记录已被访问过的顶点;创建一个队列,用来存放每一层的顶点;初始化图G。 2.从图中的v0开始访问,将的visited[v0]数组的值设置为true,同时将v0入队。 3.只要队列不空,则重复如下操作: (1)队头顶点u出队。 (2)依次检查u的所有邻接顶点w,若visited[w]的值为false,则访问w,并将visited[w]置为t...

Peisenli的博客 2138

数据结构 C 代码14:图的遍历

从图中某一顶点出发,并系统地访问完图中的所有顶点,且都恰好访问一次的运算操作就被称作 图的遍历 图的遍历理解起来并不是很难,可以分开理解:“遍”意为全部,“历”意为经历、经过,合起来就是:全部顶点都经过一遍图的遍历方式分为两种:深度优先遍历与广度优先遍历,二者的时间复杂度都是O(n2)深度优先遍历:(可以用来判断图中是否存在环) 思路:从图的某一顶点V0出发,访问此顶点;然后依次从V0的未被访问的邻接点出发,深度优先遍历图,直至图中所有和V0相通的顶点都被访问到;若此时图中尚有顶点未被访问,则另选图中一个未

cillin9的博客 1637

DeepseekHarness插件体系:从本地部署到工具调用的完整指南

本地部署大模型后,如果只能进行纯对话,实际生产力十分有限。要让模型真正落地,需要一套插件化工具链来补齐联网检索、代码执行、文档解析等能力。这类工具链的核心原理是“模型负责推理,插件负责执行”,通过调度器把用户意图与外部工具串联起来,形成闭环工作流。其技术价值在于将模型从问答机器升级为可接入业务的执行机,适合私有化知识库、自动化办公、代码辅助开发等场景。DeepSeek 系模型结合插件架构,可显著降低工具调用的开发成本。本文围绕 DeepseekHarness 的插件体系展开,介绍硬件门槛、8 类核心插件、部

weixin_34121282的博客 443

图的遍历-DFS,BFS(代码详解)

图的遍历-DFS,BFS(代码详解)!

weixin_64067830的博客 6万+

网易2026前端笔试真题解析与核心考点精讲

前端开发作为现代Web应用的核心技术栈,其技术体系包含HTML5语义化标签、CSS3布局方案和JavaScript异步编程等关键技术。语义化标签通过section、article等元素提升页面可访问性和SEO效果,Flex/Grid布局则解决了传统布局的响应式难题。在工程实践中,Promise和Async/Await已成为处理异步操作的标准方案,而闭包机制则是实现模块化的基础。这些技术被广泛应用于企业级应用开发,如网易等互联网公司的笔试真题中,常考察HTML5表单验证、CSS布局算法和JavaScript作

weixin_34417200的博客 400

Cohere Parse 5:2.3B视觉语言模型,轻松把文档转Markdown

文档解析是RAG知识库构建与内容自动化流程中的关键环节,传统OCR加规则匹配在面对复杂版面时往往力不从心。视觉语言模型通过直接理解页面布局与元素关系,将扫描件、PDF和图片转化为结构化的Markdown格式,大幅提升表格还原和标题层级识别的准确性。2.3B规模的轻量化设计,使本地部署成为可能,兼顾性能与成本。该技术适用于企业文档数字化、知识库构建、内容迁移等多类场景,可显著简化数据处理管道,减少后处理脚本工作量。本文围绕这一技术路线,系统拆解其核心能力、适用边界、硬件门槛、接入方法与效果验证流程,并给出批量

weixin_34110749的博客 425

复古 C 语言游戏代码修复与解析——以55.哈夫曼树构建与编码,生成为例

1.技术能力:掌握了古老 C 代码与现行标准的核心差异(如void main() vs int main()、非标准头文件 / 函数的替代方案),提升了编译调试、跨编译器适配的能力;2.工程化思维:学会使用 VSCode 配置 C 语言开发环境、通过 Git/Gitee 进行代码版本管理,理解了 “代码修复→工程化管理→版本控制” 的完整流程;3.算法理解:深入掌握哈夫曼树的构建逻辑和哈夫曼编码的生成原理,将理论算法与代码实现结合。

2503_94007425的博客 1267

Sqribble:面向内容生产的文档操作系统解析

文档操作系统(Document OS)是一种将排版规范、内容结构与出版规则封装为可执行逻辑的技术范式,其核心在于通过模板化、规则引擎与强约束机制,实现内容生成的确定性、可复现性与批量交付能力。区别于传统文字处理或AI生成工具,它聚焦于工程化的内容生产流程,支撑技术文档、客户方案、培训手册等高频、高一致性需求场景。本文深入剖析Sqribble所体现的云原生架构、模块化子系统协同及‘约束即赋能’设计哲学,揭示其如何以确定性规则替代概率性生成,成为市场、技术写作与教育领域高效落地的轻量级内容基础设施。

weixin_33895657的博客 352

Dragonfly 深入解析:兼容 Redis/Memcached 的高吞吐内存数据存储与核心设计

Dragonfly 是面向现代云应用工作负载的内存数据存储(in-memory datastore),API 层面与 Redis 和 Memcached 完全兼容,应用无需修改代码即可迁移接入。本文基于仓库 [README.ko-KR.md](https://link.gitcode.com/i/d4f5b1595cd42ac61c09704d3691743a)(韩文版项目说明)并结合 [src/

gitblog_00130的博客 166

TOGAF9.2/10 认证基础知识点概览表

(考点解析:业务场景是一种重要的技术,可以在企业架构的各个阶段使用,主要是架构愿景和业务架构,但如果需要,也可以在其他架构域中使用,以直接从业务的高级需求中派生架构的特征。“哪里”可以看作是对相关企业的界定、“为什么”可以被视为组织环境中的关键驱动因素和要素、“谁”是指确定发起人利益相关者和受业务指令影响的其他主要利益相关者,以创建企业架构,并确定他们来自企业的需求和优先级、他们与企业的关系以及彼此之间所需的工作行为。它是业务驱动和业务主导的,它结 合了所有业务线的必要努力,以实现所需的能力。

2302_81497763的博客 2076

debian linux系统安装教程,Debian 10(Buster)安装过程图文详解

Debian 项目发布了它的最新稳定版 Debian 10,其代号是 “Buster”,这个发布版将获得 5 年的支持。Debian 10 可用于 32 位和 64 位系统。这个发布版带来很多新的特色,列出下面一些特色:引入新的 Debian 10 的主题 “FuturePrototype”新版本的电脑桌面环境 GNOME 3.30、Cinnamon 3.8、KDE Plasma 5.14、MAT...

weixin_39864601的博客 4985

数据结构分析及其实现(Stack、Queue、Tree、LinkedList)

常见数据结构分析及实现 说明 本文中的代码是参考《Java编程思想》、某培训机构。 文中的代码放Github了,有兴趣的可以看看,点个star鼓励下我。 代码在Sublime中敲的,坑爹的GBK,注释了很多中文,一转码不能用了!!! 重点在思想,而不是实现 。再次推荐《Java编程思想》。 1、数据结构 编程的本质就是对数据(信息以数据的形式而存在)的处理,实际编程中不得不处理大量数据,因此实...

weixin_33695082的博客 803

PMD相位偏折术实战:如何用Python处理条纹图,让金属外壳的划痕无处遁形

本文详细介绍了PMD相位偏折术在金属外壳划痕检测中的应用,通过Python实现条纹图处理和局部特征增强技术,显著提升微小缺陷的检出率。文章涵盖从四步相移条纹图采集到形状图局部增强的完整流程,特别适合工业质检场景,帮助实现高精度、高效率的自动化检测。

weixin_29291573的博客 509

标记语言 HTML XHTML XML

什么是标记语言? 在电子计算机中,标记指计算机所能理解的信息符号,通过此种标记,计算机之间可以处理包含各种的信息比如文章等。 标记语言,用一系列约定好的标记来对电子文档进行标记,以实现对电子文档的语义、结构、及格式的定义。这些标记必须很容易的和内容区分,并且易于识别。 个人理解就是使用约定好的标记,比如用来对需要加粗的字体进行加粗,用来标记标题信息,用来标记女朋友等等,来标识一些东西,好在读...

爱吃肉的熊猫君's Blog 581

永磁同步电机矢量控制(十一)——基于损耗模型的PMSM效率优化策略

本文深入探讨了永磁同步电机(PMSM)基于损耗模型的效率优化策略,详细分析了铜损和铁损的构成与建模方法,并提出了最小损耗控制策略的实现方案。通过与传统MTPA控制的对比,展示了在不同工况下的效率提升效果,同时讨论了工程实现中的关键问题与解决方案,为PMSM的高效运行提供了理论支持和实践指导。

7z8x2c4v6b9的博客 231

图解说明JLink驱动与目标板电源匹配的关键细节

深入讲解JLink驱动安装及使用过程中,如何正确匹配目标板电源,避免常见通信故障。通过图解方式清晰展示关键连接细节,确保调试稳定可靠,是嵌入式开发中不可或缺的实战经验分享。

weixin_42173218的博客 431

如何读懂 ArduPilot 的飞行控制架构:一次完整的源码级深度解析

想象这样一个场景:你的多旋翼在强风里悬停,电机转速每秒要修正上百次;与此同时,它还要记住"起飞点在哪"、判断"电量还能撑多久",并且一旦指南针突然失准,必须在毫秒级内决定是返航还是原地降落。把这些事全部交给一块只有几百 MHz 的 STM32 芯片——这就是 ArduPilot 每天在做的事。 ArduPilot 是一套开源自动驾驶飞控软件,自 2010 年持续开发,通过统一的硬件抽象与模块化算

gitblog_00904的博客 214

手把手教你学Simulink--电机控制架构与算法实现​场景示例:基于Simulink的电机速度环PID调节仿真

本文带你完成了电机速度环PID参数的科学整定,实现了:✅ 理解了速度环的级联系统特性与关键参数影响工程整定法(理论初值)系统辨识法(参数未知时)✅ PID Tuner自动调参(推荐!自动调参兼具快速性与鲁棒性✅ 学会了前馈补偿抗饱和等工程实用技巧。

MHD0815的博客 653

如何从零复现 UniPath 多阶段 PathRefine 训练:2~3 阶段 warm-start 完整指南

想从零开始训练 UniPath 的晶体结构生成模型,最核心的挑战在于它的 **多阶段 PathRefine 训练流水线**:每条流水线都由 2~3 个阶段串接完成,后续阶段通过 **warm-start(`partial_ckpt_load` 部分权重加载)** 继承前一阶段的 checkpoint,并用 **freeze-epoch** 策略分阶段解冻新增的 flow 头。本文为 UniPath

gitblog_00426的博客 315

【嘉立创EDA】PCB 多层板如何交换内层,4层板交换板层

本文使用嘉立创EDA,描述如何在PCB设计时,如何交换各个板层,特别是4层板需要交换内层时,应该如何操作,以供有需要的读者参考。

l64481000的博客 2013

全功能tgbot/Telegram机器人多功能有后台版源码

trx兑换 闪兑 关键字监控 群管 usdt监控 余额查询 推广分享 等几乎常见的tg机器人功能都有。全功能tgbot/telegram机器人多功能有后台版源码-打包好的可以直接上手。

百创科技 2700

RAG学习

RAG

qq_44862692的博客 216

大中型超市收银系统(可直接运行)

光步前台收银系统系统功能:商品销售、商品退货、挂单、收银员权限、会员销售、单品取消、整单取消、商品查询、脱机销售、脱机数据上传,基本资料下载,开钱箱、小票打印、收款结算,支持多币种付款方式(现金、储值卡、礼券、微信等),支持多种促销。运行环境:.net 4.0登陆信息用户名:1001 密码:123456内置商品信息,商品编码可输入00001000 - 00010000,测试效果相关功能键F5 结算 F9挂单 F11商品查询 F8退货 Insert单品取消 Del整单取消 F12开钱箱F2 会员 Ctrl+Z退出系统 Home 锁屏 相关源码待公开

【数据结构】基础:图的遍历实现(附C++源代码)

摘要:将会在数据结构专题中开展关于图论的内容介绍,其中包括四部分,分别为图的概念与实现、图的遍历、图的最小生成树以及图的最短路径问题。本文将介绍图的遍历,分别为深度优先遍历和广度优先遍历,需要了解其实现实现与方法。

qq_66725576的博客 2361

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

win10 跳过U8环境检测的IE WebControls.rar

win10 安装u8的时候 提示IE WebControls 不通过,这个工具可以欺骗u8的检查。 跳过U8环境检测的IE WebControls。提示导入失败后,重新检测通过。

【算法】图的存储和遍历

一般来说,树和图有两种存储方式,树是无环连通图,树是特殊的图,这里只讲图。 图分成两种有向图和无向图 无向图:有向图建两条边,a->b , b->a 所以说,无向图是一种特殊的有向图 , 我们只讲 有向图的存储

指针不指南吗的博客 1989

最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版

Part-E1-SDIO-Specification-Ver4.00-Final-120220.pdf:最新的sdio 4.0 spec 协议标准 带书签 高清完整PDF版,包括SDIO 接口的外设,SDIO 接口官方协议等。

【数据结构】图的定义,存储,遍历

详细讲解图的定义,存储,遍历

小吉妙妙屋 1354
上一篇: [LeetCode]1828. 统计一个圆中点的数目
下一篇: [LeetCode]1663. 具有给定数值的最小字符串(贪心+逆向思维)
天皓Oo
博客等级 码龄5年 337粉丝 61原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值