集美大学课程实验报告-实验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提交截图
PTA提交截图

题目4:(PTA编程)公路村村通(最小生成树)

本机运行截图
本机截图

PTA提交截图
PTA提交截图


五、实验小结(实验中遇到的问题及解决过程、实验体会和收获)

遇到的问题及解决方法:

  1. 问题:在创建图的代码中,邻接矩阵的初始化部分存在错误,导致邻接矩阵的值没有正确赋值。
    • 解决方法:仔细检查了代码逻辑,发现是输入边的权重时,没有正确地将字符转换为索引。通过调整x - 'a'和y - 'a'的计算方式,确保了邻接矩阵的正确赋值。
  2. 问题:在判断颜色分配是否有效时,没有正确检查相邻顶点的颜色是否相同,导致程序输出错误结果。
    • 解决方法:重新审视了图着色问题的定义,明确需要检查每条边的两个端点颜色是否不同。通过遍历边数组edges,逐一比较相邻顶点的颜色,确保逻辑正确。

实验体会和收获:

  • 通过这次实验,掌握了在图上的基本操作:图的创建和图的遍历,掌握了图遍历算法:DFS与BFS,掌握来如何编写最小生成树算法。
  • 在实验过程中,我学会了在处理字符和索引转换时,需要格外注意细节,避免因小错误导致程序逻辑错误。对于复杂的逻辑问题,需要严格按照问题定义逐步实现,避免遗漏关键步骤。

六、附件(参考文献和相关资料

  1. C++菜鸟教程
  2. C++菜鸟教程
  3. C++菜鸟教程
posted on 2025-05-18 16:43  醉梦4781  阅读(32)  评论(0)    收藏  举报