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