集美大学课程实验报告-实验5:图
| 项目名称 | 内容 |
|---|---|
| 课程名称 | 数据结构 |
| 班级 | 网安2411 |
| 指导教师 | 郑如滨 |
| 学生姓名 | 马梦佳 |
| 学号 | 202321514092 |
| 实验项目名称 | 实验5:图 |
| 上机实践日期 | 2025年5月15日 |
| 上机实践时间 | 4学时 |
一、目的(本次实验所涉及并要求掌握的知识点)
-学会创建图(邻接矩阵),掌握在图上的基本操作。
-掌握图遍历算法:DFS与BFS。
-掌握编写最小生成树算法。
二、实验内容与设计思想
题目1:图的创建
主要内容:创建有向带权图(邻接矩阵)。
函数相关伪代码
结构体 BiTNode
数据成员
VexType vexs[MAXVEXNUM]; // 点的集合
ArcCell arcs[MAXVEXNUM][MAXVEXNUM]; // 边的集合
int vexNum, arcNum;
函数 CreateGraph(vexNum, arcNum):
创建图结构 g
设置 g 的顶点数为 vexNum
设置 g 的边数为 arcNum
输入:读取 vexNum 个字符,存入 g.vexs(顶点数组)
初始化邻接矩阵 g.arcs 为 0(全零矩阵)
for 对于每一条边 (arcNum 次):
读取两个顶点 x 和 y
读取边的权值 e
计算 i = x - 'a',j = y - 'a'
设置 g.arcs[i][j] = e
设置 g.arcs[j][i] = e //无向图
返回图 g
函数 printGraph(g):
输出图的顶点数和边数
for 对于每一个顶点 i:
输出顶点 g.vexs[i]
输出该顶点在邻接矩阵中对应的整行数值:
for 遍历 j 从 0 到 vexNum - 1
输出 g.arcs[i][j]
换行
主函数 main:
输入顶点数 vexNum 和边数 arcNum
调用 CreateGraph 创建图 g
调用 printGraph 打印图的信息
函数代码
#include <iostream>
using namespace std;
#define MAXVEXNUM 100
// 点,边
typedef int ArcCell;
typedef char VexType;
typedef struct {
VexType vexs[MAXVEXNUM]; // 点的集合
ArcCell arcs[MAXVEXNUM][MAXVEXNUM]; // 边的集合
int vexNum, arcNum;
}MyGraph;
MyGraph* CreateGraph(int vexNum, int arcNum)
{
//1.初始化点的数、边数
MyGraph* g = new MyGraph;
g->vexNum = vexNum;
g->arcNum = arcNum;
//2.初始化点列表(通过cin)
memset(g->vexs, 0, sizeof(g->vexs));
for (int i = 0; i < vexNum; i++)
{
cin >> g->vexs[i];
}
//3.初始化边矩阵
memset(g->arcs, 0, sizeof(g->arcs));
char x = 0, y = 0;
int e = 0;
for (int i = 0; i < arcNum; i++)
{
cin >> x >> y;
cin >> e;
g->arcs[x - 'a'][y - 'a'] = e;
g->arcs[y - 'a'][x - 'a'] = e;
}
return g;
}
void printGraph(MyGraph* g)
{
cout << "图的点数:" << g->vexNum << ",边数:" << g->arcNum << endl;
for (int i = 0; i < g->vexNum; i++)
{
//打印点
cout << g->vexs[i] << " ";
//打印该点所在的行
for (int j = 0; j < g->vexNum; j++)
{
cout << g->arcs[i][j] << " ";
}
cout << endl;
}
}
int main()
{
int vexNum, arcNum;
cin >> vexNum >> arcNum;
MyGraph* g = CreateGraph(vexNum, arcNum);
printGraph(g);
}
时间复杂度与空间复杂度
- 时间复杂度:O(n^2)
- 空间复杂度:O(n^2)
题目2:图的遍历
主要内容:利用DFS的方法遍历连通图。
函数相关伪代码
结构体 MyGraph
数据成员:
VexType vexs[MAXVEXNUM] // 点的集合
ArcCell arcs[MAXVEXNUM][MAXVEXNUM] // 边的集合
int vexNum // 顶点数
int arcNum // 边数
函数 CreateGraph(vexNum, arcNum)
输入:
vexNum:顶点数
arcNum:边数
输出:
返回一个图结构 g
创建图结构 g
设置 g.vexNum = vexNum
设置 g.arcNum = arcNum
初始化顶点数组 g.vexs
for i = 0 to vexNum - 1
输入 g.vexs[i]
初始化邻接矩阵 g.arcs 为全零矩阵
for i = 0 to arcNum - 1
输入两个顶点 x 和 y
输入边的权值 e
设置 g.arcs[x - 'a'][y - 'a'] = e
返回图 g
函数 printGraph(g)
输入:
g:图结构
输出:
打印图的顶点数、边数以及邻接矩阵
输出 "图的点数:" g.vexNum ",边数:" g.arcNum
for i = 0 to g.vexNum - 1
输出顶点 g.vexs[i]
for j = 0 to g.vexNum - 1
输出 g.arcs[i][j]
换行
函数 DFS(g, v, visited)
输入:
g:图结构
v:当前顶点
visited:访问标记数组
输出:
深度优先搜索遍历结果
输出顶点 g.vexs[v]
设置 visited[v] = true
for i = 0 to g.vexNum - 1
if g.arcs[v][i] != 0 and not visited[i]
调用 DFS(g, i, visited)
主函数 main
输入:
vexNum:顶点数
arcNum:边数
输出:
图的结构和DFS遍历结果
输入 vexNum 和 arcNum
调用 CreateGraph 创建图 g
调用 printGraph 打印图的信息
初始化 visited 数组为 false
for i = 0 to g.vexNum - 1
if not visited[i]
调用 DFS(g, i, visited)
函数代码
#include <iostream>
using namespace std;
#define MAXVEXNUM 100
// 点,边
typedef int ArcCell;
typedef char VexType;
typedef struct {
VexType vexs[MAXVEXNUM]; // 点的集合
ArcCell arcs[MAXVEXNUM][MAXVEXNUM]; // 边的集合
int vexNum, arcNum;
}MyGraph;
MyGraph* CreateGraph(int vexNum, int arcNum)
{
//1.初始化点的数、边数
MyGraph* g = new MyGraph;
g->vexNum = vexNum;
g->arcNum = arcNum;
//2.初始化点列表(通过cin)
memset(g->vexs, 0, sizeof(g->vexs));
for (int i = 0; i < vexNum; i++)
{
cin >> g->vexs[i];
}
//3.初始化边矩阵
memset(g->arcs, 0, sizeof(g->arcs));
char x = 0, y = 0;
int e = 0;
for (int i = 0; i < arcNum; i++)
{
cin >> x >> y;
cin >> e;
g->arcs[x - 'a'][y - 'a'] = e;
}
return g;
}
void printGraph(MyGraph* g)
{
cout << "图的点数:" << g->vexNum << ",边数:" << g->arcNum << endl;
for (int i = 0; i < g->vexNum; i++)
{
//打印点
cout << g->vexs[i] << " ";
//打印该点所在的行
for (int j = 0; j < g->vexNum; j++)
{
cout << g->arcs[i][j] << " ";
}
cout << endl;
}
}
// 深度优先搜索算法
void DFS(MyGraph* G, int v, bool visited[]) {
// 1.访问顶点v
cout << G->vexs[v] << " ";
visited[v] = true; // 设置该顶点为已访问
// 2.对v的所有未被访问的邻接点进行DFS
for (int i = 0; i < G->vexNum; i++) {
if ((G->arcs[v][i] != 0) && (!visited[i])) { // 如果存在边且邻接点未被访问
DFS(G, i, visited); // 递归访问该邻接点
}
}
}
int main()
{
int vexNum, arcNum;
cin >> vexNum >> arcNum;
MyGraph* g = CreateGraph(vexNum, arcNum);
printGraph(g);
// 初始化visited数组为false
bool visited[MAXVEXNUM];
for (int i = 0; i < g->vexNum; ++i) {
visited[i] = false;
}
cout << "DFS遍历结果: \n";
for (int i = 0; i < g->vexNum; ++i) {
if (!visited[i]) { // 如果该顶点没有被访问过
DFS(g, i, visited);
}
}
delete g; // 释放动态分配的内存
return 0;
}
时间复杂度与空间复杂度
- 时间复杂度:O(n^2)
- 空间复杂度:O(n^2)
题目3:(PTA编程)图着色问题
主要内容:图着色问题是一个著名的NP完全问题。给定无向图G=(V,E),问可否用K种颜色为V中的每一个顶点分配一种颜色,使得不会有两个相邻顶点具有同一种颜色?但本题并不是要你解决这个着色问题,而是对给定的一种颜色分配,请你判断这是否是图着色问题的一个解。
函数相关伪代码
函数 main()
输入:
V:顶点数
E:边数
K:颜色数
输出:
对每个测试用例输出是否为有效的K着色
读取 V, E, K
// 存储图的边信息
创建边数组 edges,大小为 E
for i = 0 to E-1
读取边的两个端点 u, v
edges[i] = (u, v)
读取测试用例数量 N
for 每个测试用例 i = 0 to N-1
创建颜色数组 colors,大小为 V+1(顶点编号从1开始)
创建颜色集合 colorSet
初始化 isValid 为 true
// 读取颜色分配
for 每个顶点 j = 1 to V
读取顶点 j 的颜色存入 colors[j]
将 colors[j] 加入 colorSet
// 检查颜色数量是否为K
if colorSet 的大小不等于 K
isValid = false
else
// 检查相邻顶点是否颜色不同
for 每条边 (u, v) in edges
if colors[u] 等于 colors[v]
isValid = false
跳出循环
输出 (isValid ? "Yes" : "No")
函数代码
#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++) {
int u, v;
cin >> u >> v;
edges[i] = { u, v };
}
int N;
cin >> N;
for (int i = 0; i < N; i++) {
vector<int> colors(V + 1); // 顶点编号从1开始
set<int> colorSet;
bool isValid = true;
// 读取颜色分配
for (int j = 1; j <= V; j++) {
cin >> colors[j];
colorSet.insert(colors[j]);
}
// 检查颜色数量是否为K
if (colorSet.size() != K) {
isValid = false;
}
else {
// 检查相邻顶点是否颜色不同
for (const auto& edge : edges) {
int u = edge.first;
int v = edge.second;
if (colors[u] == colors[v]) {
isValid = false;
break;
}
}
}
cout << (isValid ? "Yes" : "No") << endl;
}
return 0;
}
时间复杂度与空间复杂度
- 时间复杂度:O(E+N×(VlogV+E))
- 空间复杂度:O(E+V)
题目4:(PTA编程)公路村村通(最小生成树)
主要内容:现有村落间道路的统计数据表中,列出了有可能建设成标准公路的若干条道路的成本,求使每个村落都有公路连通所需要的最低成本。
函数相关伪代码
结构体 Edge
成员:
u: 边的起点
v: 边的终点
cost: 边的权重
函数 find(x, parent)
输入:
x: 当前节点
parent: 并查集父节点数组
输出:
x所在集合的根节点
if parent[x] != x then
parent[x] = find(parent[x], parent) // 路径压缩
return parent[x]
函数 unite(x, y, parent)
输入:
x, y: 待合并的两个节点
parent: 并查集父节点数组
输出:
合并成功返回true,失败返回false
rootX = find(x, parent)
rootY = find(y, parent)
if rootX == rootY then
return false // 已在同一集合
parent[rootX] = rootY
return true
函数 main()
输入:
n: 顶点数
m: 边数
输出:
最小生成树的总权值,若不存在则输出-1
读取 n 和 m
创建边数组 edges,大小为 m
for i = 0 to m-1 do
读取边的起点u、终点v和权值cost
edges[i] = (u, v, cost)
// 按边权升序排序
对 edges 按 cost 字段升序排序
// 初始化并查集
创建父节点数组 parent,大小为 n+1
for i = 1 to n do
parent[i] = i
totalCost = 0 // 最小生成树总权值
edgeCount = 0 // 已选边数
// Kruskal算法核心
for 每条边 e in edges do
if unite(e.u, e.v, parent) then // 若加入当前边不形成环
totalCost += e.cost
edgeCount += 1
if edgeCount == n-1 then // 已选够n-1条边
break
if edgeCount == n-1 then
输出 totalCost
else
输出 -1
函数代码
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
// 定义边结构体
struct Edge {
int u, v, cost;
};
// 并查集查找根节点
int find(int x, vector<int>& parent) {
if (parent[x] != x)
parent[x] = find(parent[x], parent); // 路径压缩
return parent[x];
}
// 并查集合并
bool unite(int x, int y, vector<int>& parent) {
int rootX = find(x, parent);
int rootY = find(y, parent);
if (rootX == rootY)
return false; // 已经在一个集合里
parent[rootX] = rootY;
return true;
}
int main() {
int n, m;
cin >> n >> m;
vector<Edge> edges(m);
for (int i = 0; i < m; ++i) {
cin >> edges[i].u >> edges[i].v >> edges[i].cost;
}
// 按照成本升序排序
sort(edges.begin(), edges.end(), [](const Edge& a, const Edge& b) {
return a.cost < b.cost;
});
// 初始化并查集
vector<int> parent(n + 1);
for (int i = 1; i <= n; ++i)
parent[i] = i;
int totalCost = 0;
int edgeCount = 0;
for (const auto& e : edges) {
if (unite(e.u, e.v, parent)) {
totalCost += e.cost;
++edgeCount;
if (edgeCount == n - 1)
break;
}
}
if (edgeCount == n - 1)
cout << totalCost << endl;
else
cout << -1 << endl;
return 0;
}
时间复杂度与空间复杂度
- 时间复杂度:O(nlogn)
- 空间复杂度:O(n^2logn)
三、实验使用环境(本次实验所使用的平台和相关软件)
- 操作系统:Windows 11
- 编程语言:C++
- 开发工具:Visual Studio 2022
- 编译器:C/C++ 17.13
四、实验步骤和调试过程(实验步骤、测试数据设计、测试结果分析)
题目1:图的创建
本机运行截图

题目2:图的遍历
本机运行截图

题目3:(PTA编程)图着色问题
本机运行截图

PTA提交截图

题目4:(PTA编程)公路村村通(最小生成树)
本机运行截图

PTA提交截图

五、实验小结(实验中遇到的问题及解决过程、实验体会和收获)
遇到的问题及解决方法:
- 问题:在创建图的代码中,邻接矩阵的初始化部分存在错误,导致邻接矩阵的值没有正确赋值。
- 解决方法:仔细检查了代码逻辑,发现是输入边的权重时,没有正确地将字符转换为索引。通过调整x - 'a'和y - 'a'的计算方式,确保了邻接矩阵的正确赋值。
- 问题:在判断颜色分配是否有效时,没有正确检查相邻顶点的颜色是否相同,导致程序输出错误结果。
- 解决方法:重新审视了图着色问题的定义,明确需要检查每条边的两个端点颜色是否不同。通过遍历边数组edges,逐一比较相邻顶点的颜色,确保逻辑正确。
实验体会和收获:
- 通过这次实验,掌握了在图上的基本操作:图的创建和图的遍历,掌握了图遍历算法:DFS与BFS,掌握来如何编写最小生成树算法。
- 在实验过程中,我学会了在处理字符和索引转换时,需要格外注意细节,避免因小错误导致程序逻辑错误。对于复杂的逻辑问题,需要严格按照问题定义逐步实现,避免遗漏关键步骤。
浙公网安备 33010602011771号