实验5:图
课程实验报告-实验5:图
| 项目名称 | 内容 |
|---|---|
| 课程名称 | 数据结构 |
| 班级 | 网安2511 |
| 实验项目名称 | 图 |
| 上机实践日期 | 2026.05.29 |
| 上机实践时间 | 2学时 |
一、目的(本次实验所涉及并要求掌握的知识点)
- 掌握图的深度优先遍历(DFS)、广度优先遍历(BFS)两种核心遍历算法,理解两种遍历的执行逻辑、适用场景与遍历顺序差异。
- 掌握最小生成树核心算法(Prim算法、Kruskal算法),理解最小生成树的定义、生成条件,能够解决公路村村通等实际工程场景问题。
- 掌握无向图、有向图邻接矩阵的存储结构原理,理解邻接矩阵存储图的优缺点。
二、实验内容与设计思想
题目1:图的创建
函数相关伪代码
Algorithm GraphADT
Input: 顶点数 n,边数 m;随后 m 条边 (u, v)
Output: 打印图的邻接矩阵
1. 定义常量 MAXN ← 100
2. 定义结构体 Graph
vertexNum // 顶点数
edgeNum // 边数
edge[MAXN][MAXN] // 邻接矩阵
3. 初始化图 InitGraph(Graph G)
G.vertexNum ← 0
G.edgeNum ← 0
for i ← 0 to MAXN - 1 do
for j ← 0 to MAXN - 1 do
G.edge[i][j] ← 0
4. 创建图 CreateGraph(Graph G)
读入 G.vertexNum, G.edgeNum
for k ← 0 to G.edgeNum - 1 do
读入 u, v
G.edge[u][v] ← 1
G.edge[v][u] ← 1
5. 打印图 PrintGraph(Graph G)
输出 "邻接矩阵:"
for i ← 0 to G.vertexNum - 1 do
for j ← 0 to G.vertexNum - 1 do
输出 G.edge[i][j]
换行
6. 主流程 Main
创建图 G
InitGraph(G)
CreateGraph(G)
PrintGraph(G)
函数代码
#include <iostream>
using namespace std;
const int MAXN = 100;
struct Graph {
int vertexNum; // 顶点数
int edgeNum; // 边数
int edge[MAXN][MAXN]; // 邻接矩阵
};
// 初始化图
void InitGraph(Graph &G) {
G.vertexNum = 0;
G.edgeNum = 0;
for (int i = 0; i < MAXN; i++) {
for (int j = 0; j < MAXN; j++) {
G.edge[i][j] = 0;
}
}
}
// 创建图
void CreateGraph(Graph &G) {
cin >> G.vertexNum >> G.edgeNum;
for (int k = 0; k < G.edgeNum; k++) {
int u, v;
cin >> u >> v;
G.edge[u][v] = 1;
G.edge[v][u] = 1; // 无向图
}
}
// 打印邻接矩阵
void PrintGraph(Graph &G) {
cout << "邻接矩阵:" << endl;
for (int i = 0; i < G.vertexNum; i++) {
for (int j = 0; j < G.vertexNum; j++) {
cout << G.edge[i][j] << " ";
}
cout << endl;
}
}
int main() {
Graph G;
InitGraph(G);
CreateGraph(G);
PrintGraph(G);
return 0;
}
题目2:图的遍历(DFS、BFS)
函数相关伪代码
Algorithm GraphTraversal
输入:顶点数 vNum,边数 eNum,以及 eNum 条边 (u, v)
输出:从顶点 0 开始的 DFS 与 BFS 遍历序列
1. 定义常量 MAXN ← 100
2. 定义图结构 Graph
vNum // 顶点数量
eNum // 边数量
edge[MAXN][MAXN] // 邻接矩阵
3. 初始化图 InitGraph(Graph G)
G.vNum ← 0
G.eNum ← 0
for i ← 0 to MAXN - 1 do
for j ← 0 to MAXN - 1 do
G.edge[i][j] ← 0
4. 创建无向图 CreateGraph(Graph G)
读入 G.vNum, G.eNum
for k ← 0 to G.eNum - 1 do
读入 u, v
G.edge[u][v] ← 1
G.edge[v][u] ← 1
5. 访问标记数组 visited[MAXN]
6. 清空访问标记 ClearVisit()
for i ← 0 to MAXN - 1 do
visited[i] ← false
7. 深度优先遍历 DFS(Graph G, int v)
输出 v
visited[v] ← true
for j ← 0 to G.vNum - 1 do
if G.edge[v][j] == 1 and visited[j] == false then
DFS(G, j)
8. 广度优先遍历 BFS(Graph G, int v)
创建空队列 Q
输出 v
visited[v] ← true
Q.push(v)
while Q 非空 do
cur ← Q.front()
Q.pop()
for j ← 0 to G.vNum - 1 do
if G.edge[cur][j] == 1 and visited[j] == false then
输出 j
visited[j] ← true
Q.push(j)
9. 主流程 Main
创建图 G
InitGraph(G)
CreateGraph(G)
输出 "DFS遍历序列:"
ClearVisit()
DFS(G, 0)
输出 "BFS遍历序列:"
ClearVisit()
BFS(G, 0)
函数代码
#include <iostream>
#include <queue>
using namespace std;
const int MAXN = 100;
struct Graph {
int vNum; // 顶点数
int eNum; // 边数
int edge[MAXN][MAXN]; // 邻接矩阵
};
bool visited[MAXN];
// 初始化图
void InitGraph(Graph &G) {
G.vNum = 0;
G.eNum = 0;
for (int i = 0; i < MAXN; i++)
for (int j = 0; j < MAXN; j++)
G.edge[i][j] = 0;
}
// 创建无向图
void CreateGraph(Graph &G) {
cin >> G.vNum >> G.eNum;
for (int k = 0; k < G.eNum; k++) {
int u, v;
cin >> u >> v;
G.edge[u][v] = 1;
G.edge[v][u] = 1;
}
}
// 清空访问标记
void ClearVisit() {
for (int i = 0; i < MAXN; i++)
visited[i] = false;
}
// 深度优先遍历 DFS
void DFS(Graph &G, int v) {
cout << v << " ";
visited[v] = true;
for (int j = 0; j < G.vNum; j++) {
if (G.edge[v][j] == 1 && !visited[j]) {
DFS(G, j);
}
}
}
// 广度优先遍历 BFS
void BFS(Graph &G, int v) {
queue<int> Q;
cout << v << " ";
visited[v] = true;
Q.push(v);
while (!Q.empty()) {
int cur = Q.front();
Q.pop();
for (int j = 0; j < G.vNum; j++) {
if (G.edge[cur][j] == 1 && !visited[j]) {
cout << j << " ";
visited[j] = true;
Q.push(j);
}
}
}
}
int main() {
Graph G;
InitGraph(G);
CreateGraph(G);
cout << "DFS遍历序列:" << endl;
ClearVisit();
DFS(G, 0);
cout << endl;
cout << "BFS遍历序列:" << endl;
ClearVisit();
BFS(G, 0);
cout << endl;
return 0;
}
题目3:PTA图着色问题
函数相关伪代码
输入 V, E, K
创建边集合 edges[E]
for i = 1 to E do
输入 u, v
edges[i] = (u, v)
输入 N
for 每个方案 in 1..N do
创建数组 color[1..V]
创建空集合 usedColors
for i = 1 to V do
输入 color[i]
将 color[i] 加入 usedColors
if size(usedColors) ≠ K then
输出 "No"
continue
end if
flag ← true
for 每条边 (u, v) in edges do
if color[u] == color[v] then
flag ← false
break
end if
end for
if flag == true then
输出 "Yes"
else
输出 "No"
end if
end for
函数代码
#include <iostream>
#include <vector>
#include <set>
using namespace std;
int main() {
int V, E, K;
cin >> V >> E >> K;
vector<pair<int, int>> edges(E);
for (int i = 0; i < E; i++) {
cin >> edges[i].first >> edges[i].second;
}
int N;
cin >> N;
while (N--) {
vector<int> color(V + 1);
set<int> colorsUsed;
for (int i = 1; i <= V; i++) {
cin >> color[i];
colorsUsed.insert(color[i]);
}
// 颜色种类必须正好等于 K
if ((int)colorsUsed.size() != K) {
cout << "No\n";
continue;
}
bool valid = true;
for (auto &e : edges) {
if (color[e.first] == color[e.second]) {
valid = false;
break;
}
}
cout << (valid ? "Yes" : "No") << "\n";
}
return 0;
}
题目4:公路村村通
函数相关伪代码
Algorithm KruskalMST
Input: 顶点数 n,边数 m,边集合 (u, v, w)
Output: 最小生成树的总权值,若不存在则输出 -1
1. 定义边结构 Edge(u, v, w)
2. 定义并查集数组 parent[MAX]
3. 初始化并查集 Init()
for i ← 1 to n do
parent[i] ← i
4. 查找根节点 Find(x)
if parent[x] ≠ x then
parent[x] ← Find(parent[x])
return parent[x]
5. 对边按权值升序排序 Sort(edge[0…m-1])
(此处使用冒泡排序)
for i ← 0 to m-2 do
for j ← 0 to m-i-2 do
if edge[j].w > edge[j+1].w then
交换 edge[j] 与 edge[j+1]
6. 构造最小生成树
sum ← 0 // 总权值
count ← 0 // 已选边数
for i ← 0 to m-1 do
fu ← Find(edge[i].u)
fv ← Find(edge[i].v)
if fu ≠ fv then
parent[fu] ← fv
sum ← sum + edge[i].w
count ← count + 1
if count == n - 1 then
break
7. 输出结果
if count == n - 1 then
输出 sum
else
输出 -1
函数代码
#include <iostream>
using namespace std;
const int MAXN = 110;
// 边结构
struct Edge {
int u, v, w;
};
Edge edges[MAXN];
int parent[MAXN];
int n, m;
// 初始化并查集
void Init() {
for (int i = 1; i <= n; i++) {
parent[i] = i;
}
}
// 查找根节点(路径压缩)
int Find(int x) {
if (parent[x] != x) {
parent[x] = Find(parent[x]);
}
return parent[x];
}
// 冒泡排序:按边权升序
void Sort() {
for (int i = 0; i < m - 1; i++) {
for (int j = 0; j < m - i - 1; j++) {
if (edges[j].w > edges[j + 1].w) {
Edge temp = edges[j];
edges[j] = edges[j + 1];
edges[j + 1] = temp;
}
}
}
}
int main() {
cin >> n >> m;
for (int i = 0; i < m; i++) {
cin >> edges[i].u >> edges[i].v >> edges[i].w;
}
Init();
Sort();
int sum = 0; // 总权值
int cnt = 0; // 已选边数
for (int i = 0; i < m; i++) {
int fu = Find(edges[i].u);
int fv = Find(edges[i].v);
if (fu != fv) {
parent[fu] = fv;
sum += edges[i].w;
cnt++;
if (cnt == n - 1) break;
}
}
if (cnt == n - 1)
cout << sum << endl;
else
cout << -1 << endl;
return 0;
}
三、实验使用环境(本次实验所使用的平台和相关软件)
- 操作系统:Windows 11 专业版
- 编程语言:C++
- 开发工具:Visual Studio 2022
- 编译器:MSVC(Microsoft Visual C++ Compiler)
四、实验步骤和调试过程(实验步骤、测试数据设计、测试结果分析)
题目1:图的创建
本机运行截图

题目2:图的遍历(DFS、BFS)
本机运行截图

题目3:PTA图着色问题
PTA提交截图

题目4:公路村村通
本机运行截图

AI对话截图


五、实验小结(实验中遇到的问题及解决过程、实验体会和收获)
遇到的问题及解决方法:
- 问题:邻接矩阵输入时顶点下标越界
- 解决方法:增加输入合法性检查,限制顶点编号在 0 ~ n-1范围内,避免非法内存访问
- 问题:BFS遍历时出现死循环
- 解决方法:忘记将节点加入队列前标记为已访问,导致同一节点重复入队,补充队列操作前的 visited标记逻辑
实验体会和收获:
- 熟练掌握了图的基本存储结构(邻接矩阵)与经典算法(DFS、BFS、Kruskal)的代码实现流程。
- 深入理解了图结构的非线性特征,相比线性表,图的操作必须时刻关注顶点间的拓扑关系与连通性约束。
- 学会了合理利用AI辅助写代码,但是必须自主调试修改,不能直接复制使用。
浙公网安备 33010602011771号