运筹学中的节约里程法及其python实现_clarke,wright1964_vivian_ll的博客-程序员秘密

技术标签: 启发式算法  python  运筹学  

节约里程法简介

节约里程法,又称C-W算法 、节约算法或节约法,是由Clarke和Wright于1964年首次提出的,用来解决VRP问题,是重要的物流算法,是用来解决运输车辆数目不确定的问题的最有名的启发式算法。

节约里程法核心思想是依次将运输问题中的两个回路合并为一个回路,每次使合并后的总运输距离减小的幅度最大,直到达到一辆车的装载限制时,再进行下一辆车的优化。优化过程分为并行方式和串行方式两种。

利用节约法确定配送路线的主要出发点是,根据配送中心的运输能力和配送中心到各个用户以及各个用户之间的距离来制定使总的车辆运输的吨公里数最小的配送方案。另还需满足以下条件;(1)所有用户的要求;(2)不使任何一辆车超载;(3)每辆车每天的总运行时间或行驶里程不超过规定的上限;(4)用户到货时间要求。

基本原理

基本思想为为达到高效率的配送,使配送的时间最小距离最短成本最低,而寻找的最佳配送路线。

假定有n个访问地,把每个访问地看成一个点,并取其中的一个点为基点(起点),例如以1点为基点。首先将每个点与该基点相连接,构成线路1→j→1(j=2,3,…,n),这样就得到了一个具有n-1条线路的图(当然,这时尚未形成Hamilton回路)。旅行者按此线路访问这n个点所走的路程总和为
z = ∑ j = 2 n C 1 j z=\sum_{j=2}^n{C_{1j}} z=j=2nC1j
其中 C 1 j C_{1j} C1j为由点1到点j的路段长度,注意此处假定 C 1 j = C j 1 C_{1j}=C_{j1} C1j=Cj1(对所有j)。

若连接点i和点j,即使旅行者走弧(i,j)时(当然这时就不再经过弧(i,1)和弧(1,j),所引起的路程节约值s(i,j)可计算如下
s ( i , j ) = 2 c 1 i + 2 c 1 j − c 1 i + c 1 j + c i j ) = c 1 i + c 1 j − c i j s(i,j)=2c_{1i}+2c_{1j}-c_{1i}+c_{1j}+c_{ij})=c_{1i}+c_{1j}-c_{ij} s(i,j)=2c1i+2c1jc1i+c1j+cij)=c1i+c1jcij
对不同的点对(i,j),s(i,j)越大,旅行者通过弧(i,j)时所节约的路程越多,因而应优先将其安排到旅行线路中去,使旅行者旅行时通过这一条弧。

迭代步骤

在具体应用该方法时,可按以下步骤进行。

(1)选取基点,例如以1点为基点。将每个点与该基点相连接,得到n-1条线路1→j→1(j=2,3,…,n)。
(2)对不违背限制条件的所有可连接点对(i,j),如下计算其节约值(i,j不为基点)
s ( i , j ) = = c 1 i + c 1 j − c i j s(i,j)==c_{1i}+c_{1j}-c_{ij} s(i,j)==c1i+c1jcij
(3)将所有 s ( i , j ) s(i,j) s(i,j)按其值的大小由大到小排列。
(4)按 s ( i , j ) s(i,j) s(i,j)的上述顺序,逐个考察其端点i和j,若满足以下条件,就将弧(i,j)插入到线路中。其条件是:
a. 点i和点j不在一条线路上;
b. 点i和点j均与基点相邻。

(5)返回步骤(4),直至考察完所有可插入弧(i,j)为止。
通过以上各迭代步骤,使问题的解逐步得到改善,最后达到满意解(也有可能达到最优解)。

例题

已知配送中心P0向5个用户Pj配送货物,其配送路线网络、配送中心与用户的距离以及用户之间的距离如下图所示,配送中心有3台2t卡车和2台4t两种车辆可供使用。利用节约里程法制定最优的配送方案。
在这里插入图片描述
第一步,作运输里程表,列出配送中心到用户及用户间的最短距离。
在这里插入图片描述
第二步,按节约里程公式求得相应的节约里程数。
在这里插入图片描述
第三步,将节约里程按从大到小顺序排列。
在这里插入图片描述
第四步,根据载重量约束与节约里程大小,顺序连接各客户结点,形成两个配送线。
P2P3-P3P4-P2P4-P4P5-P1P2-P1P5-P1P3-P2P5-P3P5-P1P4
在这里插入图片描述
得出结果
配送线路一:
运量=1.7+0.9+1.4=4t
运行距离=8+4+5+7=24km
用一辆4t车运送,节约距离为18km
配送线路二:
运量=2.4+1.5=3.9t<4t
运行距离=8+10+16=34km
用一辆4t车运送,节约距离为2km

初始方案:配送线路5条,需要车5辆,配送距离=39*2=78km
优化后的方案:2条配送路线,2辆4t车,配送距离=24+34=58km

python程序

import csv
from operator import itemgetter

class Vrp():

    # -----------初始数据定义---------------------

    def __init__(self):
        self.mans = 9                                                   # 客户数量
        self.tons = 3                                                   # 车辆载重
        self.distanceLimit = 1000                                       # 车辆一次行驶的最大距离
        self.distance = []                                              # 各个客户及配送中心距离
        self.q = [0, 1, 1.1, 0.9, 0.8, 1.1, 0.4, 0.7, 0.8, 0.9]         # 10个客户分布需要的货物的需求量,第0位为配送中心自己
        self.savings = []                                               # 节约度
        self.Routes = []                                                # 路线
        self.Cost = 0                                                   # 总路程

    # -----------导入距离数据---------------------

    def datainput(self):
        with open("data.csv", "r") as csvfile:
            reader = csv.reader(csvfile)
            for line in reader:
                line = [float(x) for x in line]
                self.distance.append(line)

    # -----------节约算法主程序---------------------

    def savingsAlgorithms(self):
        saving = 0
        for i in range(1, len(self.q)):
            self.Routes.append([i])

        for i in range(1, len(self.Routes) + 1):                                                 # 使用Sij = Ci0 + C0j - Cij计算节约度
            for j in range(1, len(self.Routes) + 1):
                if i == j:
                    pass
                else:
                    saving = (self.distance[i][0] + self.distance[0][j]) - self.distance[i][j]
                    self.savings.append([i, j, saving])                                          # 将结果以元组形式存放在列表中

        self.savings = sorted(self.savings, key=itemgetter(2), reverse=True)                     # 按照节约度从大到小进行排序
        for i in range(len(self.savings)):
            print(self.savings[i][0],'--',self.savings[i][1], "  ",self.savings[i][2])           # 打印节约度

        for i in range(len(self.savings)):
            startRoute = []
            endRoute = []
            routeDemand = 0
            for j in range(len(self.Routes)):
                if (self.savings[i][0] == self.Routes[j][-1]):
                    endRoute = self.Routes[j]
                elif (self.savings[i][1] == self.Routes[j][0]):
                    startRoute = self.Routes[j]

                if ((len(startRoute) != 0) and (len(endRoute) != 0)):
                    for k in range(len(startRoute)):
                        routeDemand += self.q[startRoute[k]]
                    for k in range(len(endRoute)):
                        routeDemand += self.q[endRoute[k]]
                    routeDistance = 0
                    routestore = [0]+endRoute+startRoute+[0]
                    for i in range(len(routestore)-1):
                        # print(routestore[i],routestore[i+1])
                        # print(self.distance[routestore[i]][routestore[i+1]])
                        routeDistance += self.distance[routestore[i]][routestore[i+1]]

                    #print(routestore,"== ==:",routeDistance)

                    if (routeDemand <= self.tons) and (routeDistance <= self.distanceLimit):     # 按照限制规则对​​路线进行更改
                        self.Routes.remove(startRoute)
                        self.Routes.remove(endRoute)
                        self.Routes.append(endRoute + startRoute)
                    break

        for i in range(len(self.Routes)):
            self.Routes[i].insert(0, 0)
            self.Routes[i].insert(len(self.Routes[i]), 0)

    # -----------输出最终结果---------------------

    def printRoutes(self):
        for i in self.Routes:
            costs = 0
            for j in range(len(i)-1):
                costs += self.distance[i[j]][i[j+1]]
            print("路线:  ",i,"  路程:  ",costs)

    def calcCosts(self):
        for i in range(len(self.Routes)):
            for j in range(len(self.Routes[i]) - 1):
                self.Cost += self.distance[self.Routes[i][j]][self.Routes[i][j + 1]]

        print("\nTotal Distance: ", round(self.Cost, 3))

    # -----------Master函数---------------------

    def start(self):                      # Master函数,调用所有其他函数
        print("== == == == == == == == == == == == == == == 导入数据 == == == == == == == = == == == == == == == =")
        self.datainput()
        print("== == == 距离表 == == ==")
        for i in self.distance:
            print(i)
        print("== == == 需求表 == == ==")
        print(self.q)
        print("== == == 限制条件 == == ==")
        print("车辆最大载重:",self.tons)
        print("车辆最长运输距离:", self.distanceLimit)
        print("== == == == == == == == == == == == == == == 节约度 == == == == == == == = == == == == == == == =")
        self.savingsAlgorithms()          # 函数调用计算节省量并生成路线
        print("== == == == == == == == == == == == == == == 结果 == == == == == == == = == == == == == == == =")
        self.printRoutes()
        self.calcCosts()
        self.datainput()


if __name__ == '__main__':
	vrp = Vrp()
	vrp.start()

参考网址:
节约里程法-百度百科
节约里程算法的python实现(含github地址)
C# 节约里程法实现(原理很详细,还有例题)

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/vivian_ll/article/details/105917188

智能推荐

Android移植lame库(采用CMake)_Jaivne_Kuang的博客-程序员秘密

貌似许多人都是从lame库开始入门Android NDK开发的,在网上一搜一大堆详细教程。本篇的亮点是采用Google推荐的CMake工具(不是ndk-builder)来移植lame项目。重点写一下与ndk-builder的差异,而非教程。1.CMake是什么?这个是AndroidStudio 2.2以上的版本才可使用的,跟ndk-builder一样是一款原生构建工具。

【Android架构】基于MVP模式的Retrofit2+RXjava封装(一)_欢子-3824的博客-程序员秘密

#最近有个新项目要做,搭建框架的时候,顺便梳理了下MVP模式,特此记录,欢迎大家指正。项目地址GitHub一 、首先是依赖 compile 'com.google.code.gson:gson:2.8.0' compile 'com.squareup.okhttp3:okhttp:3.4.1' compile 'com.yanzhenjie:permiss...

机器学习—神经网络入门_sigmode_刘小航9527的博客-程序员秘密

神经网络基础在机器学习中,线性回归和逻辑回归用来处理相关问题很简单,当我们的数据集如下时:例如一个房屋有很多的特征时,特征点有多个,算出如上的逻辑回归,此时函数很复杂,所以这不是一个好办法。于是,我们使用神经网络算法可以来实现这个大量的特征算法:神经元:它有输入神经,输出神经,简而言之,神经元是一个计算的东西,它通过用户输入,然后输出,传递到其他节点,所以计算过程如下:神经网络如...

selenium+python自动化测试--读取excel数据_weixin_30340775的博客-程序员秘密

1、excel中数据(注意:数据是纯数字时,要将其设置成文本)2、读取excel文件函数封装文件名称:read_excel.pyimport xlrdclass ReadExcel(): def __init__(self, excelPath, sheetName="Sheet1"): self.data = xlrd.open_workb...

进制转换(十进制转二进制)_lbylzk的博客-程序员秘密

#include int main(){    int a[30]={0},i,j,n;    scanf("%d",&n);    i=0;    while(n>0)    {        a[i]=n%2;        n/=2;        i++;    }    if(i>0) i--;    for(j=i; j>=0; j-

.NET ASP.NET 页生命周期概述_豆皮没有豆的博客-程序员秘密

ASP.NET 页生命周期概述ASP.NET 页运行时,此页将经历一个生命周期,在生命周期中将执行一系列处理步骤。 这些步骤包括初始化、实例化控件、还原和维护状态、运行事件处理程序代码以及进行呈现。 了解页生命周期非常重要,因为这样做您就能在生命周期的合适阶段编写代码,以达到预期效果。如果您要开发自定义控件,就必须熟悉页生命周期,以便正确进行控件初始化,使用视图状态数据填充控件属性以及运行控件...

随便推点

用VS添加引用dll也会出错?你遇到过吗?_weixin_33671935的博客-程序员秘密

  使用C#开发,我们经常引用各种类库,我们通常是在Visual Studio中引用上面单击右键,添加引用...,浏览...,选择dll,确定,但是这样做会不会有什么问题呢?当然,有人到现在为止没有碰到过问题,下面来一个实例,来说一下其中可能出现的问题。 一、搭建Demo  这里就以SQLite数据库为例吧,我们新建一个控制台项目,名字就叫做SQLiteDemo吧,然后在项目中添加Lib文...

电子信息工程专业概论_本科专业介绍 | 电子信息工程_weixin_39842475的博客-程序员秘密

尽责 守信 求精 创新重庆工程学院是经教育部批准设立的一所以工学为主,以计算机、软件、电子信息为特色,经济管理和人文艺术等学科专业协调发展的全日制普通本科高校。学校2020年共开设24个本科专业招生,分别为数据科学与大数据技术、智能科学与技术、信息安全、计算机科学与技术、物联网工程、网络工程、软件工程、电子信息工程、通信工程、信息工程、自动化、机器人工程、数字媒体艺术、动画、艺术与...

tcp重组原理_kaiser丶H的博客-程序员秘密

1 .引言TCP/IP 协议现在已经广泛的被应用。数据在网络上应用 TCP/IP 协议进行传输的时候,需要将数据分成多个数据包。目前在网络安全领域都将用到 TCP 会话的重组问题。只有将数据包重组以后,才能还原一次完整的 TCP 会话。由于网络问题,数据包可能会经过不同的路由传输到目的地,并且到达目的地的数据包可能顺序会发生改变。在传输过程中,协 议对数据的传输进行控制,对在传输过程中丢失的数据包协议将控制系统将丢失的数据包重 新传送。这些都是 TCP 会话在重组的时候将遇到的问题。本文经过对 TCP/

ROS机器人操作系统中关于消息、主题、节点、服务、启动文件的详细叙述_xufuq2017的博客-程序员秘密

概述首先我们先来具体认识一下这几种不同的名字代表的都是什么内容,话不多说,先roscore,然后rosrun turtlesim turtlesim_node调出小乌龟,之后在开启新终端输入rosnode info turtlesim就会出现下方图片所示内容。这其中可以看到列出的几项内容,其中Node后边列出的是节点名称,可以看出其名字就是我们查阅的turtlesim节点。Publicat...

Fragment生命周期_AM小可乐的博客-程序员秘密

Fragment在Activity&amp;ViewPager中使用的生命周期简介一、在Activity中1.在Activity中add:onAttachonCreateonCreateViewonViewCreatedonActivityCreatedonStartonResume2.在Activity中hide:onHiddenChanged-hidden=true3....

从源码理解LinkedList.java_zhongrui_fzr的博客-程序员秘密

package java.util;import java.util.function.Consumer;/** * List和Deque接口的双向链表实现,实现了所有可选接口,允许空值null * 支持所有双向链表应该支持的操作,深入链表的操作都是从链表头遍历到链表尾 * 该实现不支持并发。多线程访问,至少一个线程修改列表结构时,需要外部同步,如: * List list = C

推荐文章

热门文章

相关标签