拿来就能用!Dijkstra 算法实现快递路径优化
作者 | 李秋键
责编 | 伍杏玲
出品 | AI科技大本营(ID:rgznai100)
近几年来,快递行业发展迅猛,其中的程序设计涉及到运送路径的最优选择问题,下面我们尝试模拟实现快递路径优化问题,假设为快递公司设计快递投递路线优化程序:
(1)每个市有中转分发点,有些城市之间有直通路线,有些没有直通路线;(2)城市间的运费计算公式为:距离*1;
(3)假设投递包裹的尺寸、重量都一样,每条运输线路有个运力上限(即只能运输多少个包裹)。
按照第(1)点要求,假设一共有7个城市,分别为A、B、C、D、E、F、G。
按照的第(2)点和第(3)点要求,假设各城市间满足的路线布局和费用,以及运力上限分别如下图所示:
实现的效果如下:
程序运行结果图(运力上限优化)
程序思路
(1) 建立各城市对象;
(2) 建立城市对象之间的关联,关联值包括四点(城市路径起点,城市路径终点,路径距离值,路径运力上限)。如城市A和城市B之间的关联值为[A,B,3,4];
(3) 考虑到包裹尺寸、重量都是一样的,故假设其单位量都为1,即可以不用考虑。随机产生需要运输的当天包裹m个,产生的每个包裹对象的参量属性包括如下三点(包裹变量序号,包裹运输起点,包裹运输终点)。比如产生包裹1属性为[1,A,G];
(4) 系统的可视化;
(5) 路径优化算法的设计。
基于上面分析,我们可以开始着手模拟设计“快递投递路线优化程序”,该系统主要包含三个基本模块:
1、总体框架程序,其中包括城市对象(城市路径起点,城市路径终点,路径距离值,路径运力上限),包裹对象(包裹变量序号,包裹运输起点,包裹运输终点),包裹数等基本参数设计;
2、可视化路径选择,将路径选择的结果输出后,按照基本的流程图形式进行全部输出;
3、优化算法设计,找到费用最低的路径方法。
数据结构设计
首先是程序预准备,包括定义宏、结构体。
参数定义:
这里定义城市最大数量为20。按照我们设定的7个城市,足够使用。
#define MAX_VERTEX_NUM 20
定义INFINITE,用来当做无穷大,即表示两个城市之间没有直接路线或达到运力上限直连路线作废。
#define INFINITE 10000
图结构体的定义:
参数包括城市数vertexNum,城市名称vertex[MAX_VERTEX_NUM]数组,各城市路线距离值数组arc[MAX_VERTEX_NUM][MAX_VERTEX_NUM],各城市路线运力上限数组limit[MAX_VERTEX_NUM][MAX_VERTEX_NUM],各城市运力上限数组count[MAX_VERTEX_NUM][MAX_VERTEX_NUM]。
代码如下:
typedef struct
{intvertexNum;charvertex[MAX_VERTEX_NUM];intarc[MAX_VERTEX_NUM][MAX_VERTEX_NUM];intlimit[MAX_VERTEX_NUM][MAX_VERTEX_NUM];intcount[MAX_VERTEX_NUM][MAX_VERTEX_NUM];
}Graph,*PGraph;
存储距离和路径辅助结构体建立:
其中包括距离distance和路径数组path[MAX_VERTEX_NUM]。
代码如下:
//辅助数组中的元素定义
typedef struct
{int distance;int path[MAX_VERTEX_NUM];
}ArrayNode;
双向图的建立:
按照上文设定好的题目条件,建立双向网络,存储着城市属性,包括城市路线距离值arc参数、运力上限限流值limit参数、统计各路线已经使用的情况count参数。
代码如下:
void createdGraph(PGraph g)
{ int i,j; g->vertexNum=7; for(i=0;i<g->vertexNum;i++) g->vertex[i]='A'+i; for(i=0;i<g->vertexNum;i++) for(j=0;j<g->vertexNum;j++) g->arc[i][j]=0; for(i=0;i<g->vertexNum;i++) for(j=0;j<g->vertexNum;j++) g->limit[i][j]=0;
//这个属性是每条路的距离长度g->arc[0][4]=3; g->arc[0][2]=5; g->arc[1][2]=4; g->arc[1][3]=4; g->arc[1][4]=4; g->arc[1][5]=3; g->arc[2][5]=5; g->arc[2][6]=3;g->arc[3][4]=4; g->arc[3][5]=7; g->arc[5][6]=5;//双向g->arc[2][0]=5;g->arc[2][1]=4;g->arc[3][1]=4;g->arc[4][0]=3;g->arc[4][1]=4;g->arc[4][3]=4;g->arc[5][1]=3;g->arc[5][2]=5;g->arc[5][3]=7;g->arc[6][2]=3;g->arc[6][5]=5;
//这个属性是每条路上的限流,即运力上限g->limit[0][4]=4; g->limit[0][2]=7; g->limit[1][2]=4; g->limit[1][3]=5; g->limit[1][4]=6; g->limit[1][5]=7; g->limit[2][5]=7; g->limit[2][6]=6;g->limit[3][4]=5; g->limit[3][5]=8; g->limit[5][6]=6;g->limit[2][0]=7;g->limit[2][1]=4;g->limit[3][1]=5;g->limit[4][0]=4;g->limit[4][1]=6;g->limit[4][3]=5;g->limit[5][1]=7;g->limit[5][2]=7;g->limit[5][3]=8;g->limit[6][2]=6;g->limit[6][5]=6;
//这个属性是计算该条路走过了多少次,计数,判断会不会超过限流(运力上限),超过限流(运力上限)就把距离设成很大的数,即不考虑。初始情况下都是为0for(i=0;i<g->vertexNum;i++) for(j=0;j<g->vertexNum;j++) g->count[i][j]=0;g->count[0][4]=0; g->count[0][2]=0; g->count[1][2]=0; g->count[1][3]=0; g->count[1][4]=0; g->count[1][5]=0; g->count[2][5]=0; g->count[2][6]=0;g->count[3][4]=0; g->count[3][5]=0; g->count[5][6]=0;g->count[2][0]=0;g->count[2][1]=0;g->count[3][1]=0;g->count[4][0]=0;g->count[4][1]=0;g->count[4][3]=0;g->count[5][1]=0;g->count[5][2]=0;g->count[5][3]=0;g->count[6][2]=0;g->count[6][5]=0;
}
算法设计
这里使用的算法是最短路径算法——Dijkstra。但考虑到路线的双向问题、限流问题等,需要对算法进行优化和修改。
算法描述
迪杰斯特拉(Dijkstra)算法是典型最短路径算法,用于计算一个节点到其他节点的最短路径。它的主要特点是以起始点为中心向外层层扩展(广度优先搜索思想)[1],直到扩展到终点为止。
基本思想:
通过Dijkstra计算图G中的最短路径时,需要指定起点s(即从顶点s开始计算)[2]。此外,引进两个集合S和U。S的作用是记录已求出最短路径的顶点(以及相应的最短路径长度),而U则是记录还未求出最短路径的顶点(以及该顶点到起点s的距离)[3]。初始时,S中只有起点s;U中是除s之外的顶点,并且U中顶点的路径是”起点s到该顶点的路径”[4]。然后,从U中找出路径最短的顶点,并将其加入到S中;接着,更新U中的顶点和顶点对应的路径[5]。然后,再从U中找出路径最短的顶点,并将其加入到S中[6];接着,更新U中的顶点和顶点对应的路径……重复该操作,直到遍历完所有顶点。
操作步骤:
(1) 初始时,S只包含起点s;U包含除s外的其他顶点,且U中顶点的距离为”起点s到该顶点的距离”[例如,U中顶点v的距离为(s,v)的长度,然后s和v不相邻,则v的距离为∞[7]。
(2) 从U中选出”距离最短的顶点k”,并将顶点k加入到S中[8];同时,从U中移除顶点k。
(3) 更新U中各个顶点到起点s的距离[9]。之所以更新U中顶点的距离,是由于上一步中确定了k是求出最短路径的顶点,从而可以利用k来更新其它顶点的距离[10];例如,(s,v)的距离可能大于(s,k)+(k,v)的距离。
(4) 重复步骤(2)和(3),直到遍历完所有顶点[11]。
单纯的看上面的理论可能比较难以理解,下面通过实例来对该算法进行说明。
最短路径算法代码:
(1)首先是计算from到各个顶点的直接距离,即初始化shortestPath数组:
for(i=0;i<g->vertexNum;i++){ if(from==i){ shortestPath[i].distance=0; shortestPath[i].path[0]=i; flag[from]=1; } else if(g->arc[from][i]>0){ shortestPath[i].path[0]=from; shortestPath[i].path[1]=i; shortestPath[i].distance=g->arc[from][i]; }else shortestPath[i].distance=INFINITE; }
(2)然后每次求出一个最短路径:
while(n<g->vertexNum){ //选择shortestPath中距离最小的,求出from到这个顶点的最短路径 index=-1; for(i=0;i<g->vertexNum;i++){ if(i==from) continue; if(flag[i]==0 && index==-1 && shortestPath[i].distance!=INFINITE) index=i; if(flag[i]==0 && index!=-1 && shortestPath[i].distance<shortestPath[index].distance) index=i; } flag[index]=1; //修改到各个顶点的最短路径 for(i=0;i<g->vertexNum;i++){ if(i==from) continue; if(g->arc[index][i]>0 && g->arc[index][i]+shortestPath[index].distance<shortestPath[i].distance){ shortestPath[i].distance=g->arc[index][i]+shortestPath[index].distance; //修改路径 j=0; while(1){ shortestPath[i].path[j]=shortestPath[index].path[j]; if(shortestPath[index].path[j]==index) break; j++; } shortestPath[i].path[j+1]=i; } } n++; }
(3)输出结果:
printf("%c到%c的最短路径长度是:%d\n",from+'A',to+'A',shortestPath[to].distance); printf("经过的顶点: \n"); i=0; while(1){ printf("%-3c\n",shortestPath[to].path[i]+'A'); if(shortestPath[to].path[i]==to) break; i++; } printf("\n");
(4)程序主函数调用:这里设定的是产生25个包裹。包裹1为{1,A,F},2为{2,A,G},3为{3,B,F},4为{4,D,G},5为{5,C,F};其他包裹都是从A到F。
void main()
{ int num=25;int k=0;Graph graph; char from,to; createdGraph(&graph); //from为包裹起点城市,to为包裹终点城市;假设包裹1为{1,A,F},2为{2,A,G},3为{3,B,F},4为{4,D,G},5为{5,C,F}while(k<num){k++;if(k==1){from='A';to='F'; printf("包裹%d>>>>起始点为%c,终点为%c,得到的最终路径为:\n",k,from,to);Dijkstra(&graph,from-'A',to-'A'); }else if(k==2){from='A';to='G'; printf("包裹%d>>>>起始点为%c,终点为%c,得到的最终路径为:\n",k,from,to);Dijkstra(&graph,from-'A',to-'A'); }else if(k==3){from='B';to='F'; printf("包裹%d>>>>起始点为%c,终点为%c,得到的最终路径为:\n",k,from,to);Dijkstra(&graph,from-'A',to-'A'); }else if(k==4){from='D';to='G'; printf("包裹%d>>>>起始点为%c,终点为%c,得到的最终路径为:\n",k,from,to);Dijkstra(&graph,from-'A',to-'A'); }else if(k==5){from='C';to='F'; printf("包裹%d>>>>起始点为%c,终点为%c,得到的最终路径为:\n",k,from,to);Dijkstra(&graph,from-'A',to-'A'); }else{from='A';to='F'; printf("包裹%d>>>>起始点为%c,终点为%c,得到的最终路径为:\n",k,from,to);Dijkstra(&graph,from-'A',to-'A'); }}}
(5)结果展示:最终输出的结果就是没有考虑路线运力上限的输出结果:
程序运行结果图(未考虑运力上限优化)
上图可以明显的看出,运输第25次包裹时,从A运到F它还是选择了ACF路线。题目中设定的是A到C的运力上限只有5次,这里却走了AC路线高达20次,由此可见仅使用最短路径算法是不适应要求的,故下面将对算法加入限流条件进行优化。
算法优化
加入限流考虑,即运力上限,每条路只能走一定次数:
(1)首先初始化数组count,用来统计各个路线已经走过的次数。初始条件都为0,未走过一次:
//这个属性是计算该条路走过了多少次,计数,判断会不会超过限流(运力上限),超过限流(运力上限)就把距离设成很大的数,即不考虑。初始情况下都是为0for(i=0;i<g->vertexNum;i++) for(j=0;j<g->vertexNum;j++) g->count[i][j]=0;g->count[0][4]=0; g->count[0][2]=0; g->count[1][2]=0; g->count[1][3]=0; g->count[1][4]=0; g->count[1][5]=0; g->count[2][5]=0; g->count[2][6]=0;g->count[3][4]=0; g->count[3][5]=0; g->count[5][6]=0;g->count[2][0]=0;g->count[2][1]=0;g->count[3][1]=0;g->count[4][0]=0;g->count[4][1]=0;g->count[4][3]=0;g->count[5][1]=0;g->count[5][2]=0;g->count[5][3]=0;g->count[6][2]=0;g->count[6][5]=0;
(2)判断每条路已使用过的次数是否超过限流(运力上限),如果超过了,就把路距离设为INFINITE,表示此条路不再考虑:
for(i=0;i<g->vertexNum;i++) for(j=0;j<g->vertexNum;j++) if (g->count[i][j]>g->limit[i][j] || g->count[j][i]>g->limit[j][i]){g->arc[i][j]=INFINITE; g->arc[j][i]=INFINITE;}
(3)计数。每次选择最短路径的路线的各个支路都要加上1:因为路线是双向的,需要双向都加上1。
i=0; while(1){ //这条路被走过一次计数count就加上1//printf("%d",shortestPath[to].path[i]+0);printf("%-3c\n",shortestPath[to].path[i]+'A'); if(shortestPath[to].path[i]==to) break;
//这条路被走过一次计数count就加上1g->count[shortestPath[to].path[i]+0][shortestPath[to].path[i+1]+0]+=1;g->count[shortestPath[to].path[i+1]+0][shortestPath[to].path[i]+0]+=1;i++; }
(4)优化算法结果:
程序运行结果图(运力上限优化)
由上图发现,在考虑运力上限后,算法符合题目要求。同样是从A运到F目的地,AC这条路运力上限为5,我们看到包裹11走的是ACF,但是包裹12走的是AEBF,因为AC已经达到了运力上限,不能再走,故更换路线!
同理在包裹17后,AE这条路也达到了运力上限,已经没办法从A到F了,因为AC和AE这两条路都达到了运力上限,故程序找不到路线,符合设定的要求!
源码链接:https://pan.baidu.com/s/1iKlJ3At9mXpVmv3N8RQX0Q
提取码:0aib
作者简介:李秋键,CSDN博客专家,CSDN达人课作者。硕士在读于中国矿业大学,开发有taptap竞赛获奖等。
更多精彩推荐
☞30 周岁的 Python,“虐”我 20 年☞快过HugeCTR:用OneFlow轻松实现大型推荐系统引擎☞高手的习惯:pythonic风格代码☞对比四种爬虫定位元素方法,你更爱哪个?
点分享点收藏点点赞点在看
相关文章:

92号油的发动机能加97吗?标号越高不代表就越好
当我们买了车(不管是二手还是新车吧),我们都能很快知道这车的发动机是加什么标号的汽油的。这些信息都能在说明书或者在加油盖附近找到。但同时很多粉丝也在问,我的车是92的,但我加97的话发动机会不会更有力更省油&…
不要跳槽!!!
在这个俗称“金三银四”的跳槽季,很多人都蠢蠢欲动,想要拿更高的薪资,想要去更大的平台......但我也要奉劝大家一句:三思而后行。确实,春节过后,大家都在为开年做准备,跳槽也好,学习…

按下回车键指向下一个位置的一个函数
functiontofocus(itemname) // 按回车置下一个位置 2{ 3vara 4aeval( " document.vouch. "itemname) 5a.focus() 6} 7 在控件中使用onkeypress" javascrip:if(window.event.keyCode13){tofocus(nextformname)}提取下一个控件名

初涉LR,关联
摘要:Loadrunner是一种很好的性能测试工具,它通过对创建Vuser脚本、定义场景、运行场景、分析结果四大模块来进行性能负载测试。 在回放脚本时有时会出现运行不成功的情况,有可能是因为之前所录制的参数与现实的不一致的原因,比如…

新型智能电视攻击,9成国外设备或受影响
在近日举办的欧洲广播联盟媒体网络安全研讨会上,瑞士安全研究员Rafael Scheel分享了一种针对智能电视的新型攻击方法:通过发送恶意数字视频地面广播信号(DVB-T),实现远程控制电视设备并获得智能电视root访问权限,之后利用电视可以…

按esc键退出的一个函数
1functionesckey(keycode) // 按esc键退出2{ 3if (keycode 27 ) 4{ 5window.close() 6} 7}

Linux挂载卸载光盘实践
在Linux下有时候需要挂载光盘,拷贝文件或安装系统,例如拷贝Redhat操作系统镜像文件等。下面介绍一下在Linux系统下挂载、卸载光盘的方法。 在Linux系统中,每一个物理设备都可以看做是一个文件,而像硬盘、光盘等物理设备文件都在/d…

程序员晒元宵节福利,网友:看了我想砸键盘......
再过几天就到元宵节了,又到了互联网大厂晒福利、拉仇恨的时候了。小编在脉脉上看到许多不愿透露姓名的网友的爆料,一起来看看吧。有的网友说收到了汤圆,还有员工说收到了四盒草莓,但是还有网友透露自己喜提加班,更有甚…

tomcat_deploy 平滑启动脚本
1.此脚本需要nginx安装ginx_upstream_check_module 配置完成平滑重启 2.脚本内容如下: 1 #!/bin/bash2 cat <<MADAY3 ---------------------------------------------------------4 -------------------------------------------------------------5 A)服…

判断输入是否为中文的函数
1functionischinese(s){ 2varrettrue ; 3for ( vari0 ;i < s.length;i) 4 retret &&(s.charCodeAt(i) > 10000 ); 5returnret; 6 }

OpenERP与Python 元编程
Python元编程被称为“黑魔法”。Python界的传奇人物Tim Peters有云: 引用 Python的元编程这种黑魔法99%的人都无需了解,如果你拿不准是否应该用到它时,你不需要它. OpenERP基本遵循了Tim Peters的教诲,但是却在6.1版本之后忍不住触…

联手中科大、浙大、华科大等高校,阿里研发4项最新AI安全技术
随着互联网技术对抗环境日益复杂化,各大网络平台页面可供用户上传并做展示的内容,都可能面临恶意攻击,例如黑灰产团伙会发布色情等不良图片和视频,以及发布可能涉嫌抄袭侵权的商品或其他违规信息,甚至一些黑灰产团伙还…

联想S820 MIUI刷机包 MIUI 4.4.30 流畅执行 在线主题破解
ROM介绍 破解免费使用MIUI全部主题(方法:开机开启Root权限,进入WSM工具箱→安装二进制文件→重新启动→再次进入WSM工具箱→两个工具打上勾→重新启动),然后尽情奔放吧 .加入V4A音效 .加入安卓4.4切换特效 .加大外放音量。不爆音 .集成 WSM …

列表框操作函数集合
1 /*列表框互相操作函数集 */23// 描述: 添加不重复列表框元素4functionselAdd( srcList, dstList )5 {6varselectedIndex newArray();7varcount 0 ;89for( i0 ; i < srcList.options.length; i ){1011if( srcList.options[i].selected ){1213selectedIndex[count] i;14…

看过漫改,但你看过「改漫」吗?AI 一键让影视变漫画
作者 | 神经小兮来源 | HyperAI超神经头图 | 下载于视觉中国把影视剧变成漫画,是怎样的一种神操作?来自大连理工大学和香港城市大学的团队,最新提出的 AI 框架,可自动将影视剧转换为漫画。从此,观影追剧又多了一种打开…

跨越企业的“中等收入陷阱”
在国际经济学中,有一个“中等收入陷阱”的概念,含义为:新兴市场国家突破人均GDP1000美元的“贫困陷阱”后,很快会奔向1000美元至3000美元的“起飞阶段”;但到人均GDP3000美元附近以后,快速发展中积聚的矛盾…

docker 数据卷与容器卷
2019独角兽企业重金招聘Python工程师标准>>> 容器中管理数据主要有两种方式: 数据卷(Data Volumes) 数据卷容器(Data Volumes Dontainers) 数据卷 使用-v可以挂载一个本地的目录到容器中作为数据卷。 [root…

document.all与WEB标准
1、DOM WEB标准现在可真是热门中热门,不过下面讨论的是一个不符合标准的document.all[]。DOM--DOCUMENT OBJECT MODEL文档对象模型,提供了访问文档对象的方法.例如文档中有一个table,你要改变它的背景颜色,那就可…

终于有人把Python讲清楚了!
经常有人问我,Python初学者该怎么学好Python?其实从事Python开发的这些年中,我见过很多相关的教程和书籍,他们大都这样讲 :先介绍 Python 的基本语法规则、list、dict、tuple 等数据结构,然后再介绍字符串处…

开源 免费 java CMS - FreeCMS1.5-建站向导
2019独角兽企业重金招聘Python工程师标准>>> 下载地址:http://code.google.com/p/freecms/ 建站向导 从FreeCMS 1.5开始支持 为了方便用户创建站点,系统提供了建站向导功能。 从左侧管理菜单点击建站向导进入。 第一步:创建…

Python实战之网络编程socket学习笔记及简单练习
sk socket.socket(socket.AF_INET,socket.SOCK_STREAM,0) 参数一:地址簇 socket.AF_INET IPv4(默认) socket.AF_INET6 IPv6 socket.AF_UNIX 只能够用于单一的Unix系统进程间通信 参数二:类型 socket.SOCK_STREAM 流式socke…

用IE重起计算机或者关机
<script language"JavaScript"> var Applicationnew ActiveXObject(Shell.Application.1); </script> <button οnclickApplication.ShutdownWindows();>关机</button><br> <button οnclickApplication.Suspend();>挂起</bu…

系统故障分析和排查
日志的功能 用于记录系统、程序运行中发生的各种事件通过阅读日志,有助于诊断和解决系统故障日志文件的分类内核及系统日志由系统服务syslog统一进行管理,日志格式基本相似用户日志记录系统用户登录及退出系统的相关信息程序日志由各种应用程序独立管理的…

用数据分析《你好,李焕英》“斐妈”爆红的真相
作者 | 俊欣来源 | 数据分析与篮球头图 | 下载于视觉中国《你好,李焕英》成为了春节档最热门最火爆的电影之一。截止目前,根据猫眼电影专业版的数据显示,该影片的票房已经突破了43亿;在抖音搜索上,因为其“好哭”而冲上…
[转] Android开发之如何保证Service不被杀掉(broadcast+system/app)
转发:原文链接http://blog.csdn.net/mad1989/article/details/22492519 序言 最近项目要实现这样一个效果:运行后,要有一个service始终保持在后台运行,不管用户作出什么操作,都要保证service不被kill,这可真…

如何使得按确定和取消按纽转到两个不同的页面!
问: 如何使得按确定和取消按纽转到两个不同的页面! confirm(),后面的具体参数是什么? ______________________________________________________________________________________________ 答1: 看个例子吧! <scrip…

PHP函数学习nl2br(),strlen(),mb_strlen()
2019独角兽企业重金招聘Python工程师标准>>> 1 nl2br($str): 注意:n之后的是字母L的小写,不要当做数字1. 函数作用:在$str中的每个新行(\n)之前插入HTML换行符( <br/> ) 示例: echo nl2br("One line.\nAnot…

携手中国电信、中国联通,华为正式发布首个5G超级刀片站 A+P 2.0天线商用网络
近日,在2021 MWC 上海期间,中国电信、中国联通携手华为发布首个5G超级刀片站 AP 2.0天线商用网络。 中国电信5G共建共享工作组高级项目经理李志军分享中国电信部署AP 2.0后的商用体验。AP 2.0颜值与实力兼备,解决了无空间部署5G以及5G挂高低…

『干货』分享你最喜欢的技巧和提示(Xcode,objective-c,swift,c...等等)
亲爱的读者们,你们好 !年底将近,分享从过去一年你最喜欢的技巧和建议作为礼物送给新手们。提交你的最喜欢的迅速或objc琐事,实用的提示,意外的发现,实用的解决方法,没用的迷恋,或不论什么其它你认为今年非常酷。就在以下写下你的评论! 笔者分享总结例如以下(本篇会不定期进行更…

一口一个,超灵活的Python迷你项目
来源 | 法纳斯特责编 | 寇雪芹头图 | 下载于视觉中国在使用Python的过程中,我最喜欢的就是Python的各种第三方库,能够完成很多操作。下面就给大家介绍22个通过Python构建的项目,以此来学习Python编程。大家也可根据项目的目的及提示ÿ…