当前位置: 首页 > news >正文

C/C++ 数据结构与算法【图】 图+邻接矩阵+邻接表+DFS+BFS+最小生成树+最短路径+拓扑排序详细解析【日常学习,考研必备】带图+详细代码

一、图的定义

在这里插入图片描述

1)无向图,有向图,完全图

在这里插入图片描述

在这里插入图片描述

2)稀疏图,稠密图,网,邻接,关联

在这里插入图片描述

3)度

在这里插入图片描述

4)路径

在这里插入图片描述

5)连通图

在这里插入图片描述

6)权与网

在这里插入图片描述

7)子图

在这里插入图片描述

8)连通分量

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

二、图的类型定义

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

三、图的存储结构

在这里插入图片描述

1)邻接矩阵表示法(数组)

在这里插入图片描述

(1)无向图邻接矩阵

在这里插入图片描述

(2)有向图邻接矩阵

在这里插入图片描述
在这里插入图片描述

(3)网的邻接矩阵

在这里插入图片描述

(4)邻接矩阵存储表示

1、用两个数组分别存储顶点表和邻接矩阵
#define MVNum 100        //最大顶点数
typedef char VerTexType; //设顶点的数据类型为字符型
typedef int ArcType;     //假设边的权值类型为整型typedef struct{VerTexType vexs[MVNum];      //顶点表ArcType arcs[MVNum][MVNum];   //邻接矩阵int vexnum, arcnum;           //图的当前点数和边数
}AMGraph; // Adjacency Matrix Graph
2、采用邻接矩阵表示法创建无向图

在这里插入图片描述

#include <iostream>
#include <climits> // 用于INT_MAX
#include <sstream> // 用于istringstreamusing namespace std;#define MVNum 100        // 最大顶点数
typedef char VerTexType; // 设顶点的数据类型为字符型
typedef int ArcType;     // 假设边的权值类型为整型// 定义状态类型
typedef enum { ERROR, OK } Status;// 图的结构体定义
typedef struct {VerTexType vexs[MVNum];      // 顶点表ArcType arcs[MVNum][MVNum];   // 邻接矩阵int vexnum, arcnum;           // 图的当前顶点数和边数
} AMGraph; // 邻接矩阵图// 查找顶点u的位置,存在则返回顶点表中的下标;否则返回-1
int LocateVex(const AMGraph& G, VerTexType u) {for (int i = 0; i < G.vexnum; ++i) {if (u == G.vexs[i]) {return i;}}return -1;
}// 创建无向网G
Status CreateUDN(AMGraph& G) {cin >> G.vexnum >> G.arcnum; // 输入总顶点数,总边数for (int i = 0; i < G.vexnum; ++i) {cin >> G.vexs[i]; // 依次输入点的信息}// 初始化邻接矩阵for (int i = 0; i < G.vexnum; ++i) {for (int j = 0; j < G.vexnum; ++j) {G.arcs[i][j] = INT_MAX; // 边的权值均置为极大值(无穷大)}}for (int k = 0; k < G.arcnum; ++k) { // 构造邻接矩阵VerTexType v1, v2;ArcType w;cin >> v1 >> v2 >> w; // 输入一条边所依附的顶点及边的权值int i = LocateVex(G, v1); // 确定v1 和 v2在G中的位置int j = LocateVex(G, v2);if (i == -1 || j == -1) return ERROR; // 如果顶点不存在,返回错误G.arcs[i][j] = w; // 边<v1,v2>的权值置为wG.arcs[j][i] = w; // 无向图,对称设置}return OK;
}// 打印图的信息
void PrintGraph(const AMGraph& G) {cout << "图的顶点: ";for (int i = 0; i < G.vexnum; ++i) {cout << G.vexs[i] << ' ';}cout << '\n';cout << "邻接矩阵:\n";for (int i = 0; i < G.vexnum; ++i) {for (int j = 0; j < G.vexnum; ++j) {if (G.arcs[i][j] == INT_MAX) {cout << "INF "; // 表示无穷大}else {cout << G.arcs[i][j] << ' ';}}cout << '\n';}
}int main() {AMGraph G;// 模拟用户输入:3个顶点,3条边// 顶点:A B C// 边:A-B:5 B-C:6 A-C:7istringstream mockInput("3 3\nA B C\nA B 5\nB C 6\nA C 7");// 将mockInput绑定到cin,用于测试streambuf* prevCinBuf = cin.rdbuf(mockInput.rdbuf());if (CreateUDN(G) == OK) {cout << "图创建成功。\n";PrintGraph(G);}else {cerr << "创建图时发生错误。\n";}// 恢复cin至原始状态cin.rdbuf(prevCinBuf);return 0;
}
3、运行结果

在这里插入图片描述

4、采用邻接矩阵表示有向图

在这里插入图片描述

#include <iostream>
#include <climits> // 用于INT_MAX
#include <sstream> // 用于istringstreamusing namespace std;#define MVNum 100        // 最大顶点数
typedef char VerTexType; // 设顶点的数据类型为字符型
typedef int ArcType;     // 假设边的权值类型为整型// 定义状态类型
typedef enum { ERROR, OK } Status;// 图的结构体定义
typedef struct {VerTexType vexs[MVNum];      // 顶点表ArcType arcs[MVNum][MVNum];   // 邻接矩阵int vexnum, arcnum;           // 图的当前顶点数和边数
} AMGraph; // 邻接矩阵图// 查找顶点u的位置,存在则返回顶点表中的下标;否则返回-1
int LocateVex(const AMGraph& G, VerTexType u) {for (int i = 0; i < G.vexnum; ++i) {if (u == G.vexs[i]) {return i;}}return -1;
}// 创建有向网G
Status CreateDG(AMGraph& G) {cin >> G.vexnum >> G.arcnum; // 输入总顶点数,总边数for (int i = 0; i < G.vexnum; ++i) {cin >> G.vexs[i]; // 依次输入点的信息}// 初始化邻接矩阵for (int i = 0; i < G.vexnum; ++i) {for (int j = 0; j < G.vexnum; ++j) {G.arcs[i][j] = INT_MAX; // 边的权值均置为极大值(无穷大)}}for (int k = 0; k < G.arcnum; ++k) { // 构造邻接矩阵VerTexType v1, v2;ArcType w;cin >> v1 >> v2 >> w; // 输入一条边所依附的顶点及边的权值int i = LocateVex(G, v1); // 确定v1 和 v2在G中的位置int j = LocateVex(G, v2);if (i == -1 || j == -1) return ERROR; // 如果顶点不存在,返回错误G.arcs[i][j] = w; // 有向图,只设置单向边<v1,v2>的权值为w}return OK;
}// 打印图的信息
void PrintGraph(const AMGraph& G) {cout << "图的顶点: ";for (int i = 0; i < G.vexnum; ++i) {cout << G.vexs[i] << ' ';}cout << '\n';cout << "邻接矩阵:\n";for (int i = 0; i < G.vexnum; ++i) {for (int j = 0; j < G.vexnum; ++j) {if (G.arcs[i][j] == INT_MAX) {cout << "INF "; // 表示无穷大}else {cout << G.arcs[i][j] << ' ';}}cout << '\n';}
}int main() {AMGraph G;// 模拟用户输入:3个顶点,3条边// 顶点:A B C// 边:A->B:5 B->C:6 A->C:7istringstream mockInput("3 3\nA B C\nA B 5\nB C 6\nA C 7");// 将mockInput绑定到cin,用于测试streambuf* prevCinBuf = cin.rdbuf(mockInput.rdbuf());if (CreateDG(G) == OK) {cout << "有向图创建成功。\n";PrintGraph(G);}else {cerr << "创建有向图时发生错误。\n";}// 恢复cin至原始状态cin.rdbuf(prevCinBuf);return 0;
}
5、运行结果

在这里插入图片描述

邻接矩阵——有什么好处?

  • 直观、简单、好理解

  • 方便检查任意一对顶点间是否存在边

  • 方便找任一顶点的所有“邻接点”(有边直接相连的顶点)

  • 方便计算任一顶点的”度“(从该点发出的边数为“出度”,指向该点的边数为“入度”)

    • 无向图:对应行(或列)非0元素的个数

    • 有向图:对应行非0元素的个数是“出度”对应列非0元素的个数是“入度“

邻接矩阵——有什么不好?

  1. 不便于增加和删除顶点
  2. 浪费空间——存稀疏图(点很多而边很少)有大量无效元素对稠密图(特别是完全图)还是很合算的
  3. 浪费时间——统计稀疏图中一共有多少条边

2)邻接表表示法(链式)

在这里插入图片描述

(1)无向图邻接表

在这里插入图片描述

(2)有向图邻接表

在这里插入图片描述

(3)邻接表存储表示

在这里插入图片描述

说明**:例如:**AdjList v; **相当于:**VNode v[MVNum];

在这里插入图片描述

1、图的结构定义
typedef struct {AdjList vertices;  // vertices--vetex的复数int vexnum,arcnum; // 图的当前顶点数和弧数
} ALGraph;

在这里插入图片描述

2、采用邻接表表示法创建无向图

在这里插入图片描述

#include <iostream>
#include <sstream> // 用于istringstreamusing namespace std;#define MVNum 100 // 最大顶点数typedef char VerTexType; // 设顶点的数据类型为字符型// 边结点定义
typedef struct ArcNode {int adjvex;              // 该边所指向的顶点的位置struct ArcNode* nextarc; // 指向下一条边的指针
} ArcNode;// 顶点结点定义
typedef struct VNode {VerTexType data;    // 顶点信息ArcNode* firstarc;  // 指向第一条依附该顶点的边的指针
} VNode;// 邻接表类型定义
typedef struct {VNode vertices[MVNum]; // 图的顶点数组int vexnum, arcnum;    // 图的当前顶点数和边数
} ALGraph;// 定义状态类型
typedef enum { ERROR, OK } Status;// 查找顶点u的位置,存在则返回顶点表中的下标;否则返回-1
int LocateVex(const ALGraph& G, VerTexType u) {for (int i = 0; i < G.vexnum; ++i) {if (u == G.vertices[i].data) {return i;}}return -1;
}// 创建无向图G
Status 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; // 初始化表头结点的指针域}for (int k = 0; k < G.arcnum; ++k) {VerTexType v1, v2;cin >> v1 >> v2; // 输入一条边依附的两个顶点int i = LocateVex(G, v1);int j = LocateVex(G, v2);if (i == -1 || j == -1) return ERROR; // 如果顶点不存在,返回错误// 生成新的边结点*p1,并将其插入顶点vi的边表头部ArcNode* p1 = new ArcNode{ j, G.vertices[i].firstarc };G.vertices[i].firstarc = p1;// 生成新的对称边结点*p2,并将其插入顶点vj的边表头部ArcNode* p2 = new ArcNode{ i, G.vertices[j].firstarc };G.vertices[j].firstarc = p2;}return OK;
}// 打印图的信息
void PrintGraph(const ALGraph& G) {cout << "图的顶点: ";for (int i = 0; i < G.vexnum; ++i) {cout << G.vertices[i].data << ' ';}cout << '\n';cout << "邻接表:\n";for (int i = 0; i < G.vexnum; ++i) {cout << G.vertices[i].data << ": ";ArcNode* p = G.vertices[i].firstarc;while (p != NULL) {cout << G.vertices[p->adjvex].data << ' ';p = p->nextarc;}cout << '\n';}
}int main() {ALGraph G;// 模拟用户输入:3个顶点,3条边// 顶点:A B C// 边:A-B A-C B-Cstring input = "3 3\nA B C\nA B\nA C\nB C";istringstream mockInput(input);// 将mockInput绑定到cin,用于测试streambuf* prevCinBuf = cin.rdbuf(mockInput.rdbuf());if (CreateUDG(G) == OK) {cout << "无向图创建成功。\n";PrintGraph(G);} else {cerr << "创建无向图时发生错误。\n";}// 恢复cin至原始状态cin.rdbuf(prevCinBuf);return 0;
}
3、运行结果

在这里插入图片描述

4、根据邻接表表示法创建有向图
#include <iostream>
#include <climits> // 用于INT_MAX
#include <sstream> // 用于istringstreamusing namespace std;#define MVNum 100 // 最大顶点数typedef char VerTexType; // 设顶点的数据类型为字符型
typedef int OtherInfo;    // 假设和边相关的信息类型为整型// 边结点定义
typedef struct ArcNode {int adjvex;              // 该边所指向的顶点的位置struct ArcNode* nextarc; // 指向下一条边的指针OtherInfo info;          // 和边相关的信息
} ArcNode;// 顶点结点定义
typedef struct VNode {VerTexType data;   // 顶点信息ArcNode* firstarc; // 指向第一条依附该顶点的边的指针
} VNode;// 邻接表类型定义
typedef struct {VNode vertices[MVNum]; // vertices--vertex的复数int vexnum, arcnum;    // 图的当前顶点数和弧数
} ALGraph;// 定义状态类型
typedef enum { ERROR, OK } Status;// 查找顶点u的位置,存在则返回顶点表中的下标;否则返回-1
int LocateVex(const ALGraph& G, VerTexType u) {for (int i = 0; i < G.vexnum; ++i) {if (u == G.vertices[i].data) {return i;}}return -1;
}// 创建有向图G
Status CreateDG(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; // 初始化表头结点的指针域}for (int k = 0; k < G.arcnum; ++k) {VerTexType v1, v2;cin >> v1 >> v2; // 输入一条边依附的两个顶点int i = LocateVex(G, v1);int j = LocateVex(G, v2);if (i == -1 || j == -1) return ERROR; // 如果顶点不存在,返回错误// 生成一个新的边结点*pArcNode* p = new ArcNode;p->adjvex = j;               // 设置邻接点序号为jp->nextarc = G.vertices[i].firstarc; // 将新结点*p插入顶点vi的边表头部G.vertices[i].firstarc = p;}return OK;
}// 打印图的信息
void PrintGraph(const ALGraph& G) {cout << "图的顶点: ";for (int i = 0; i < G.vexnum; ++i) {cout << G.vertices[i].data << ' ';}cout << '\n';cout << "邻接表:\n";for (int i = 0; i < G.vexnum; ++i) {cout << G.vertices[i].data << ": ";ArcNode* p = G.vertices[i].firstarc;while (p != NULL) {cout << G.vertices[p->adjvex].data << ' ';p = p->nextarc;}cout << '\n';}
}int main() {ALGraph G;// 模拟用户输入:3个顶点,3条边// 顶点:A B C// 边:A->B A->C B->Cstring input = "3 3\nA B C\nA B\nA C\nB C";istringstream mockInput(input);// 将mockInput绑定到cin,用于测试streambuf* prevCinBuf = cin.rdbuf(mockInput.rdbuf());if (CreateDG(G) == OK) {cout << "有向图创建成功。\n";PrintGraph(G);} else {cerr << "创建有向图时发生错误。\n";}// 恢复cin至原始状态cin.rdbuf(prevCinBuf);return 0;
}
5、运行结果

在这里插入图片描述

(4)邻接表的特点

在这里插入图片描述

3)邻接矩阵与邻接表关系

在这里插入图片描述

在这里插入图片描述

4)邻接表的改进

(1)十字链表

在这里插入图片描述

在这里插入图片描述

有向图的十字链表表示
#define MAX_VERTEX_NUM 20
// 定义宏MAX_VERTEX_NUM为20,表示图中最多可以有20个顶点。
// 这是图的最大容量,可以根据需要调整。typedef struct ArcBox
{int tailvex, headvex; // 分别存储边的尾顶点(出发顶点)和头顶点(到达顶点)的索引。struct ArcBox* hlink, * tlink; // hlink指向相同头顶点的下一条边;tlink指向相同尾顶点的下一条边。InfoType* info; // 指向额外信息的指针,如边的权重等。InfoType需要事先定义。
} ArcBox;
// 定义ArcBox结构体,用于表示图中的每一条边(弧)。该结构体包含边的两个端点以及指向入边链表和出边链表中下一个元素的指针。typedef struct VexNode {VertexType data; // 存储顶点的数据,VertexType应该根据实际需求定义。ArcBox* firstin, * firstout; // firstin指向第一个进入此顶点的边;firstout指向从这个顶点出发的第一条边。
} VexNode;
// 定义VexNode结构体,用于表示图中的每一个顶点。该结构体包括顶点数据和指向第一条入边和出边的指针。typedef struct {VexNode xlist[MAX_VERTEX_NUM]; // 定义一个大小为MAX_VERTEX_NUM的VexNode数组,用于存储所有顶点。int vexnum, arcnum; // vexnum存储当前图中顶点的数量,arcnum存储边(弧)的数量。
} OLGraph;
// 定义OLGraph结构体,用于表示整个十字链表形式的有向图。它包含了一个VexNode类型的数组xlist,用以存储图的所有顶点,以及记录顶点数和边数的整型变量。

(2)邻接多重表

在这里插入图片描述

无向图的邻接多重表表示

四、图的遍历

图的特点:

图中可能存在回路,且图的任一顶点都可能与其它顶点相通,在访问完某个顶点之后可能会沿着某些边又回到了曾经访问过的顶点。

怎样避免重复访问?

解决思路:设置辅助数组 visited [n ],用来标记每个被访问过的顶点。

初始状态 visited [i]为0

顶点i被访问,改 visited [i]为1,防止被多次访问

图常用的遍历

深度优先搜索(Depth_First Search——DFS)

广度优先搜索(Breadth_Frist Search——BFS)

1)深度优先遍历DFS

1、介绍

一条路走到黑

在这里插入图片描述

方法:

  • 在访问图中某一起始顶点v后,由v出发,访问它的任一邻接顶点 w
  • 再从 w1 出发,访问与 w1 邻接但还未被访问过的顶点 w2
  • 然后再从 w2 出发,进行类似的访问…
  • 如此进行下去,直至到达所有的邻接顶点都被访问过的顶点 u 为止
  • 接着,退回一步,退到前一次刚访问过的顶点,看是否还有其它没有被访问的邻接顶点
  • 如果有,则访问此顶点,之后再从此顶点出发,进行与前述类似的访问
  • 如果没有,就再退回一步进行搜索。重复上述过程,直到连通图中所有顶点都被访问过为止

在这里插入图片描述

2、算法实现

采用邻接矩阵表示图的深度优先搜索遍历
伪代码:
void DFS(AMGraphG, int v) {// 图G为邻接矩阵类型cout << v;visited[v] = true;    // 访问第v个顶点for (w = 0; w < G.vexnum; w++) {if ((G.arcs[v][w] != 0) && (!visited[w])) {DFS(G, w); //依次检查邻接矩阵v所在的行}}//w是v的邻接点,如果w未访问,则递归调用DFS
}
总代码:
#include <iostream>
using namespace std;// 定义图的最大顶点数量
const int MAX_VERTEX_NUM = 100;// 定义邻接矩阵图的结构体
typedef struct {int arcs[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 假设无权图,用0表示没有边,1表示有边int vexnum; // 图中当前顶点个数
} AMGraph;// 全局visited数组,用来记录顶点是否被访问过
bool visited[MAX_VERTEX_NUM];// 深度优先搜索函数
void DFS(AMGraph G, int v) {cout << v << " "; // 输出顶点编号visited[v] = true; // 标记该顶点已被访问for (int w = 0; w < G.vexnum; ++w) { // 遍历与v相邻的所有顶点if (G.arcs[v][w] != 0 && !visited[w]) { // 如果存在边且顶点w未被访问DFS(G, w); // 递归调用DFS访问顶点w}}
}// DFS遍历整个图的辅助函数
void DFSTraverse(AMGraph G) {// 初始化所有顶点为未访问状态for (int i = 0; i < G.vexnum; ++i) {visited[i] = false;}// 可能存在不连通的顶点,因此对每个顶点尝试发起DFSfor (int i = 0; i < G.vexnum; ++i) {if (!visited[i]) { // 如果顶点i未被访问,则从i开始DFSDFS(G, i);}}
}int main() {// 创建一个有5个顶点的图AMGraph G;G.vexnum = 5;// 初始化邻接矩阵为0,表示没有边for (int i = 0; i < MAX_VERTEX_NUM; ++i) {for (int j = 0; j < MAX_VERTEX_NUM; ++j) {G.arcs[i][j] = 0;}}// 添加边到图中,构建一个简单的连通图// 注意:因为是无向图,所以每添加一条边时,需要同时设置两个方向G.arcs[0][1] = G.arcs[1][0] = 1; // 边(0,1)G.arcs[0][2] = G.arcs[2][0] = 1; // 边(0,2)G.arcs[1][3] = G.arcs[3][1] = 1; // 边(1,3)G.arcs[2][4] = G.arcs[4][2] = 1; // 边(2,4)// 输出图的信息cout << "图的邻接矩阵是:" << endl;for (int i = 0; i < G.vexnum; ++i) {for (int j = 0; j < G.vexnum; ++j) {cout << G.arcs[i][j] << " ";}cout << endl;}// 执行深度优先搜索遍历cout << "从顶点0开始的DFS遍历:" << endl;DFSTraverse(G);return 0;
}
运行结果:

在这里插入图片描述

采用邻接表表示图的深度优先搜索遍历
伪代码:
void DES_AL(ALGraph G, int v) {// 图G为邻接表类型,从第ν个顶点出发深度优先搜索遍历图Gcout << v;visited[v] = true;//  访问第ν个顶点,并置访问标志数组相应分量值为truep = G.vertices[v].firstarc; // p指向ν的边链表的第一个边节点while (p != NULL) { // 边节点非空w = p->adjvex;  // 表示w是v的邻接点if (!visited[w]) {DFS_AL(G, w); //如果w未访问,则递归调用DES_AL()}p = p->nextarc; // p指向下一个边节点}
}
总代码:
#include <iostream>
using namespace std;// 定义图的最大顶点数量
const int MAX_VERTEX_NUM = 100;// 定义边节点结构体
typedef struct ArcNode {int adjvex; // 该弧所指向的顶点的位置ArcNode* nextarc; // 指向下一条弧的指针
} ArcNode;// 定义顶点结构体
typedef struct VNode {char data; // 顶点信息(这里用字符表示)ArcNode* firstarc; // 指向第一条依附该顶点的弧的指针
} VNode, AdjList[MAX_VERTEX_NUM];// 定义邻接表图结构体
typedef struct {AdjList vertices;int vexnum; // 图中当前顶点个数
} ALGraph;// 全局visited数组,用来记录顶点是否被访问过
bool visited[MAX_VERTEX_NUM];// 创建新的边节点
ArcNode* CreateArcNode(int adjvex) {ArcNode* newNode = new ArcNode();newNode->adjvex = adjvex;newNode->nextarc = NULL;return newNode;
}// 添加边到图中
void AddEdge(ALGraph& G, int v1, int v2) {// 添加从v1到v2的边ArcNode* p = CreateArcNode(v2);p->nextarc = G.vertices[v1].firstarc;G.vertices[v1].firstarc = p;// 因为是无向图,所以也需要添加从v2到v1的边p = CreateArcNode(v1);p->nextarc = G.vertices[v2].firstarc;G.vertices[v2].firstarc = p;
}// 深度优先搜索函数
void DFS_AL(ALGraph G, int v) {cout << v << " "; // 输出顶点编号visited[v] = true; // 标记该顶点已被访问ArcNode* p = G.vertices[v].firstarc; // p指向v的第一个边节点while (p != NULL) { // 遍历与v相邻的所有顶点int w = p->adjvex; // 获取v的邻接点wif (!visited[w]) { // 如果顶点w未被访问,则递归调用DFS_ALDFS_AL(G, w);}p = p->nextarc; // 移动到下一个边节点}
}// DFS遍历整个图的辅助函数
void DFSTraverse(ALGraph G) {// 初始化所有顶点为未访问状态for (int i = 0; i < G.vexnum; ++i) {visited[i] = false;}// 可能存在不连通的顶点,因此对每个顶点尝试发起DFSfor (int i = 0; i < G.vexnum; ++i) {if (!visited[i]) { // 如果顶点i未被访问,则从i开始DFSDFS_AL(G, i);}}
}// 示例主函数
int main() {// 创建一个有5个顶点的图ALGraph G;G.vexnum = 5;// 初始化顶点数据(可以省略这一步,如果不需要显示顶点信息)for (int i = 0; i < G.vexnum; ++i) {G.vertices[i].data = 'A' + i;G.vertices[i].firstarc = NULL; // 初始化为空}// 添加边到图中,构建一个简单的连通图AddEdge(G, 0, 1); // 边(0,1)AddEdge(G, 0, 2); // 边(0,2)AddEdge(G, 1, 3); // 边(1,3)AddEdge(G, 2, 4); // 边(2,4)// 执行深度优先搜索遍历cout << "从顶点0开始的DFS遍历:" << endl;DFSTraverse(G);// 清理动态分配的内存(避免内存泄漏)for (int i = 0; i < G.vexnum; ++i) {ArcNode* current = G.vertices[i].firstarc;while (current != NULL) {ArcNode* next = current->nextarc;delete current;current = next;}}return 0;
}
运行结果:

在这里插入图片描述

邻接矩阵来表示图,遍历图中每一个顶点都要从头扫描该顶m行,时间复杂度为O(n²),

邻接表来表示图,虽然有 2e个表结点,但只需扫描e个结点即可完成遍历,加上访问 n个头结点的时间,时间复杂度为O(n+e)。

结论:

  • 稠密图适于在邻接矩阵上进行深度遍历。
  • 稀疏图适于在邻接表上进行深度遍历。

2)广度优先搜索BFS

优先访问各个结点的邻接点,用队列实现

1、介绍

在这里插入图片描述

2、算法实现

在这里插入图片描述

伪代码:
void BFS(Graph G, int v) {//按广度优先非递归遍历连通图Gcout << v;visited[v] = true;//访问第v个顶点InitQueue(Q);  //辅助队列Q初始化,置空EnQueue(Q, v); //v进队while (!QueueEmpty(Q)) { // 队列非空DeQueue(Q, u);       // 队头元素出队并置为ufor (w = FirstAdjVex(G, u); w >= 0; w = NextAdjVex(G, u, w)) if (!visited[w]) {cout << w; visited[w] = true; // w为u的尚未访问的邻接顶点EnQueue(Q, w);     // w进队}//if}//while
}//BFS
采用邻接矩阵表示图的广度优先搜索
#include <iostream>
#include <queue>
using namespace std;const int MAX_VERTEX_NUM = 100; // 假设图的最大顶点数
bool visited[MAX_VERTEX_NUM] = { false }; // 访问标记数组
int adjMatrix[MAX_VERTEX_NUM][MAX_VERTEX_NUM]; // 邻接矩阵// 查找顶点u的第一个邻接顶点
int FirstAdjVex(int u) {for (int v = 0; v < MAX_VERTEX_NUM; ++v) {if (adjMatrix[u][v]) return v;}return -1; // 没有邻接顶点返回-1
}// 查找顶点u相对于w的下一个邻接顶点
int NextAdjVex(int u, int w) {for (int v = w + 1; v < MAX_VERTEX_NUM; ++v) {if (adjMatrix[u][v]) return v;}return -1; // 没有下一个邻接顶点返回-1
}void BFS(int v) {queue<int> Q;cout << v << " ";visited[v] = true;Q.push(v);while (!Q.empty()) {int u = Q.front();Q.pop();for (int w = FirstAdjVex(u); w >= 0; w = NextAdjVex(u, w)) {if (!visited[w]) {cout << w << " ";visited[w] = true;Q.push(w);}}}
}int main() {// 初始化一个简单的无向图(例如:A-B-C-D)// 注意:这里假设顶点编号从0开始memset(adjMatrix, 0, sizeof(adjMatrix)); // 初始化邻接矩阵为0adjMatrix[0][1] = adjMatrix[1][0] = 1; // A-BadjMatrix[1][2] = adjMatrix[2][1] = 1; // B-CadjMatrix[2][3] = adjMatrix[3][2] = 1; // C-D// 调用BFS算法cout << "从顶点0开始的BFS遍历: ";BFS(0);cout << endl;return 0;
}
运行结果:

在这里插入图片描述

采用邻接表表示图的广度优先搜索遍历
#include <iostream>
#include <queue>
#include <vector>
using namespace std;const int MAX_VERTEX_NUM = 100; // 假设图的最大顶点数
bool visited[MAX_VERTEX_NUM] = { false }; // 访问标记数组
vector<int> adjList[MAX_VERTEX_NUM]; // 邻接表// 邻接表中查找顶点u的第一个邻接顶点
int FirstAdjVex(int u) {if (!adjList[u].empty()) return adjList[u][0];return -1; // 没有邻接顶点返回-1
}// 邻接表中查找顶点u相对于位置idx的下一个邻接顶点
int NextAdjVex(int u, int idx) {if (++idx < adjList[u].size()) return adjList[u][idx];return -1; // 没有下一个邻接顶点返回-1
}void BFS(int v) {queue<int> Q;cout << v << " ";visited[v] = true;Q.push(v);while (!Q.empty()) {int u = Q.front();Q.pop();int w = FirstAdjVex(u), idx = 0;while (w >= 0) {if (!visited[w]) {cout << w << " ";visited[w] = true;Q.push(w);}w = NextAdjVex(u, idx++);}}
}int main() {// 初始化一个简单的无向图(例如:A-B-C-D)// 注意:这里假设顶点编号从0开始adjList[0].push_back(1); // A-BadjList[1].push_back(0);adjList[1].push_back(2); // B-CadjList[2].push_back(1);adjList[2].push_back(3); // C-DadjList[3].push_back(2);// 调用BFS算法cout << "从顶点0开始的BFS遍历: ";BFS(0);cout << endl;return 0;
}
运行结果:

在这里插入图片描述

如果使用邻接矩阵,则BFS对于每一个被访问到的顶点,都要循环检测矩阵中的整整一行(n个元素),总的时间代价为O(n²)

邻接表来表示图,虽然有 2e 个表结点,但只需扫描e个结点即可完成遍历,加上访问 n个头结点的时间,时间复杂度为O(n+e)。

3)DFS与BFS算法效率比较

空间复杂度相同,都是O(n)(借用了堆栈或队列)

时间复杂度只与存储结构,(邻接矩阵或邻接表)有关,而与搜索路径无关

五、图的应用

1)最小生成树

在这里插入图片描述

但是含有 n 个顶点n - 1条边的图不一定是生成树

在这里插入图片描述

在这里插入图片描述

1、构造最小生成树

在这里插入图片描述

在这里插入图片描述

2、普利姆(Prim)算法

在这里插入图片描述

3、克鲁斯卡尔(Kruskal)算法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2)最短路径问题

1、两点间的最短路径问题

在这里插入图片描述

2、某源点到其他各点最短路径

在这里插入图片描述

3)两种常见的最短路径问题:

Diikstra(迪杰斯特拉)算法

单源最短路径

在这里插入图片描述

在这里插入图片描述
算法步骤:

在这里插入图片描述
在这里插入图片描述

Floyd(弗洛伊德) 算法

所有顶点间的最短路径

方法一:每次以一个顶点为源点,重复执行 Dijkstra 算法 n次。

方法二:弗洛伊德 (Floyd)算法

算法思想:

  • 逐个顶点试探
  • 从 vi 到 vj 的所有可能存在的路径中
  • 选出一条长度最短的路径

在这里插入图片描述

4)拓扑排序

有向无环图:无环的有向图,简称 DAG图(Directed Acycline Graph)

在这里插入图片描述

有向无环图常用来描述一个工程或系统的进行过程。(通常把计划、施工、生产、程序流程等当成是一个工程)。

一个工程可以分为若干个子工程,只要完成了这些子工程(活动)就可以导致整个工程的完成。

1、AOV 网和AOE 网

AOV 网:拓扑排序
用一个有向图表示一个工程的各子工程及其相互制约的关系,其中
以顶点表示活动
,弧表示活动之间的优先制约关系,称这种有向图为顶点表示活动的网,简称 AOV网(Activity On Vertex network)。

AOE 网:关键路径
用一个有向图表示一个工程的各子工程及其相互制约的关系,以
弧表示活动
,以顶点表示活动的开始或结束事件,称这种有向图为边表示活动的网,简称为AOE网(Activity On Edge)。

AOV网特点:

在这里插入图片描述

2、拓扑排序介绍

在 AOV 网没有回路的前提下,我们将全部活动排列成一个线性序列,使得若 AOV 网中有弧<i,j>存在,则在这个序列中,i 一定排在 j 的前面,具有这种性质的线性序列称为拓扑有序序列,相应的拓扑有序排序的算法称为拓扑排序

在这里插入图片描述

在这里插入图片描述

5)关键路径

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

相关文章:

C/C++ 数据结构与算法【图】 图+邻接矩阵+邻接表+DFS+BFS+最小生成树+最短路径+拓扑排序详细解析【日常学习,考研必备】带图+详细代码

一、图的定义 1&#xff09;无向图&#xff0c;有向图&#xff0c;完全图 2&#xff09;稀疏图&#xff0c;稠密图&#xff0c;网&#xff0c;邻接&#xff0c;关联 3&#xff09;度 4&#xff09;路径 5&#xff09;连通图 6&#xff09;权与网 7&#xff09;子图 8&#xff0…...

Linux实验报告7-文件管理

目录 一&#xff1a;实验目的 二&#xff1a;实验内容 (1)查看/etc/inittab文件的权限属性&#xff0c;并指出该文件的所有者以及文件所属组群。 (2)新建文件test&#xff0c;设置文件权限为r--r-----。 (3)新建文件test2&#xff0c;设系统中有用户study和用户组studygr…...

RJ45网口模块设计

1、以太网概述及RJ45实物 2、常用网口信号介绍 3、RJ45网口布局布线要点分析 4、总结 1、变压器下面需要进行挖空处理&#xff0c;以免底下的铜引入干扰&#xff0c;&#xff08;将多边形挖空区域的所在层设置为Multi-Layer多层&#xff09; 2、为了更直观的看一个类中线的长…...

电子电器架构 --- 智能座舱HUD技术革新

我是穿拖鞋的汉子&#xff0c;魔都中坚持长期主义的汽车电子工程师。 老规矩&#xff0c;分享一段喜欢的文字&#xff0c;避免自己成为高知识低文化的工程师&#xff1a; 所谓鸡汤&#xff0c;要么蛊惑你认命&#xff0c;要么怂恿你拼命&#xff0c;但都是回避问题的根源&…...

嵌入式开发中的机器人表情绘制

机器人的表情有两种&#xff0c;一种是贴图&#xff0c;一钟是调用图形API自绘。 贴图效果相对比较好&#xff0c;在存储空间大的情况下是可以采用的。 自绘比较麻烦&#xff0c;但在资源和空缺少的情况下&#xff0c;也是很有用的。而且自绘很容易通过调整参数加入随机效果&…...

orm01

静态文件处理 静态文件&#xff1a;如&#xff1a;图片、音频、视频、css、js等静态文件的相关配置也在 项目名/项目名/settings.py 文件中进行配置 - 配置静态文件的访问路径STATIC_URL- 功能&#xff1a;通过哪个 url 地址找静态文件- 默认配置&#xff1a;STATIC_URL /sta…...

Maven 测试和单元测试介绍

一、测试介绍 二、单元测试 1&#xff09;介绍 2&#xff09;快速入门 添加依赖 <dependencies><!-- junit依赖 --><dependency><groupId>org.junit.jupiter</groupId><artifactId>junit-jupiter</artifactId><version>5.9…...

Postman接口测试03|执行接口测试、全局变量和环境变量、接口关联、动态参数、断言

目录 七、Postman 1、安装 2、postman的界面介绍 八、Postman执行接口测试 1、请求页签 3、响应页签 九、Postman的环境变量和全局变量 1、创建环境变量和全局变量可以解决的问题 2、postman中的操作-全局变量 1️⃣手动设置 2️⃣代码设置 3️⃣界面获取 4️⃣代…...

UE5 丧尸类杂兵的简单AI

A、思路 1、关卡初始化时&#xff0c;自动产生随机巡逻点&#xff0c;小兵到达后&#xff0c;去另一个随机巡逻点。 2、加入视力&#xff0c;发现主角后&#xff0c;不再巡逻&#xff0c;而开始追击主角并攻击。条件循环。 3、加入听力。主角的奔跑与射击会产生噪音&#xf…...

Linux字符设备驱动开发的三种方式(分析+对比+示例)

文章目录 一. 字符设备的驱动方法二. 三种方法的对比三. 开发环境四. 代码示例1. 传统设备驱动模型2. 总线设备驱动模型3. 设备树驱动模型 五. 相关链接 一. 字符设备的驱动方法 字符设备驱动 是指在I/O传输过程中以字节流进行读写操作的设备。典型的如LCD、蜂鸣器、SPI、触摸屏…...

C++设计模式之行为型模式概述,它们的目的与特点

行为型设计模式需要解决的问题 行为型设计模式主要关注对象之间的责任分配和交互。它们解决的问题包括&#xff1a; 对象之间的通信&#xff1a;如何让对象之间高效地通信&#xff0c;同时保持松耦合。算法的封装与复用&#xff1a;如何将算法或行为封装起来&#xff0c;使其…...

把Huggingface下载的arrow数据集转化为json格式

Arrow2json 使用默认的Huggingface路径 以allenai/tulu-3-sft-mixture数据集为例。 使用load_dataset即可&#xff1a; from datasets import load_dataset# 加载数据集 dataset load_dataset("allenai/tulu-3-sft-mixture")# 指定保存路径 output_dir "~/…...

复习打卡大数据篇——Hadoop YARN

目录 &#xff11;.什么是yarn &#xff12;.yarn的三大角色 &#xff13;.任务&#xff08;MR&#xff09;提交到YARN运行流程 4. 调度器Scheduler 5.YARN HA 高可用 &#xff11;.什么是yarn YARN&#xff08;Yet Another Resource Negotiator&#xff09;是一个资源管…...

fpga系列 HDL:ModelSim显示模拟波形以及十进制格式数值

FPGA中使用数字滤波器时&#xff0c;可通过观察模拟波形更好地查看滤波效果。可以通过ModelSim中的波形格式设置来实现更直观的波形显示。 右键波形->Format-> Analog 效果 不同的数值格式显示&#xff1a;右键波形->Radix-> Decimal 效果 示例代码 ver…...

Linux 基本指令

目录 1.常见指令 1.1 ls指令 1.2 pwd指令 1.3 cd指令 1.4 touch指令 1.5 mkdir指令 1.6 rm和rmdir指令 1.7 man指令 1.8 cp指令 1.9 mv指令 ​编辑 1.10 cat指令 1.11 more指令 1.12 less指令 1.13 head指令 1.14.tail指令 1.15 时间相关的指令 1.16 cal…...

GO语言基础面试题

一、字符串和整型怎么相互转换 1、使用 strconv 包中的函数 FormatInt 、ParseInt 等进行转换 2、转换10进制的整形时&#xff0c;可以使用 strconv.Atoi、strconv.Itoa&#xff1a; Atoi是ParseInt(s, 10, 0) 的简写 Itoa是FormatInt(i, 10) 的简写 3、整形转为字符型时&#…...

要查询 `user` 表中 `we_chat_subscribe` 和 `we_chat_union_id` 列不为空的用户数量

文章目录 1、we_chat_subscribe2、we_chat_union_id 1、we_chat_subscribe 要查询 user 表中 we_chat_subscribe 列不为空的用户数量&#xff0c;你可以使用以下 SQL 查询语句&#xff1a; SELECT COUNT(*) FROM user WHERE we_chat_subscribe IS NOT NULL;解释&#xff1a; …...

小程序基础 —— 10 如何调试小程序代码

如何调试小程序代码 在进行项目开发的时候&#xff0c;不可避免需要进行调试&#xff0c;那么如何调试小程序呢&#xff1f; 打开微信开发者工具后&#xff0c;有一个模拟器&#xff0c;通过模拟器能够实时预览自己写的页面&#xff0c;如下&#xff1a; 在上部工具栏中有一个…...

Vue项目如何设置多个静态文件;如何自定义静态文件目录

Vite实现方案 安装插件 npm i vite-plugin-static-copy在vite.config.ts引入 import { viteStaticCopy } from vite-plugin-static-copy配置 plugins: [viteStaticCopy({targets: [{src: "要设置的静态文件目录的相对路径 相对于vite.config.ts的", dest: ./, // …...

CentOS Stream 9 安装 JDK

安装前检查 java --version注&#xff1a;此时说明已安装过JDK&#xff0c;否则为未安装。如若已安装过JDK可以跳过安装步骤直接使用&#xff0c;或者先卸载已安装的JDK版本重新安装。 安装JDK 官网下载地址&#xff1a;https://www.oracle.com/java/technologies/downloads…...

Spring Boot整合JWT实现认证与授权

概述 JSON Web Token (JWT) 是一种开放标准 (RFC 7519)&#xff0c;它定义了一种紧凑且自包含的方式&#xff0c;用于在各方之间安全地传输信息。在Web应用中&#xff0c;JWT常用于身份验证和信息交换。 依赖配置 首先需要在项目中添加JWT依赖&#xff1a; <!-- JWT依赖…...

HTML 计算网页的PPI

HTML 计算网页的PPI vscode上安装live server插件&#xff0c;可以实时看网页预览 有个疑问&#xff1a; 鸿蒙density是按照类别写死的吗&#xff0c;手机520dpi 折叠屏426dpi 平板360dpi <html lang"en" data - overlayscrollbars - initialize><header&…...

Drawio编辑器二次开发

‌ Drawio &#xff08;现更名为 Diagrams.net &#xff09;‌是一款完全免费的在线图表绘制工具&#xff0c;由 JGraph公司 开发。它支持创建多种类型的图表&#xff0c;包括流程图、组织结构图、UML图、网络拓扑图、思维导图等&#xff0c;适用于商务演示、软件设计等多种场景…...

钩子函数的作用(register_hook)

钩子函数仅在backward()时才会触发。其中&#xff0c;钩子函数接受梯度作为输入&#xff0c;返回操作后的梯度&#xff0c;操作后的梯度必须要输入的梯度同类型、同形状&#xff0c;否则报错。 主要功能包括&#xff1a; 监控当前的梯度&#xff08;不返回值&#xff09;&…...

微信小程序(uniapp)对接腾讯云IM

UniApp 对接腾讯云 IM&#xff08;即时通讯&#xff09;完整指南 一、项目背景与需求分析 随着社交场景的普及&#xff0c;即时通讯功能已成为移动应用的标配。腾讯云 IM&#xff08;Tencent IM&#xff0c;即 TIM&#xff09;提供稳定可靠的即时通讯服务&#xff0c;支持单聊…...

java helloWord java程序运行机制 用idea创建一个java项目 标识符 关键字 数据类型 字节

HelloWord public class Hello{public static void main(String[] args) {System.out.print("Hello,World!");} }java程序运行机制 用idea创建一个java项目 建立一个空项目 新建一个module 注释 标识符 关键字 标识符注意点 数据类型 public class Demo02 {public st…...

影响服务器稳定性的因素都有什么?

服务器的稳定性会影响到业务是否能够持续运行&#xff0c;用户在进行访问网站的过程中是否出现页面卡顿的情况&#xff0c;本文就来了解一下都是哪些因素影响着服务器的稳定性。 服务器中的硬件设备是保证服务器稳定运行的基础&#xff0c;企业选择高性能的处理器和大容量且高速…...

实验三 企业网络搭建及应用

实验三 企业网络搭建及应用 一、实验目的 1.掌握企业网络组建方法。 2.掌握企业网中常用网络技术配置方法。 二、实验描述 某企业设有销售部、市场部、技术部和财务部四个部门。公司内部网络使用二层交换机作为用户的接入设备。为了使网络更加稳定可靠&#xff0c;公司决定…...

推荐几个不错的AI入门学习视频

引言&#xff1a;昨天推荐了几本AI入门书&#xff08;AI入门书&#xff09;&#xff0c;反响还不错。今天&#xff0c;我再推荐几个不错的AI学习视频&#xff0c;希望对大家有帮助。 网上关于AI的学习视频特别多。有收费的&#xff0c;也有免费的。我今天只推荐免费的。 我们按…...

本地部署FreeGPT+内网穿透公网远程访问,搞定ChatGPT外网访问难题

‌FreeGPT‌是一个基于GPT 3.5/4的ChatGPT聊天网页用户界面&#xff0c;提供了一个开放的聊天界面&#xff0c;开箱即用‌。ChatGPT是非常热门的&#xff0c;但访问体验一直不太理想。为了解决这一问题&#xff0c;出现了各类方法和工具&#xff0c;其中FreeGPT是一款非常实用的…...