摘 要
本文讲述的是主要是运用C/C++语言Dijkstra算法来完成交通图的存储、图中任一顶点到其余任意一顶点间的最短路径问题,并利用Python中的复杂网络分析库Networkx来绘制有向图以实现最短路径的可视化。如果有热心的博友发现错误,欢迎评论指正。
关键字:Dijkstra算法、有向图、Python,C/C++混编、Networkx可视化
乘汽车旅行的人总希望找出到目的地的尽可能短的行程。如果有一张地图并在图上标出每个城市之间的距离,如何找出这一最短行程? 计算机网络中的路由就是通过互联的网络把信息从源地址传输到目的地址的活动。为了高效引导数据的传输,如何找出源和目的地址之间的最优路径? 这些问题中的网络(交通网,计算机通信网)可以使用一个带权图来建模,寻找最优路的需求可转换为带权图的最短路径问题。最短路径问题是图论研究中的一个经典算法问题, 旨在寻找图(由结点和边组成的)中两结点之间的最短路径。
确定起点的最短路径问题,即已知起始结点,求最短路径的问题。适合使用Dijkstra算法。也是本文设计的主要内容。
假如某个游客在下图中的某个城市,他想要环游一下其他所有城市,当他每到一个城市时,系统需要给出游客从当前城市到其他所有城市的最短路径和路径图,当前城市由用户输入。
(1) 数据结构:采用图(有向图)的结构来表示实际的交通网络。如上图6.21所示,图中顶点表示城市,边表示城市间的交通费联系。
(2) 算法:C/C++语言实现Dijkstra算法。
(3) 描述设计思路和算法步骤。
(4) 可视化:将游客从当前城市到其他所有城市的最短路径可视化。
(5) 分析Dijkstra算法的时间复杂度。
(6) 设计总结。
(1) 定义图的存储结构
arcs:邻接矩阵
G.arcs[i][j]:弧
一维数组S[i]:记录vo到终点是否被确定为最短路长度,1代表确定,0代表未确定
一维数组p[i]:记录vo到vi的当前最短路径上vi的直接前驱顶点序号。
一维数组D[i]:记录从vo到vi的当前最短路径长长度
#define MVNum 100 //最大顶点数
#define VNum 25
#define Maxint 65535 //定义一个最大数,其意义为无穷大
typedef char Vertextype;
typedef int Adjmatrix;
typedef struct
{
Vertextype vexs[MVNum]; //顶点数组 类型假定为char型
Adjmatrix arcs[MVNum][MVNum]; // 邻接矩阵 假定为int型
}MGraph;
int D1[MVNum], p1[MVNum];
int D[MVNum][MVNum], p[MVNum][MVNum];
(2) 建立图的存储结构
void CreateMGraph(MGraph *G) {
//采用邻接矩阵表示法构造有向图G
//输入顶点信息
// 初始化邻接矩阵
}
(3) 计算最短路径:
void Dijkstra(MGraph *G, int v1, int n)
{
○1初始化S和D
1、 将源点vo加到S中,即S[vo]=1;
2、 将vo的各个终点的最短路径长度初始化为权值,即D[i]=G.arcs[vo][vi],(vi€V-S)
3、 如果vo和顶点vi之间有弧,则将vi的前驱置为vo,即p[i]=vo,否则p[i]=0
○2循环n-1次,执行以下操作:
4、 选择下一条最短路径的终点vk,使得D[k]=Min{D[i]| vi€V-S }
5、 将vk加到S中,即S[vk]=1;
6、 根据条件更新从vo出发到集合V-S上任一顶点的最短路径 的长度,
若条件D[k]+G.arcs[k][i]
上图利用了Dijkstra算法计算并输出了从贵阳到所有城市的最短路径和路线图,仔细看看貌似没毛病。接下来选择其中任意一条路径,例如第一条对Python Networkx绘图部分单独测试。
代码见附录○1
两者经单独测试都没有太大的问题,接下来就是最关键也是最难处理的一部分了,Python将城市代号传给C++(DLL),然后返回最短路径。
代码见附录○2
只显示部分,代码见附录○3、○4
从上述的结果来看,貌似完成的井井有条,其实在实现得到上述结果的过程中遇到了很多的问题。
Python与c/c++结合的途径之一就是利用visual studio 2017(当然其他编译器也行)制作c/c++的动态链接库dll。
出现上述的确是挺让人摸不到头脑的,在第一步加载dll就出现了问题。搜索资料才发现在Python 里面使用 ctypes载入dll时,如果这个dll还依赖于其它的 dll 的话,这些相关的dll也得要能被 Python 的进程访问到。如果访问不到就会报以下错误。为了解决依赖性问题,直接引用了绝对路径。没想到刚解决了一个问题,下一个问题接踵而至。
出现上述情况,第一反应想到的就是文件编码和Python版本的问题。所以想从上面两个方面的寻找解决问题的方法。首先查看文件编码是否为utf-8(我在pycharm设置的默认编码),结果发现编码并没有问题。然后改变了Python版本,之前用的是Python2.7考虑到Python2.7对中文的支持(需要加入在程序最前头加几行代码),所以将Python版本切换到了Python3.6(我的电脑同时安装了Python2.7和3.6版本)。发现还是没有解决中文乱码的问题。上网百度还是无解,甚至没有搜索到类似的问题。所以只好将c++里的输出城市的函数给去掉了,转换到Python来提示。并返回最短路径对应的字符串。交由Python处理。不料,又一问题接踵而至。
查阅资料解释原因是ctypes.string_at()出现了非法的内存地址访问。string_at()接受的参数是内存地址,似乎隐约知道出错的原因了:给string_at()传递了一个错误的内存地址,导致程序执行时试图访问非法的内存地址,提示错误。于是对着他的步骤做了一遍,发现问题并没有解决。查阅其他资料亦是如此。
那从一个源点到其他的顶点的路径如何返回给Python呢?起初想到了二维数组,但是在c++中没有像Python[[路径1],[路径2],…]列表格式的数组。所以,必须静下心来喝杯茶,思考一下,转换下思路。不成想,一杯茶下来,竟有了解决的办法,利用结构体字符串数组来存储最短路径。
//定义结构体来存储路径
typedef struct StructPath
{
char shortest_path[MAX_STR_SIZE];
}StructPath, *Path;
但是由于粗心,在对字符串进行拼接时,未对字符串数组初始化,导致Python在调用dll时,导致程序崩溃。一番折腾下来才有了上述的结果。
经过一番折腾和测试,代码显得非常的凌乱,所以遵照代码的规范进行了修改,添加了注释和说明,同时为了提高代码的复用性和健壮性。我封装了一下函数。
def getCityByCode(dic, code):
"""
得到城市对应的代号
:code 城市代号
:return 返回城市名
"""
def getCodeByCity(dic, city):
"""
得到城市对应的代号
:city 地点名
:return 返回城市代号
"""
def check_input(city_code, city):
"""
检测用户输入的地点是否存在
:city_code 需要检测的城市代号
:return 返回输入正确的城市代号
"""
def shortest_path_graph(edge_labels, specify_edges):
"""
绘制最短路径图
:edge_labels 边标签,类型为字典
:specify_edges 指定的边标签:此处为最短路径的边标签,类型为列表
"""
def show_shortest_path_graph(city_dic, path_list):
"""
显示最短路径图
:city_dic 城市
:path_list 最短路径列表
"""
def user_select():
"""
给用户提供选择,是当前城市到其他城市所有的最短距离 还是到某个城市的最短距离
"""
最后一个函数主要是为了提高用户的体验,有时候用户并不想显示所有,只想从当前城市寻求到达某个城市的最短路径,所以得从所有的最短路径中,抽离出用户所要到达的那个城市的最短路径。显示所有的需求并不是没有,有的游客可能会想要在众多的路线中比较,抉择选择自己需要的。总的来说,多份选择,多个机会。
Dijkstra算法求最短路径主循环:
//开始循环每次求的V1到某个V顶点的最短路径并加V到S集中
for (i = 2; i <= n; i++)//其余n-1个顶点
{
min = Maxint; // 当前所知离v1顶点的最近距离设初值为∞
for (w = 1; w <= n; w++) //对所有顶点检查
if (!S[w] && D[w] < min)
{ //找离v1最近的顶点w并将其赋给v距离赋给min
v = w; //在S集之外的离v1最近的顶点序号
min = D[w]; //最近的距离
} //W顶点距离V1顶点更近
S[v] = 1; //将v并入S集
for (w = 1; w <= n; w++) //更新当前最短路径及距离
if (!S[w] && (D[v] + G->arcs[v][w] < D[w]))
{ //修改D2[w]和p2[w] w 属于 V-S
D[w] = D[v] + G->arcs[v][w]; //更新D2[w]
P2[w] = v;
} //End_if
} //End_for
主循环共进行了n-1次,每次执行的时间是O(n),次循环每次执行的时间也是O(n),所以算法的时间复杂度是O(n^2) ,即使是带权的领接表作为有向图的存储结构,虽然修改D的时间可以减少,但由于在D向量中选择最小分量的时间不变,所以时间复杂度仍为O(n^2)。
Dijkstra算法,老师在课堂上讲述了实现原理并演示了代码,在之前的离散数学课中也提到了,所以没有太大的问题,Python可视化部分也没有太大的问题,最近自己也在学习Python,通过上网查阅资料发现Python中Networkx模块是个不错的选择,即使在Network模块中已经封装了最短路径函数shortest_path、shortest_path_length,在本次设计中并没有采纳,否则就会失去数据结构课程的意义。只是在对C++和Python混编的时候遇到了很多的问题。如在加载dll时出现的的bug:OSError: exception: access violation reading 0x00000000000000F8、WindowsError: [Error 126],后来经过千辛万苦翻阅资料才得以解决。对自己发现问题、解决问题、跨语言编程能力有了极大的提升。
算法时间复杂度是对执行程序所需的时间的评估,程序对处理器的使用程度的估算,实际是衡量一个程序员水平高低和思维敏锐的标准。上述代码的时间复杂度并不是最优的。Dijkstra+堆优化的时间复杂度在某种程度上,比O(n^2)要小。比如手写二叉堆是O(elogv),stl优先队列是O(eloge),斐波那契堆是O(vlogv+e)。
所以,本次设计仍然有很多需要改进的地方
1、 算法的时间复杂度优化
2、 城市坐标点的位置固定问题;
3、 Python加载dll中文乱码问题。
#include
#include
#include
#include
#define MVNum 100 //最大顶点数
#define VNum 25
#define Maxint 65535 //定义一个最大数,其意义为无穷大
using namespace std;
typedef char Vertextype;
typedef int Adjmatrix;
typedef struct
{
Vertextype vexs[MVNum]; //顶点数组 类型假定为char型
Adjmatrix arcs[MVNum][MVNum]; // 邻接矩阵 假定为int型
}MGraph;
int D1[MVNum], p1[MVNum];
int D[MVNum][MVNum], p[MVNum][MVNum];
//通过城市代号获得城市名
char * getPalceByOrder(int i)
{
char *place;
place = (char *)malloc(10);
switch (i)
{
case 1:strcpy(place, "北京");
break;
case 2:strcpy(place, "天津");
break;
case 3:strcpy(place, "郑州");
break;
case 4:strcpy(place, "徐州");
break;
case 5:strcpy(place, "西安");
break;
case 6:strcpy(place, "成都");
break;
case 7:strcpy(place, "武汉");
break;
case 8:strcpy(place, "上海");
break;
case 9:strcpy(place, "福州");
break;
case 10:strcpy(place, "南昌");
break;
case 11:strcpy(place, "株洲");
break;
case 12:strcpy(place, "贵阳");
break;
case 13:strcpy(place, "昆明");
break;
case 14:strcpy(place, "广州");
break;
case 15:strcpy(place, "深圳");
break;
case 16:strcpy(place, "柳州");
break;
case 17:strcpy(place, "南宁");
break;
case 18:strcpy(place, "兰州");
break;
case 19:strcpy(place, "呼和浩特");
break;
case 20:strcpy(place, "西宁");
break;
case 21:strcpy(place, "乌鲁木齐");
break;
case 22:strcpy(place, "沈阳");
break;
case 23:strcpy(place, "长春");
break;
case 24:strcpy(place, "哈尔滨");
break;
case 25:strcpy(place, "大连");
break;
}
return place;
}
//打印城市代号对照表
void printPlace()
{
int i;
cout << "\t\t\t\t城市代号对照表\n";
cout << "********************************************************************************";
for (i = 1; i <= VNum; i++)
{
cout << i << "." << getPalceByOrder(i) << " ";
free(getPalceByOrder(i));
}
cout << "\n********************************************************************************";
}
//采用邻接矩阵表示法构造有向图G,此图为带权距离图
void CreateMGraph(MGraph *G)
{
int i, j;
for (i = 1; i <= VNum; i++) //输入顶点信息
G->vexs[i] = (char)i;
for (i = 1; i <= VNum; i++)
{
for (j = 1; j <= VNum; j++)
{
G->arcs[i][j] = Maxint; // 初始化邻接矩阵
}
}
G->arcs[1][2] = G->arcs[2][1] = 137;
G->arcs[1][3] = G->arcs[3][1] = 695;
G->arcs[1][19] = G->arcs[19][1] = 668;
G->arcs[2][4] = G->arcs[4][2] = 674;
G->arcs[2][22] = G->arcs[22][2] = 704;
G->arcs[3][4] = G->arcs[4][3] = 349;
G->arcs[3][5] = G->arcs[5][3] = 511;
G->arcs[3][7] = G->arcs[7][3] = 534;
G->arcs[4][8] = G->arcs[8][4] = 651;
G->arcs[5][6] = G->arcs[6][5] = 842;
G->arcs[5][18] = G->arcs[18][5] = 676;
G->arcs[6][12] = G->arcs[12][6] = 967;
G->arcs[6][13] = G->arcs[13][6] = 1100;
G->arcs[7][11] = G->arcs[11][7] = 409;
G->arcs[8][10] = G->arcs[10][8] = 825;
G->arcs[9][10] = G->arcs[10][9] = 622;
G->arcs[10][11] = G->arcs[11][10] = 367;
G->arcs[11][12] = G->arcs[12][11] = 902;
G->arcs[11][14] = G->arcs[14][11] = 675;
G->arcs[11][16] = G->arcs[16][11] = 672;
G->arcs[12][13] = G->arcs[13][12] = 639;
G->arcs[12][16] = G->arcs[16][12] = 607;
G->arcs[14][15] = G->arcs[15][14] = 140;
G->arcs[16][17] = G->arcs[17][16] = 255;
G->arcs[18][19] = G->arcs[19][18] = 1145;
G->arcs[18][20] = G->arcs[20][18] = 216;
G->arcs[18][21] = G->arcs[21][18] = 1892;
G->arcs[22][23] = G->arcs[23][22] = 305;
G->arcs[22][25] = G->arcs[25][22] = 397;
G->arcs[23][24] = G->arcs[24][23] = 242;
}
/**
*用Dijkstra算法求有向网G的v1顶点到其他顶点v的最短路径P[v]及其权D[v]
*设G是有向图的邻接矩阵,若边不存在则G[i][j]=Maxint
*S[v]为真当且仅当v属于S,即已经求得从v1到v的最短路径
*/
void Dijkstra(MGraph *G, int v1, int n)
{
int D[MVNum], P2[MVNum];
int v, i, w, min;
int S[MVNum];
//初始化S和D
for (v = 1; v <= n; v++) {
S[v] = 0; //置空最短路径终点集
D[v] = G->arcs[v1][v]; //置初始的最短路径值
if (D[v] < Maxint)
P2[v] = v1; //v1是前趋双亲
else
P2[v] = 0; //v 无前趋
} // End_for
D[v1] = 0; S[v1] = 1; //S集初始时只有源点 源点到源点的距离为0
//开始循环每次求的V1到某个V顶点的最短路径并加V到S集中
for (i = 2; i <= n; i++)//其余n-1个顶点
{
min = Maxint; // 当前所知离v1顶点的最近距离设初值为∞
for (w = 1; w <= n; w++) //对所有顶点检查
if (!S[w] && D[w] < min)
{ //找离v1最近的顶点w并将其赋给v距离赋给min
v = w; //在S集之外的离v1最近的顶点序号
min = D[w]; //最近的距离
} //W顶点距离V1顶点更近
S[v] = 1; //将v并入S集
for (w = 1; w <= n; w++) //更新当前最短路径及距离
if (!S[w] && (D[v] + G->arcs[v][w] < D[w]))
{ //修改D2[w]和p2[w] w 属于 V-S
D[w] = D[v] + G->arcs[v][w]; //更新D2[w]
P2[w] = v;
} //End_if
} //End_for
cout << "路径长度(单位:km) 最短路径\n";
for (i = 1; i <= n; i++)
{
cout << D[i] << "\t\t" << getPalceByOrder(i);
free(getPalceByOrder(i));
v = P2[i];
while (v != 0) {
cout << "<-" << getPalceByOrder(v);
free(getPalceByOrder(v));
v = P2[v];
}
cout << endl;
}
}
int main()
{
MGraph * G;
int v;
G = (MGraph *)malloc(sizeof(MGraph));
CreateMGraph(G); //建立图的存储结构
printPlace();
cout << "\t\t\t\t请输入城市起点代号:";
cin >> v;
Dijkstra(G, v, VNum); //调用迪杰斯特拉算法
return 0;
}
import networkx as nx
import matplotlib.pyplot as plt
def shortest_path_graph(edge_labels,specify_edges):
"""
绘制最短路径图
:edge_labels 边标签,类型为字典
:specify_edges 指定的边标签:此处为最短路径的边标签,类型为列表
"""
G = nx.DiGraph()#生成一个空的有向图
G.add_edges_from(list(edge_labels.keys()))#添加有向图的边
black_edges = [edge for edge in G.edges() if edge not in specify_edges]#分离出除指定边之外的边,方便后面两者区分
pos = nx.spring_layout(G)#创建布局
nx.draw_networkx_nodes(G, pos, cmap=plt.get_cmap('jet'),
node_color = 'r', node_size = 50)#绘制节点
nx.draw_networkx_labels(G, pos,font_size=5)#添加节点标签,此处为地点名
nx.draw_networkx_edge_labels(G, pos,edge_labels=edge_labels,font_size=3)#绘制边的标签,此处为两点之前的权值(距离)
nx.draw_networkx_edges(G, pos, edgelist=specify_edges, edge_color='r',width=1)#绘制指定边
nx.draw_networkx_edges(G, pos, edgelist=black_edges, arrows=False,width=0.5)#绘制除指定的之外的边,去掉有向图默认的方向箭头
#增大图片的像素和分辨率
plt.rcParams['savefig.dpi'] =300 #图片像素
plt.rcParams['figure.dpi'] = 300 #分辨率
plt.show()#显示图形
place={('北京', '天津'):137, ('北京', '郑州'):695, ('北京', '呼和浩特'):668, ('天津', '徐州'):674, ('天津', '沈阳'):704,
('郑州', '徐州'):349, ('郑州', '西安'):511, ('郑州', '武汉'):534, ('徐州', '上海'):651,
('西安', '成都'):842, ('西安', '兰州'):676, ('成都', '贵阳'):967, ('成都', '昆明'):1100,
('武汉', '株洲'):409, ('上海', '南昌'):825, ('福州', '南昌'):622, ('南昌', '株洲'):367,
('株洲', '贵阳'):902, ('株洲', '广州'):675, ('株洲', '柳州'):672, ('贵阳', '昆明'):639,
('贵阳', '柳州'):607, ('郑州', '西安'):511, ('郑州', '武汉'):534, ('徐州', '上海'):651,
('广州', '深圳'):140, ('柳州', '南宁'):255, ('兰州', '呼和浩特'):1145, ('兰州', '西宁'):216,
('兰州', '乌鲁木齐'):1892, ('沈阳', '长春'):305, ('沈阳', '大连'):397, ('长春', '哈尔滨'):242
}
shortest_path_edges = [('贵阳', '株洲'),('株洲', '武汉'),('武汉', '郑州'),('郑州', '北京')]
shortest_path_graph(place,shortest_path_edges)
#include
#include
#include"stdafx.h"
#include
#include
#include
#define DLL_EXPORT __declspec(dllexport)
#define MVNum 100 //最大顶点数
#define VNum 25
#define Maxint 65535 //定义一个最大数,其意义为无穷大
#define MAX_STR_SIZE 500
using namespace std;
typedef char Vertextype;
typedef int Adjmatrix;
typedef struct
{
Vertextype vexs[MVNum]; //顶点数组 类型假定为char型
Adjmatrix arcs[MVNum][MVNum]; // 邻接矩阵 假定为int型
}MGraph;
//定义结构体来存储路径
typedef struct StructPath
{
char shortest_path[MAX_STR_SIZE];
}StructPath, *Path;
int D1[MVNum], p1[MVNum];
int D[MVNum][MVNum], p[MVNum][MVNum];
using namespace std;
extern "C" {
//采用邻接矩阵表示法构造有向图G,此图为带权距离图
DLL_EXPORT
void CreateMGraph(MGraph *G)
{
int i, j;
for (i = 1; i <= VNum; i++) //输入顶点信息
G->vexs[i] = (char)i;
for (i = 1; i <= VNum; i++)
{
for (j = 1; j <= VNum; j++)
{
G->arcs[i][j] = Maxint; // 初始化邻接矩阵
}
}
G->arcs[1][2] = G->arcs[2][1] = 137;
G->arcs[1][3] = G->arcs[3][1] = 695;
G->arcs[1][19] = G->arcs[19][1] = 668;
G->arcs[2][4] = G->arcs[4][2] = 674;
G->arcs[2][22] = G->arcs[22][2] = 704;
G->arcs[3][4] = G->arcs[4][3] = 349;
G->arcs[3][5] = G->arcs[5][3] = 511;
G->arcs[3][7] = G->arcs[7][3] = 534;
G->arcs[4][8] = G->arcs[8][4] = 651;
G->arcs[5][6] = G->arcs[6][5] = 842;
G->arcs[5][18] = G->arcs[18][5] = 676;
G->arcs[6][12] = G->arcs[12][6] = 967;
G->arcs[6][13] = G->arcs[13][6] = 1100;
G->arcs[7][11] = G->arcs[11][7] = 409;
G->arcs[8][10] = G->arcs[10][8] = 825;
G->arcs[9][10] = G->arcs[10][9] = 622;
G->arcs[10][11] = G->arcs[11][10] = 367;
G->arcs[11][12] = G->arcs[12][11] = 902;
G->arcs[11][14] = G->arcs[14][11] = 675;
G->arcs[11][16] = G->arcs[16][11] = 672;
G->arcs[12][13] = G->arcs[13][12] = 639;
G->arcs[12][16] = G->arcs[16][12] = 607;
G->arcs[14][15] = G->arcs[15][14] = 140;
G->arcs[16][17] = G->arcs[17][16] = 255;
G->arcs[18][19] = G->arcs[19][18] = 1145;
G->arcs[18][20] = G->arcs[20][18] = 216;
G->arcs[18][21] = G->arcs[21][18] = 1892;
G->arcs[22][23] = G->arcs[23][22] = 305;
G->arcs[22][25] = G->arcs[25][22] = 397;
G->arcs[23][24] = G->arcs[24][23] = 242;
}
/**
*用Dijkstra算法求有向网G的v1顶点到其他顶点v的最短路径P[v]及其权D[v],并返回最短路径
*设G是有向图的邻接矩阵,若边不存在则G[i][j]=Maxint
*S[v]为真当且仅当v属于S,即已经求得从v1到v的最短路径
*/
DLL_EXPORT
Path __stdcall Dijkstra(MGraph *G, int v1, int n)
{
int D[MVNum], P2[MVNum];
int v, i, w, min;
int S[MVNum];
Path path = (Path)malloc(sizeof(StructPath));
//初始化S和D
for (v = 1; v <= n; v++) {
S[v] = 0; //置空最短路径终点集
D[v] = G->arcs[v1][v]; //置初始的最短路径值
if (D[v] < Maxint)
P2[v] = v1; //v1是前趋双亲
else
P2[v] = 0; //v 无前趋
} // End_for
D[v1] = 0; S[v1] = 1; //S集初始时只有源点 源点到源点的距离为0
//开始循环每次求的V1到某个V顶点的最短路径并加V到S集中
for (i = 2; i <= n; i++)//其余n-1个顶点
{
min = Maxint; // 当前所知离v1顶点的最近距离设初值为∞
for (w = 1; w <= n; w++) //对所有顶点检查
if (!S[w] && D[w] < min)
{ //找离v1最近的顶点w并将其赋给v距离赋给min
v = w; //在S集之外的离v1最近的顶点序号
min = D[w]; //最近的距离
} //W顶点距离V1顶点更近
S[v] = 1; //将v并入S集
for (w = 1; w <= n; w++) //更新当前最短路径及距离
if (!S[w] && (D[v] + G->arcs[v][w] < D[w]))
{ //修改D2[w]和p2[w] w 属于 V-S
D[w] = D[v] + G->arcs[v][w]; //更新D2[w]
P2[w] = v;
} //End_if
} //End_for
cout << "length(km) path\n";
char temp[3]="";
char store[MAX_STR_SIZE]="";//必须初始化,用来存储最短路径
for (i = 1; i <= n; i++)
{
cout << D[i] << "\t" << i;
_itoa(i, temp, 10);//将数字转换为字符串
strcat(store, temp);//若temp和store在开始未初始化,则此步会导致Python在加载dll时崩溃
v = P2[i];
while (v != 0) {
cout << "<-" << v;
_itoa(v, temp, 10);
strcat(store, " ");
strcat(store, temp);
v = P2[v];
}
strcat(store, ";");
cout << endl;
}
strcpy(path->shortest_path, store);
return path;
}
//接收Python传入的顶点,并返回最短路径
DLL_EXPORT
Path getShortestPath(int v)
{
MGraph * G;
G = (MGraph *)malloc(sizeof(MGraph));
Path path = (Path)malloc(sizeof(StructPath));
CreateMGraph(G); //建立图的存储结构
path=Dijkstra(G, v, VNum); //调用迪杰斯特拉算法
return path;
}
}
import networkx as nx
import matplotlib.pyplot as plt
import ctypes
DLL_PATH = 'C:/Users/huerfeng/source/repos/Dijkstra/x64/Debug/dijkstra.dll'
lib = ctypes.WinDLL(DLL_PATH)#加载动态链接库dll
MAX_STR_SIZE=500
"""
定义一个 c 类型的结构体,需要定义一个类
其中有两个要点:
1. 类必须继承自 ctypes.Structure
2. 描述这个结构体的“容貌”
第一点很简单, class XXX(Structure) 就 OK。
要做到第二点,则必须在自定义的 c 结构体类中定义一个名为 _fields_ 的属性,并赋值给如上的一个列表
_fields_ 的属性要与c/c++结构体返回的结构相对应
"""
class StructPath(ctypes.Structure):
_fields_ = [("shortest_path", ctypes.c_char * MAX_STR_SIZE)]
#POINTER(type) 返回一个类型,这个类型是指向 type 类型的指针类型, type 是 ctypes 的一个类型。
lib.getShortestPath.restype = ctypes.POINTER(StructPath)#将c/c++的返回类型与Python接收类型对应
city_code_dic={1:"北京",2:"天津",3:"郑州",4:"徐州",5:"西安",6:"成都",
7:"武汉",8:"上海",9:"福州",10:"南昌",11:"株洲",12:"贵阳",
13:"昆明",14:"广州",15:"深圳",16:"柳州",17:"南宁",18:"兰州",
19:"呼和浩特",20:"西宁", 21:"乌鲁木齐",22:"沈阳",23:"长春",
24:"哈尔滨",25:"大连"
}
city_dic={('北京', '天津'):137, ('北京', '郑州'):695, ('北京', '呼和浩特'):668, ('天津', '徐州'):674, ('天津', '沈阳'):704,
('郑州', '徐州'):349, ('郑州', '西安'):511, ('郑州', '武汉'):534, ('徐州', '上海'):651,
('西安', '成都'):842, ('西安', '兰州'):676, ('成都', '贵阳'):967, ('成都', '昆明'):1100,
('武汉', '株洲'):409, ('上海', '南昌'):825, ('福州', '南昌'):622, ('南昌', '株洲'):367,
('株洲', '贵阳'):902, ('株洲', '广州'):675, ('株洲', '柳州'):672, ('贵阳', '昆明'):639,
('贵阳', '柳州'):607, ('郑州', '西安'):511, ('郑州', '武汉'):534, ('徐州', '上海'):651,
('广州', '深圳'):140, ('柳州', '南宁'):255, ('兰州', '呼和浩特'):1145, ('兰州', '西宁'):216,
('兰州', '乌鲁木齐'):1892, ('沈阳', '长春'):305, ('沈阳', '大连'):397, ('长春', '哈尔滨'):242
}
def getCityByCode(dic, code):
"""
得到城市对应的代号
:code 城市代号
:return 返回城市名
"""
return dic.get(int(code), -1)
def getCodeByCity(dic, city):
"""
得到城市对应的代号
:city 地点名
:return 返回城市代号
"""
for key, value in dic.items():
if value == city:
return key
return -1
def check_input(city_code, city):
"""
检测用户输入的地点是否存在
:city_code 需要检测的城市代号
:return 返回输入正确的城市代号
"""
while city_code == -1:
print("没有查询到与", city, "的相关路线,请重新输入:")
city = input("")
city_code = getCodeByCity(city_code_dic, city)
return city_code
def shortest_path_graph(edge_labels, specify_edges):
"""
绘制最短路径图
:edge_labels 边标签,类型为字典
:specify_edges 指定的边标签:此处为最短路径的边标签,类型为列表
"""
G = nx.DiGraph() # 生成一个空的有向图
G.add_edges_from(list(edge_labels.keys())) # 添加有向图的边
black_edges = [edge for edge in G.edges() if edge not in specify_edges] # 分离出除指定边之外的边,方便后面两者区分
pos = nx.shell_layout(G) # 创建布局
# 布局
# circular_layout:节点在一个圆环上均匀分布
# random_layout:节点随机分布
# shell_layout:节点在同心圆上分布
# spring_layout: 用Fruchterman-Reingold算法排列节点
# spectral_layout:根据图的拉普拉斯特征向量排列节点
nx.draw_networkx_nodes(G, pos, cmap=plt.get_cmap('jet'),
node_color='r', node_size=60) # 绘制节点
nx.draw_networkx_labels(G, pos, font_size=5) # 添加节点标签,此处为地点名
nx.draw_networkx_edge_labels(G, pos, edge_labels=edge_labels, font_size=5) # 绘制边的标签,此处为两点之前的权值(距离)
nx.draw_networkx_edges(G, pos, edgelist=specify_edges, edge_color='r', width=1) # 绘制指定边
nx.draw_networkx_edges(G, pos, edgelist=black_edges, arrows=False, width=0.5) # 绘制除指定的之外的边,去掉有向图默认的方向箭头
# 增大图片的像素和分辨率
plt.rcParams['savefig.dpi'] = 300 # 图片像素
plt.rcParams['figure.dpi'] = 300 # 分辨率
plt.show() # 显示图形
def show_shortest_path_graph(city_dic, path_list):
"""
显示最短路径图
:city_dic 城市
:path_list 最短路径列表
"""
for li in path_list:
li = li.split(" ")
li.reverse()
shortest_path_list = []
for i in range(1, len(li)):
shortest_path_list.append((getCityByCode(city_code_dic, li[i - 1]), getCityByCode(city_code_dic, li[i])))
print(getCityByCode(city_code_dic, li[i - 1]), "->", end="")
if i == len(li) - 1:
print(getCityByCode(city_code_dic, li[i]), end="")
shortest_path_graph(city_dic, shortest_path_list)
def user_select():
"""
给用户提供选择,是当前城市到其他城市所有的最短距离 还是到某个城市的最短距离
"""
city = input("请输入你所在的城市:")
city_code = getCodeByCity(city_code_dic, city)
city_code = check_input(city_code, city)
p = lib.getShortestPath(city_code)
user_select = eval(input("请选择:1,从当前城市到其他城市所有的最短距离 2,从当前城市到某个城市的最短距离:"))
if (user_select == 1):
path_list = str(p.contents.shortest_path)[2:-2].split(";")
show_shortest_path_graph(city_dic, path_list)
elif user_select == 2:
dst_city = input("输入终点:")
dst_city_code = getCodeByCity(city_code_dic, dst_city)
dst_city_code = check_input(dst_city_code, city)
path_list = str(p.contents.shortest_path)[2:-2].split(";")
show_shortest_path_graph(city_dic, [path_list[dst_city_code - 1]])
else:
print("输入的选择无效,无法为你规划路线")
user_select()
user_select()