《数据结构》课程设计--c++图

简单介绍

是一种由节点(顶点)组成的数据结构,用来表示对象之间的关系。图分为有向图无向图,边可以有权重,表示从一个节点到另一个节点的代价或距离。图的常见应用包括社交网络、地图路径规划等。

Prim 和 Kruskal 算法

这两种算法都用于求解最小生成树(MST),即找到连接所有节点的最小权重树。

  • Prim算法:从一个节点开始,逐步将最小权重的边加入树中,直到所有节点都被包括进来。适合稠密图。
  • Kruskal算法:将所有边按权重升序排列,依次选择权重最小且不会形成环的边,直到形成最小生成树。适合稀疏图。

贝尔曼-福特、迪杰斯特拉、弗洛伊德算法

这三种算法都用于求解最短路径。

  • 贝尔曼-福特算法:用于计算单源最短路径,允许负权边,可以检测负权回路,但效率较低(O(VE))。适合处理图中存在负权边的情况。
  • 迪杰斯特拉算法:用于单源最短路径计算,适合无负权边的图,复杂度为O(V²)(使用优先队列时为O(E + VlogV))。效率高,但不能处理负权边。
  • 弗洛伊德算法:用于计算任意两点之间的最短路径,复杂度为O(V³),适合小型图。

实验题目

图的实现(大中国村)

任务描述

你们小组作为上学校派出的大学生团队,帮助西北某乡进行乡村公路和乡村医院建设规划的工作。乡村公路线长面广,分散在各个地区的各个角落,另外由于地形复杂,并不是所有村之间都可以直接建设公路。为了节省资金,需要用最经济的方案完成乡村公路建设和乡村医院的规划。

要求:

  • 1)要求给出用最少资金就能使所有村都能通路的方案;
  • 2)假设所有能建的公路都已经建成。需要确定乡村医院造在哪个村,可以使所有村到该医院的总路程最短;
  • 3)为了也能帮助其他乡完成类似工作,需要设计一个完整的程序,有交互界面,有文件导入等功能,详见下述功能列表;

功能列表:

  • 1)导入指定的文件,显示相应的图,显示方式自定;
  • 2)完成任务1,给出最少资金方案,展示方式自定;
  • 3)完成任务2,给出医院设置点和对应的总路程,展示方式自定;

输入文件格式:

  • 第1行到第n行,每行一个村的名称,一共n个村,村名没有重复
  • 第n+1行,分割线 “===========我是分割线============” 
  • 第n+2行到n+m+1行,一共m行,每行3个值用空格隔开,分别是村1、村2和两村之间公路长度

算法总述

本次实验实现了一个综合地图相关功能和可视化展示的算法集合。以下是主要的功能和算法:

  • 1. 地图数据加载:通过 Load_Data 和 load_weighted_graph_from_file 函数,从文件中加载了地理位置信息和地点之间的距离数据,并创建了相应的图数据结构。以有向图和加权图的方式,分别加载地图信息并进行保存、展示和计算操作。
  • 2. 最短路径算法:使用了 Dijkstra 算法和 Bellman-Ford 算法以及Floyd算法,计算了从某一起始顶点到其他各个顶点的最短路径,并确定了乡村医院的最佳位置。
  • 3. 最小生成树算法:实现了 Kruskal 和 Prim 算法以及 NetworkX 中的相关功能,找到了给定图的最小生成树,并将其用图形的方式展示出来。
  • 4. 图形可视化:使用 NetworkX 和 Matplotlib 库,实现了地图的可视化展示,包括展示最小生成树,初始化路径图以及最少资金路径图。
  • 5. 整体流程:程序通过加载文件,构建图数据结构,并进行最短路径、最小生成树计算和乡村医院位置确定,在算法计算的同时,以直观的方式将地图绘制成图形,并保存成图片文件,以便后续分析和分享。

特点及创新点:所有算法均完整实现、图形可视化。

解决方案

算法设计

图的边、结点、邻接表 的类的设计

extern vector<string> cities;
extern unordered_map<string, int> myMap;
extern vector<vector<int>> adj;
int Find(int v, vector<int> parent);
bool Union(int s, int e, vector<int>& parent, vector<int>& Rank);
class NetworkArc
{
public:
    int adjVex; // 序号
    int weight; // 权值
    int begin;
    int end;
    NetworkArc* nextArc;
    NetworkArc() // 无参数的构造函数
    {
        adjVex = 1;
        nextArc = NULL;
    }
    NetworkArc(int v, int w, int a, int b, NetworkArc* next = NULL) // 有参数的构造函数
    {
        adjVex = v;
        weight = w;
        begin = a;
        end = b;
        nextArc = next;
    }
};
template <class DataType>
class NetworkNode
{
public:
    DataType data;
    NetworkArc* firstArc;
    NetworkNode() // 无参数的构造函数
    {
        firstArc = NULL;
    }
    NetworkNode(DataType val, NetworkArc* adj = NULL) // 有参数的构造函数
    {
        data = val;
        firstArc = adj;
    }
};
template <class DataType>
class AdjListDirNetwork
{
public:
    NetworkNode<DataType>* vexTable;
    int vexNum, vexMaxNum, arcNum; // 顶点数目、允许的顶点最大数目和边数
    AdjListDirNetwork(vector<DataType> es, int vertexNum, int vertexMaxNum = 80);
    AdjListDirNetwork(vector<DataType> es, int vertexNum, vector<vector<int>> adj, int vertexMaxNum = 80);
    int GetWeight(int v1, int v2) const;
    void InsertArc(int v1, int v2, int w);
    void Display();
    vector<NetworkArc> MinSpanTree_Kruskal();
    vector<NetworkArc> MinSpanTree_Prim();
};
  • 1. NetworkArc: 表示图中的边。成员变量包括adjVex(邻接点序号)、weight(权值)、begin(开始点)、end(终点)、nextArc(指向下一条邻接边的指针)。提供了两种构造函数,其中一个是无参构造函数,另一个是带参构造函数。
  • 2. NetworkNode: 表示图中的顶点。成员变量包括data(顶点数据)和firstArc(指向该顶点的第一条邻接边的指针)。  与NetworkArc一样,提供了无参和带参构造函数。
  • 3. AdjListDirNetwork: 表示有向图的邻接表。成员变量包括vexTable(指向顶点数组的指针)、vexNum(顶点数目)、vexMaxNum(允许的最大顶点数目)以及arcNum(边数)。提供了两个构造函数,用于构建有向图。包含了获取边权值、插入边、显示图以及两种最小生成树算法的实现。
  • 总体来说,这些类提供了一种基于邻接表的图结构表示和辅助方法,使得能够在此基础上实现图的构建、边的插入和两种最小生成树算法的求解。

任务一:Prim算法和Kruskal算法

bool compareWeight(const NetworkArc& arc1, const NetworkArc& arc2)
{
    return arc1.weight < arc2.weight;
}
int Find(int v, vector<int> parent)
{
    if (v != parent[v])
        parent[v] = Find(parent[v], parent);
    // cout<<v<<": "<<parent[v]<<endl;
    return parent[v];
}
bool Union(int s, int e, vector<int>& parent, vector<int>& Rank)
{
    int sRoot = Find(s, parent);
    int eRoot = Find(e, parent);
    if (sRoot != eRoot)
    {
        if (Rank[sRoot] < Rank[eRoot])
        { // sRoot是小树,将其加入大树中
            parent[sRoot] = eRoot;
            Rank[eRoot] += Rank[sRoot];
        }
        else
        { // eRoot为小树,将其加入sRoot中
            parent[eRoot] = sRoot;
            Rank[sRoot] += Rank[eRoot];
        }
        return true; // s和e爸爸不同,需要合并返回true
    }
    return false; // s和e爸爸相同,不需要合并,返回false
}
template <class DataType>
vector<NetworkArc> AdjListDirNetwork<DataType>::MinSpanTree_Prim()
{
    int u0 = 0;
    vector<int> LowWeight(vexNum);
    vector<int> NearVertex(vexNum);
    vector<NetworkArc> arcs_output;
    for (int i = 0; i < vexNum; i++)
    {
        LowWeight[i] = GetWeight(u0, i);
        NearVertex[i] = u0;
    }
    LowWeight[u0] = 0;
    NearVertex[u0] = 1;
    for (int i = 1; i < vexNum; i++)
    {
        int min = INT_MAX;
        int k = 0;
        for (int j = 0; j < vexNum; j++)
        {
            if (NearVertex[j] != 1 && LowWeight[j] < min)
            {
                min = LowWeight[j];
                k = j;
            }
        }
        arcs_output.push_back(NetworkArc(k, min, NearVertex[k], k));
        NearVertex[k] = 1;
        for (int j = 0; j < vexNum; j++)
        {
            if (NearVertex[j] != 1 && GetWeight(k, j) < LowWeight[j])
            {
                LowWeight[j] = GetWeight(k, j);
                NearVertex[j] = k;
            }
        }
    }
    return arcs_output;
}
template <class DataType>
vector<NetworkArc> AdjListDirNetwork<DataType>::MinSpanTree_Kruskal()
{
    vector<int> parent(vexNum);
    vector<int> Rank(vexNum);
    NetworkArc arcs[arcNum]; // 边集数组
    vector<NetworkArc> arcs_output;
    // 根据G的邻接矩阵表示得到边集数组
    int k = 0;
    for (int i = 0; i < vexNum; i++)
    {
        for (int j = i + 1; j < vexNum; j++)
        { // 无向图的邻接矩阵是对称的,不需要将重复的边添加到边集数组中
            if (adj[i][j] != INT_MAX)
            {
                arcs[k].begin = i;
                arcs[k].end = j;
                arcs[k].weight = adj[i][j];
                k++;
            }
        }
    }
    for (int i = 0; i < vexNum; i++)
    {
        parent[i] = i; // 初始化数组值
        Rank[i] = 1;
    }
    // 对边集数组进行排序
    sort(arcs, arcs + arcNum, compareWeight);
    for (int i = 0; i < k; i++)
    {
        if (Union(arcs[i].begin, arcs[i].end, parent, Rank))
        {
            arcs_output.push_back(arcs[i]);
        }
    }
    return arcs_output;
}

template <class DataType>
int AdjListDirNetwork<DataType>::GetWeight(int v1, int v2) const
// 操作结果:返回顶点为v1和v2的边的权值
{
    NetworkArc* p = new NetworkArc();
    p = vexTable[v1].firstArc;
    while (p != NULL && p>adjVex != v2)
        p = p>nextArc;
    if (p != NULL)
        return p>weight; // 返回权值
    else
        return INT_MAX; // 返回权值为infinity,表示边不存在
}
template <class DataType>
AdjListDirNetwork<DataType>::AdjListDirNetwork(vector<DataType> es, int vertexNum, int vertexMaxNum)
// 操作结果:构造顶点数据为es[],顶点数为numVex,顶点个数为vertexNum,边数为0的有向网
{
    vexNum = vertexNum;
    vexMaxNum = vertexMaxNum;
    arcNum = 0;
    vexTable = new NetworkNode<DataType>[vexMaxNum];
    for (int v = 0; v < vexNum; v++)
    {
        vexTable[v].data = es[v];
        vexTable[v].firstArc = NULL;
    }
}
template <class DataType>
AdjListDirNetwork<DataType>::AdjListDirNetwork(vector<DataType> es, int vertexNum, vector<vector<int>> adj, int vertexMaxNum)
// 操作结果:构造顶点数据为es[],顶点数为numVex,顶点个数为vertexNum,边数为0的有向网
{
    vexNum = vertexNum;
    vexMaxNum = vertexMaxNum;
    arcNum = 0;
    vexTable = new NetworkNode<DataType>[vexMaxNum];
    for (int v = 0; v < vexNum; v++)
    {
        vexTable[v].data = es[v];
        vexTable[v].firstArc = NULL;
    }
    for (int v = 0; v < vertexNum; v++)
    {
        for (int u = 0; u < vertexNum; u++)
        {
            if (u == v)
            {
                this>InsertArc(v, u, 0);
            }
            else
            {
                if (adj[v][u] != INT_MAX)
                {
                    this>InsertArc(v, u, adj[v][u]);
                    this>InsertArc(u, v, adj[v][u]);
                    arcNum++;
                }
            }
        }
    }
}
template <class DataType>
void AdjListDirNetwork<DataType>::Display()
{
    NetworkArc* p;
    for (int v = 0; v < vexNum; v++)
    {                                           // 显示第v个邻接链表
        cout << v << ":\t" << vexTable[v].data; // 显示顶点号
        p = vexTable[v].firstArc;
        while (p != NULL)
        {
            if (p>weight != 0 and p>weight != INT_MAX)
            {
                cout << ">(" << p>adjVex << "," << p>weight << ")";
            }
            p = p>nextArc;
        }
        cout << endl;
    }
}
template <class DataType>
void AdjListDirNetwork<DataType>::InsertArc(int v1, int v2, int w)
// 操作结果:插入顶点为v1和v2,权为w的边
{
    NetworkArc* p;
    p = vexTable[v1].firstArc;
    vexTable[v1].firstArc = new NetworkArc(v2, w, v1, v2, p);
}   }

1. 最小生成树算法:

  •     Prim算法:使用LowWeight和NearVertex来维护和记录生成树和非树顶点之间的最短路径;通过循环迭代,每次找到距离当前生成树最近的顶点并将其加入最小生成树,同时更新LowWeight和NearVertex。
  •     Kruskal算法:将所有边按权值从小到大排序;逐个考虑边,如果两个顶点不在同一个连通分量中,则将这条边加入最小生成树,同时合并这两个连通分量。

2. UnionFind算法:

  •     Find函数:通过递归方式实现路径压缩,在查找过程中将顶点的根节点指向树的根节点,提高了后续查询的效率;
  •     Union函数:根据两个顶点的根节点的秩,合并两个连通分量,优化了合并操作的效率。

3. 邻接表图:

  •     使用邻接表构建有向图表示;插入、获取边权值、显示图等方法为对图的基本操作;
  •     图的构建:从顶点数据和邻接矩阵构建有向图,处理了自环边和双向边。

任务二:弗洛伊德算法

void floyd()
{
    for(int i=0;i<m;i++)
    {
        for(int j=0;j<m;j++)
        {
            for(int k=0;k<m;k++)
            {
                dp[i][j]=min(dp[i][j],dp[i][k]+dp[k][j]);
            }
        }
    }
}
void outputFloyd()
{
    int id=0;
    for(int i=0;i<m;i++)
    {
        int res=0;
        for(int j=0;j<m;j++)
        {
            if(i==j)dp[i][j]=0;
            if(dp[i][j]!=infinte)res+=dp[i][j];
        }
        sum.push_back(res);
        int temp=sum[0];
        sort(sum.begin(),sum.end());
        if(sum[0]!=temp)
        id=i;  
    }
    cout << "乡村医院应该建在: " << p[id].nodename << endl;
    cout << "所有村到该医院的总路程最短为: " << sum[0] << endl;
}
void FloydRunning()
{
    floyd();outputFloyd();
}

这段代码实现了Floyd算法以及一些与之相关的操作,包括节点的结构 node,以及搜索、Floyd算法执行和输出结果的一些方法。

1. Floyd算法:解决所有点对之间的最短路径问题,通过三层循环逐步更新两点之间的最短距离。

  •     更新过程中利用 min 函数来保留最小值。
  •     floyd 函数用于执行Floyd算法,计算各个点之间的最短路径。

2.节点结构 node:表示地点的名称和序号,并具有 isvisited 属性用于记录某个地点是否已经被访问,searchFloyd 函数用于根据地点名称查找对应节点。

3. outputFloyd 函数用于输出Floyd算法计算后的最短路径的结果。

  • 通过循环遍历,计算并保存每个点到其他点的最短路径和,并根据计算结果对节点进行排序,输出最短路径最小的节点及其最短路径值。

4. FloydRunning 函数用于执行Floyd算法并输出最短路径结果。

任务二:迪杰斯特拉算法

int main()
{
    AdjListDirNetwork<string> net = Load_Data();
    net.Display();
    ///迪杰斯特拉解决
    HospitalLocation_Dijkstra(net);
    return 0;
}
/
AdjListDirNetwork<string> Load_Data()
{
    ifstream file("china.txt");
    string line;
    vector<string> cities;
    unordered_map<string, int> myMap;
    int i = 0;
    while (getline(file, line) && line != "==========我是分割线==============")
    {
        cities.push_back(line);
        myMap[line] = i;
        i++;
    }
    vector<vector<int>> adj(i, vector<int>(i, INT_MAX));
    while (getline(file, line))
    {
        stringstream ss(line);
        string city1, city2;
        int distance;
        ss >> city1 >> city2 >> distance;
        adj[myMap[city1]][myMap[city2]] = distance;
    }
    AdjListDirNetwork<string> net(cities, adj.size(), adj);
    return net;
}
void ShortestPathDij(const AdjListDirNetwork<string>& g, int v0, int* path, int* dist)
{
    // 操作结果: 用Dijkstra算法求有向网g从顶点v0到其余顶点v的最短路径path和路径长度dist[v],
    //	path[v]存储最短路径上终点的前一顶点的顶点号
    const int infinity = DEFAULT_INFINITY;
    int numVertices = g.vexNum;
    vector<bool> visited(numVertices, false);
    // 初始化距离数组和路径数组
    for (int v = 0; v < numVertices; ++v)
    {
        dist[v] = (v == v0) ? 0 : infinity;
        path[v] = (v == v0) ? -1 : v0;
    }
    for (int i = 0; i < numVertices 1; ++i)
    {
        int minDist = infinity;
        int u = -1;
        // 找到未访问过的顶点中距离起始顶点最近的顶点
        for (int j = 0; j < numVertices; ++j)
        {
            if (!visited[j] && dist[j] < minDist)
            {
                minDist = dist[j];
                u = j;
            }
        }
        if (u == -1)
            break; // 如果找不到合适的顶点,则退出循环
        visited[u] = true;
        // 更新与顶点u相邻的顶点的距离和路径
        for (int v = 0; v < numVertices; ++v)
        {
            int weight = g.GetWeight(u, v);
            if (!visited[v] && weight != INT_MAX && dist[u] + weight < dist[v])
            {
                dist[v] = dist[u] + weight;
                path[v] = u;
            }
        }
    }
}
void HospitalLocation_Dijkstra(const AdjListDirNetwork<string>& net)
{
    int num = net.vexNum;
    string min_node;
    int min_distance_sum = INT_MAX;
    int* path = new int[num];
    int* dist = new int[num];
    for (int v = 0; v < num; v++)
    {
        ShortestPathDij(net, v, path, dist);
        int distance_sum = 0;
        for (int i = 0; i < num; ++i)
        { //对于每个村庄v,计算其他村庄到v最短路径之和
            if (i != v)
            {
                distance_sum += dist[i];
            }
        }
        cout << "村庄 " << net.vexTable[v].data << " 到其他村庄的总路程为: " << distance_sum << endl;
        if (distance_sum < min_distance_sum)   //更新比较求得总路程最短的村庄作为乡村医院
        {
            min_distance_sum = distance_sum;
            min_node = net.vexTable[v].data;
        }
    }
    cout << "乡村医院应该建在: " << min_node << endl;
    cout << "所有村到该医院的总路程最短为:" << min_distance_sum << endl;
    delete[] path;
    delete[] dist;
}

这段代码涉及了加载地图信息、利用Dijkstra算法求解村庄间最短路径和确定乡村医院位置。

  • 1. 加载地图信息:从文件 "china.txt" 中加载地点信息以及各地点之间的距离数据。创建一个 AdjListDirNetwork<string> 对象来表示地图信息,并使用加载的数据进行初始化。
  • 2. Dijkstra算法求解最短路径:ShortestPathDij 方法用于对有向网中的一个顶点到其他顶点的最短路径进行计算。在该方法中,使用 Dijkstra 算法对指定的起点顶点到其余各个顶点的最短路径进行求解,并将结果存储在 path 和 dist 中。
  • 3. 确定乡村医院位置:HospitalLocation_Dijkstra 方法用于根据 Dijkstra 算法求解的最短路径结果,确定乡村医院的最佳位置。通过循环计算每个村庄到其他村庄的总路程,找出总路程最短的村庄即为乡村医院最佳位置。

任务二:贝尔曼福特算法

#define DEFAULT_SIZE 1000
#define DEFAULT_INFINITY 1000000
AdjListDirNetwork<string> Load_Data();
void BellmanFord(const AdjListDirNetwork<string>& net, int v0, int* path, int* dist);
void HospitalLocation_BellmanFord(const AdjListDirNetwork<string>& net);
using namespace std;
int main()
{
    AdjListDirNetwork<string> net = Load_Data();
    net.Display();
    ///贝尔曼-福特解决
    HospitalLocation_BellmanFord(net);
    return 0;
}
/
AdjListDirNetwork<string> Load_Data()
{
    ifstream file("chinese.txt");
    string line;
    vector<string> cities;
    unordered_map<string, int> myMap;
    int i = 0;
    while (getline(file, line) && line != "==========我是分割线==============")
    {
        cities.push_back(line);
        myMap[line] = i;
        i++;
    }
    vector<vector<int>> adj(i, vector<int>(i, INT_MAX));
    while (getline(file, line))
    {
        stringstream ss(line);
        string city1, city2;
        int distance;
        ss >> city1 >> city2 >> distance;
        adj[myMap[city1]][myMap[city2]] = distance;
    }
    AdjListDirNetwork<string> net(cities, adj.size(), adj);
    return net;
}
void BellmanFord(const AdjListDirNetwork<string>& net, int v0, int* path, int* dist)
{
    int vexNum = net.vexNum;
    int* distTemp = new int[vexNum];
    // 初始化 dist 和 path 数组
    for (int v = 0; v < vexNum; ++v)
    {
        // 如果节点 v 与节点 v0 之间有连接,则将 dist[v] 设置为其权重值,否则设置为无穷大
        dist[v] = (v == v0) ? 0 : (net.GetWeight(v0, v) != INT_MAX ? net.GetWeight(v0, v) : INT_MAX);
        // 初始化 path 数组,表示从 v0 到其他节点的最短路径的前一个节点为 v0
        path[v] = v0;
    }
    // 执行 V-1 次循环
    for (int k = 1; k < vexNum; ++k)
    {// 复制 dist 数组到 distTemp
        for (int v = 0; v < vexNum; ++v)
        {
            distTemp[v] = dist[v];
        }
        // 对每一条边进行松弛操作
        for (int u = 0; u < vexNum; ++u)
        {// 只考虑节点 u 到节点 v 的有向边
            if (u != v0)
            {
                for (int v = 0; v < vexNum; ++v)
                {// 如果节点 u 到节点 v 有连接,并且通过节点 u 到达节点 v 的路径比当前路径短,则更新距离和路径
                    if (net.GetWeight(u, v) != INT_MAX && distTemp[u] != INT_MAX && distTemp[v] > distTemp[u] + net.GetWeight(u, v)) {
                        distTemp[v] = distTemp[u] + net.GetWeight(u, v);
                        path[v] = u;
                    }
                }
            }
        }
        // 将 distTemp 复制回 dist
        for (int v = 0; v < vexNum; ++v)
        {
            dist[v] = distTemp[v];
        }
    }
    delete[] distTemp;
}
void HospitalLocation_BellmanFord(const AdjListDirNetwork<string>& net)
{
    int num = net.vexNum;
    string min_node;
    int min_distance_sum = INT_MAX;
    int* path = new int[num];
    int* dist = new int[num];
    for (int v = 0; v < num; v++)
    {
        BellmanFord(net, v, path, dist);
        int distance_sum = 0;
        for (int i = 0; i < num; ++i)
        { //对于每个村庄v,计算其他村庄到v最短路径之和
            if (i != v)
            {
                distance_sum += dist[i];
            }
        }
        cout << "村庄 " << net.vexTable[v].data << " 到其他村庄的总路程为: " << distance_sum << endl;
        if (distance_sum < min_distance_sum)   //更新比较求得总路程最短的村庄作为乡村医院
        {
            min_distance_sum = distance_sum;
            min_node = net.vexTable[v].data;
        }
    }
    cout << "乡村医院应该建在: " << min_node << endl;
    cout << "所有村到该医院的总路程最短为:" << min_distance_sum << endl;
    delete[] path;
    delete[] dist;
  • 1.加载地图信息:使用 Load_Data 方法加载地理位置信息和各地点之间的距离数据,然后通过创建一个 AdjListDirNetwork<string> 对象来表示这些地理位置和距离信息。
  • 2.Bellman-Ford 算法:用于计算从某一起始顶点到其他各个顶点的最短路径和路径长度。在算法中,通过循环迭代,对每一个顶点进行松弛操作,通过比较路径长度来寻找最短路径,直到达到最优解。
  • 3.乡村医院位置确定:HospitalLocation_BellmanFord 方法利用 Bellman-Ford 算法的计算结果,计算了各个村庄到其他村庄的总路程,并根据计算出的总路程确定了最佳的乡村医院位置。

可视化展示

import matplotlib.pyplot as plt
import networkx as nx
import sys
import shapefile

def load_weighted_graph_from_chinese(filename):
    G = nx.Graph()
    read_flag = False
    with open(filename, "r", encoding="GBK") as file:
        for line in file:
            line = line.strip()
            if line == "==========我是分割线==============":
                read_flag = True
                continue
            if read_flag:
                source, target, weight = line.split()
                G.add_edge(source, target, weight=int(weight))
    return G

def load_weighted_graph_from_file(filename):
    G = nx.Graph()
    with open(filename, "r", encoding="GBK") as file:
        for line in file:
            source, target, weight = line.split()
            G.add_edge(source, target, weight=int(weight))
    return G

city_coordinates = {
    "北京": (39.9042, 116.4074),
    "上海": (31.2304, 121.4737),
    "广州": (23.1291, 113.2644),
    "武汉": (30.5928, 112.3055),
    # "武汉": (30.5928, 114.3055),
    "西安": (35.3416, 107.9398),
    # "西安": (34.3416, 108.9398),
    "成都": (30.5728, 104.0668),
    "沈阳": (41.8057, 123.4315),
    "厦门": (24.4798, 118.0894),
    "台北": (25.0330, 121.5654),
    "乌鲁木齐": (43.8256, 87.6168),
    "拉萨": (29.6500, 91.1000),
}
a={0: '北京', 1: '上海', 2: '广州', 3: '武汉', 4: '西安', 5: '成都', 6: '沈阳', 7: '厦门', 8: '台北', 9: '乌鲁木齐', 10: '拉萨'}

def Draw_Orignal():
    china_shapefile = "simplied_china_country"
    sf = shapefile.Reader(china_shapefile)

    # 创建一个新的Figure对象
    plt.figure(figsize=(10, 6))

    # 绘制中国地图的轮廓
    for shape in sf.shapes():
        x = [i[0] for i in shape.points]
        y = [i[1] for i in shape.points]
        plt.plot(x, y, color="black")

    city_coordinates_swapped = {
        city: (lon, lat) for city, (lat, lon) in city_coordinates.items()
    }


    filename = "chinese.txt"
    G = load_weighted_graph_from_chinese(filename)

    pos = city_coordinates_swapped
    plt.rcParams["font.sans-serif"] = ["Microsoft YaHei"]
    nx.draw(
        G,
        pos,
        with_labels=True,
        node_size=350,
        node_color="skyblue",
        font_family="Microsoft YaHei",
        font_size=9,
    )
    labels = nx.get_edge_attributes(G, "weight")
    nx.draw_networkx_edge_labels(G, pos, edge_labels=labels, font_family="Microsoft YaHei",font_size=7)

    plt.savefig("初始路径图.png")
    plt.show()

def Draw_MinSpanTree(flag):
    china_shapefile = "simplied_china_country"
    sf = shapefile.Reader(china_shapefile)

    # 创建一个新的Figure对象
    plt.figure(figsize=(10, 6))

    # 绘制中国地图的轮廓
    for shape in sf.shapes():
        x = [i[0] for i in shape.points]
        y = [i[1] for i in shape.points]
        plt.plot(x, y, color="black")

    city_coordinates_swapped = {
        city: (lon, lat) for city, (lat, lon) in city_coordinates.items()
    }

    # 
    filename = "min_span_tree.txt"
    G = load_weighted_graph_from_file(filename)
    pos = city_coordinates_swapped
    plt.rcParams["font.sans-serif"] = ["Microsoft YaHei"]
    nx.draw(
        G,
        pos,
        with_labels=True,
        node_size=350,
        node_color=[
            "red" if(flag==1 and node == int(sys.argv[2]))  else "skyblue" for node in G.nodes()
        ],  # 设置武汉节点为红色
        font_family="Microsoft YaHei",
        font_size=9,
    )
    labels = nx.get_edge_attributes(G, "weight")
    nx.draw_networkx_edge_labels(G, pos, edge_labels=labels, font_family="Microsoft YaHei",font_size=8)

    plt.savefig("最少资金路径图.png")
    plt.show()

if(int(sys.argv[1])==1):
    Draw_Orignal()   
elif(int(sys.argv[1])==2):
    Draw_MinSpanTree(0)
elif(int(sys.argv[1])==3):
    Draw_MinSpanTree(1)
else:
    Draw_Orignal()  
    Draw_MinSpanTree(1)

使用 NetworkX 和 Matplotlib 库绘制加权图的可视化,加载地图信息并以图形方式展示。

  • 1. 加权图可视化:通过 load_weighted_graph_from_file 函数从文件中加载加权图的信息,并使用 NetworkX 创建图对象。使用 spring_layout 对图进行布局定位,然后使用 Matplotlib 进行绘图。
  • 2. 图形展示:图形的展示使用的是 Matplotlib 库,对图形的节点、边、文字标签等进行配置,以便生成最终的图形。
  • 3. 生成的图形可以保存成文件,这里使用了 plt.savefig 方法将图形保存为图片文件。

源程序代码 main.cpp

最终,经过整合,在AjdNetwork.h中定义所有的类和结构体;

在Assistance.h中定义:

vector<string> cities;            // 城市列表
unordered_map<string, int> myMap; // 城市和序号的映射
vector<vector<int>> adj;          // 邻接矩阵
int infinte = 0X3f3f3f3f;
int dp[100][100];
node p[100];
int m = 0;
AdjListDirNetwork<string> Load_Data();                                                  // 数据加载
void Display_MinSpanTree(vector<NetworkArc> &MinSpanTree);                              // 最小生成树展示
void Display_MinSpanTree_Tofile(vector<NetworkArc> &MinSpanTree, const string & filename); // 将最少资金路径信息写入文件
string Find_City(int i);                                                                // 根据序号确定城市
ode searchFloyd(string nodename);

在HospitalLocation.h中定义:

vector<int>sum;
void Dijkstra(const AdjListDirNetwork<string> &g, int v0, int* path, int* dist);
void BellmanFord(const AdjListDirNetwork<string> &net, int v0, int* path, int* dist);
void HospitalLocation(const AdjListDirNetwork<string> &net, int flag);
void floyd();
void outputFloyd();
void FloydRunning();

从而实现精简的main.cpp:

int main()
{
	AdjListDirNetwork<string> net = Load_Data();    //初始化
	cout << "导入文件,显示邻接表:" << endl;
	net.Display();
	Draw(1);
	string choice;
	while (true) {
		cout << endl;
		cout << "==============菜单:==============" << endl;
		cout << "1. 任务1:最小生成树(克鲁斯卡尔)" << endl;
		cout << "2. 任务1:最小生成树(普里姆)" << endl;
		cout << "3. 任务2:最短路径(贝尔曼福德)" << endl;
		cout << "4. 任务2:最短路径(迪杰斯特拉)" << endl;
		cout << "5. 任务2:最短路径(弗洛伊德)" << endl;
		cout << "6. 图形显示生成树" << endl;
		cout << "7. 退出" << endl;
		cout << "=================================" << endl;
		cout << "请选择操作: ";
		cin >> choice;
		cout << endl;
		if (choice.size() != 1 || !isdigit(choice[0]))
		{
			cout << "无效的选择,请重新输入" << endl;
			cin.clear(); // Clear error flags
			cin.ignore(numeric_limits<streamsize>::max(), '\n');
			continue;
		}
		int option = stoi(choice);
		switch (option)
		{
		case 1:
		{
			vector<NetworkArc> MinSpanTree = net.MinSpanTree_Kruskal();
			cout << "最小生成树(克鲁斯卡尔):" << endl;
			Display_MinSpanTree(MinSpanTree);
			Draw(2);
			break;
		}
		case 2:
		{
			vector<NetworkArc> MinSpanTree = net.MinSpanTree_Prim();
			cout << "最小生成树(普里姆):" << endl;
			Display_MinSpanTree(MinSpanTree);
			Draw(2);
			break;
		}
		case 3:
		{
			cout << "最短路径(贝尔曼福德):" << endl;
			HospitalLocation(net, 1);
			Draw(3);
			break;
		}
		case 4:
		{
			cout << "最短路径(迪杰斯特拉):" << endl;
			HospitalLocation(net, 2);
			Draw(3);
			break;
		}
		case 5:
		{
			cout << "最短路径(弗洛伊德):" << endl;
			HospitalLocation(net, 3);
			Draw(3);
			break;
		}
		case 6:
		{
			vector<NetworkArc> MinSpanTree = net.MinSpanTree_Prim(); // or any tree you want to display
			Display_MinSpanTree_Tofile(MinSpanTree, "min_span_tree.txt");
			Draw(4);
			cout << endl << "已生成'最少资金路径图.png'和'初始路径图.png'和'min_span_tree.txt'" << endl;
			break;
		}
		case 7:
		{
			cout << "退出程序" << endl;
			return 0;
		}
		default:
		{
			cout << "无效的选择,请重新输入" << endl;
			break;
		}
		}
	}
	return 0;
}

算法分析(时间复杂度)

1. Dijkstra算法: 时间复杂度:O((V + E)logV),其中 V 为顶点数,E 为边数。在使用最小堆(优先队列)的情况下,每次更新最短路径需要 O(logV) 的时间,一共有 V 个顶点,E 条边。

2. Bellman-Ford 算法:时间复杂度:O(V * E),其中 V 为顶点数,E 为边数。Bellman-Ford 算法中对所有边进行了 V-1 次松弛操作。

3. Prim和Kruskal算法:

  • Prim算法时间复杂度:O(ElogV),其中 V 为顶点数,E 为边数。在这里,使用二叉堆的实现方式,选择最小边和更新相邻边的顶点。
  • Kruskal算法时间复杂度:O(ElogE),其中 E 为边数。该算法主要涉及到排序边以及查找并查集树。排序边的复杂度为 O(ElogE),并查集的操作复杂度为 O(logE)。

实验结果展示

功能0.0

功能1

功能2

功能3:图形演示

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值