实验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:图的创建

本机运行截图
image

题目2:图的遍历(DFS、BFS)

本机运行截图
image

题目3:PTA图着色问题

PTA提交截图
image

题目4:公路村村通

本机运行截图
image

AI对话截图

image
image


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

遇到的问题及解决方法:

  1. 问题:邻接矩阵输入时顶点下标越界
    • 解决方法:增加输入合法性检查,限制顶点编号在 0 ~ n-1范围内,避免非法内存访问
  2. 问题:BFS遍历时出现死循环
    • 解决方法:忘记将节点加入队列前标记为已访问,导致同一节点重复入队,补充队列操作前的 visited标记逻辑

实验体会和收获:

  • 熟练掌握了图的基本存储结构(邻接矩阵)与经典算法(DFS、BFS、Kruskal)的代码实现流程。
  • 深入理解了图结构的非线性特征,相比线性表,图的操作必须时刻关注顶点间的拓扑关系与连通性约束。
  • 学会了合理利用AI辅助写代码,但是必须自主调试修改,不能直接复制使用。

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

  1. 图DFS、BFS遍历 官方可视化教学平台(新加坡国立大学)
  2. Kruskal最小生成树 官方算法详解(新加坡国立大学)
posted @ 2026-05-29 19:32  Wujee  阅读(43)  评论(1)    收藏  举报