数据结构实验5:图的基本操作

发布时间:2024年01月22日

一、问题描述
实现图的基本操作,包括:创建图的存储结构、复制已有的图、计算图的结点个数和弧/边条数、深度优先遍历序列、广度优先遍历序列、最小生成树、拓扑排序等。

二、实验目的
掌握图的基本操作。

三、实验内容及要求
1、构造图的存储结构。
2、实现图的创建、复制、计算图的结点个数和弧/边条数、深度优先遍历序列、广度优先遍历序列、最小生成树、拓扑排序等操作。


四、数据结构设计及算法原理

数据结构设计

  1. Graph 类:

    • 使用 list<pair<int, int>> *adj 来表示图的邻接表,其中 pair<int, int> 存储相邻顶点和边的权重(对于无权图,权重可忽略)。
    • int V 表示图中顶点的数量。
    • 提供构造函数和析构函数来初始化和清理资源。
  2. 辅助数据结构:

    • 对于拓扑排序,使用 stack<int> 来存储排序的顶点。
    • 对于 Prim 算法,使用 priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> 来选择最小权重的边。
    • 使用 vector<int>vector<bool> 来存储关于顶点的临时信息,如 key(Prim 算法中的最小权重)和 visited(用于标记访问过的顶点)。

算法原理

  1. 拓扑排序:

    • 使用深度优先搜索(DFS)来访问每个顶点。
    • 在DFS的过程中,当完成一个顶点的所有相邻顶点的访问后,将该顶点推入栈中。
    • 完成所有顶点的DFS后,栈中的顶点顺序即为拓扑排序的结果。
  2. Prim 算法(最小生成树):

    • 从任意顶点开始构建最小生成树,逐步加入权重最小的边。
    • 使用优先队列来选择当前可访问的边中权重最小的边。
    • 更新连接的顶点的最小权重和父顶点。
    • 重复这个过程直到所有顶点都被访问。

流程图代码

拓扑排序
未访问的顶点
开始
初始化访问标记数组和栈
检查所有顶点
执行DFS
所有顶点访问完毕
按栈顺序输出顶点
结束
Prim 算法
开始
初始化 key数组, parent数组, 访问标记数组, 优先队列
所有顶点访问完毕
从优先队列中取出最小权重边
更新该边连接的顶点的key和parent
将新的邻接顶点加入优先队列
根据parent数组构建最小生成树
结束

变量定义

  • V: 图中顶点的数量。
  • adj: 邻接表,存储图的边信息。
  • visited: 用于标记顶点是否已被访问。
  • key: Prim算法中,用于存储到达每个顶点的最小权重。
  • parent: Prim算法中,用于存储构建最小生成树时每个顶点的父顶点。
  • priority_queue: 在Prim算法中,用于选择最小权重边。

五、测试数据及结果分析

测试数据 1: 拓扑排序

输入

考虑以下有向无环图 (DAG):

  • 边集:{(0, 1), (0, 2), (1, 3), (2, 3), (3, 4), (4, 5)}
  • 顶点数:6
输出

一种可能的拓扑排序序列为:0, 2, 1, 3, 4, 5

结果分析

在这个DAG中,顶点0没有任何入度,是排序的起始点。顶点5没有出度,是排序的终点。拓扑排序输出的顺序满足了图中所有的方向约束。由于拓扑排序可能不唯一,任何满足顶点间依赖关系的排序都是正确的。

测试数据 2: Prim 算法

输入

考虑以下加权无向图:

  • 边集及权重:{(0, 1, 2), (0, 3, 6), (1, 3, 8), (1, 4, 5), (1, 2, 3), (2, 4, 7)}
  • 顶点数:5
输出

一种可能的最小生成树边集及权重为:{(0, 1, 2), (1, 2, 3), (0, 3, 6), (1, 4, 5)}

结果分析

Prim 算法从一个顶点开始,逐渐增加边以构造最小生成树。在这个例子中,每次都选择连接剩余顶点的最小权重边。输出的边集构成了一个覆盖所有顶点的树,并且总权重(2 + 3 + 6 + 5 = 16)是最小的,这符合最小生成树的定义。

六、总结与思考

数据结构的重要性

图作为一种复杂且强大的数据结构,在解决许多实际问题(如网络分析、路径查找等)中扮演着核心角色。在这个任务中,选择适当的图表示方法(如邻接表)对于有效地实现算法至关重要。邻接表不仅节约空间,尤其是在稀疏图中,而且在遍历图时提供了高效的边访问。

算法理解和应用

拓扑排序和 Prim 算法是图论中两个非常重要的算法。通过实现这些算法,我加深了对它们的理解:

  • 拓扑排序:这个算法对于理解有向无环图(DAG)中的依赖关系至关重要。它在项目规划、课程安排等领域有广泛应用。
  • Prim 算法:作为构造最小生成树的经典方法,Prim 算法在网络设计、电路布线等多个领域中有着重要作用。

测试的必要性

通过不同的测试数据集来验证算法的正确性非常重要。在这个过程中,我不仅确保了算法在标准情况下的正确运行,还通过边缘案例来测试算法的鲁棒性。这种方法在任何软件开发过程中都是必不可少的。

编程技能的提升

实现复杂数据结构和算法要求深入的编程技能和理论知识。在解决这个问题的过程中,我不仅需要精确地编写代码来实现这些算法,还需要确保代码的可读性和效率。


  • 完整代码:
#include <iostream>
#include <list>
#include <queue>
#include <stack>
#include <vector>
#include <climits>
#include <set>

using namespace std;

class Graph 
{
private:
    int V;
    list<pair<int, int>>* adj; // 存储边和权重

    void DFSUtil(int v, vector<bool>& visited, stack<int>& Stack) 
    {
        visited[v] = true;

        for (auto i = adj[v].begin(); i != adj[v].end(); ++i)
            if (!visited[i->first])
                DFSUtil(i->first, visited, Stack);

        Stack.push(v);
    }

public:
    Graph(int V) 
    {
        this->V = V;
        adj = new list<pair<int, int>>[V];
    }

    ~Graph() 
    {
        delete[] adj;
    }

    void addEdge(int v, int w, int weight = 0) 
    {
        adj[v].push_back(make_pair(w, weight));
    }

    // 拓扑排序
    void topologicalSort() 
    {
        stack<int> Stack;
        vector<bool> visited(V, false);

        for (int i = 0; i < V; i++)
            if (visited[i] == false)
                DFSUtil(i, visited, Stack);

        while (!Stack.empty()) 
        {
            cout << Stack.top() << " ";
            Stack.pop();
        }
    }

    // 最小生成树 - Prim算法
    void primMST() 
    {
        priority_queue<pair<int, int>, vector<pair<int, int>>, greater<pair<int, int>>> pq;

        int src = 0; // 从顶点0开始
        vector<int> key(V, INT_MAX);
        vector<int> parent(V, -1);
        vector<bool> inMST(V, false);

        pq.push(make_pair(0, src));
        key[src] = 0;

        while (!pq.empty()) 
        {
            int u = pq.top().second;
            pq.pop();

            inMST[u] = true;

            for (auto i = adj[u].begin(); i != adj[u].end(); ++i) \
            {
                int v = (*i).first;
                int weight = (*i).second;

                if (inMST[v] == false && key[v] > weight) 
                {
                    key[v] = weight;
                    pq.push(make_pair(key[v], v));
                    parent[v] = u;
                }
            }
        }

        for (int i = 1; i < V; ++i)
            cout << parent[i] << " - " << i << endl;
    }

    
};

int main() {
    // 示例用图
    Graph g(6);
    g.addEdge(0, 1, 5);
    g.addEdge(0, 2, 3);
    g.addEdge(1, 3, 6);
    g.addEdge(1, 2, 2);
    g.addEdge(2, 4, 4);
    g.addEdge(2, 5, 2);
    g.addEdge(2, 3, 7);
    g.addEdge(3, 4, -1);
    g.addEdge(4, 5, -2);

    cout << "拓扑排序: ";
    g.topologicalSort();

    cout << "\n最小生成树(Prim算法): \n";
    g.primMST();

    return 0;
}
文章来源:https://blog.csdn.net/m0_73898917/article/details/135738037
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。