1、前言
小伙伴们好,提及来已经很久很久很久没见了呢!以前一直忙着作其余事情去了(泛指学习一类),公众号已经落下很久很久了。今天来写点好玩的东西。node
提及来,小编彷佛就是作启发式算法起家的。当时记得老师是这么跟我说的,启发式算法这东西很简单,你不须要基础,有高中基础就够了(其实他想说的是初中……)。web
后来小编一直在学这个东西,作了三四年了,用启发式算法作过的大大小小的project已经不记得有多少了,因此还算得上有一点点经验。所以今天就来写写,怎样实现一个比较高效的启发式算法吧~算法
2、何为高效?
说到这个词,相信你们必定不陌生。高效意思就是达到相同效果或者更好的效果时,使用的时间更短,所须要的资源更少。就拿小编来讲,因为小编特别笨,学同样东西须要花一周的时间,而群里的小伙伴只须要一天的时间就能学会。那么这位小伙伴是要比我高效的。编程
一样的对于一个启发式算法而言,不一样人实现出来,即便是使用同一编程平台达到一样的效果,运行时间也会千差万别,相差几倍甚至几十倍。这样说出来你们可能还没啥感受,那么放一下咱们以前作过的一些数值实验你们直观感觉下吧~微信

这是某个Java实现的求解VRP类问题的算法代码,两个算法都达到了一样的效果,只不过绿色曲线对应的算法在计算过程当中去除了相关冗余,能够看到运行时间直线降低。根据咱们的统计,消除冗余计算后可以使计算时间下降约83%
,对于工业化生产而言,提高哪怕一个小数点都能带来巨大的收益,何止是83个百分点呢。怎么说呢,这简直是A small step forward,one step civiliazation。架构

3、放码过来?
不要一上来就是写代码,不加思考就上手写代码,你只会搓一坨屎山出来,本身坑害本身。开始写代码以前必定要构思好算法的总体架构,解的表示方式,如何快速获得邻居解等。建议是思考的时间必定要占总时间的一半以上。app

其实思路清晰写代码是很是快的,好比每次在写代码的时候我都会先写好注释,好比:编辑器
//1. 先获取全部可行点的信息
//2. 对点按照成本进行排序
//3. 贪心将各个点插入到解中
而后写的时候我只须要按照这个思路往下走就能够了,这就跟你写小学生做文同样,起床刷牙到公园看鲸鱼,必定要思路清晰。svg
4、邻居解如何计算?
到了今天的核心问题,咱们都知道,邻域搜索过程当中,邻居解与当前解相比每每只有细微的变化,所以迭代过程当中绝大部分变量不须要从新计算,消除了冗余计算,可大大提升邻域搜索效率,下降运行时间。听不懂吗?不要紧,我举例子,慢慢给你讲解。学习
下面是一个VRP问题(没有TW哦)的一个初始解 :

为了方便表示咱们用 表示x的cost吧,其中x能够为一个解、解中的一条路径。 表示边<i,j>的距离。对于初始解,计算它的cost,只能是从头至尾挨个遍历一下了。所以:
其中各条路径的cost又能够表示为:
所以最后解的计算方式为:
为了方便比较咱们将这种计算解cost的方式称为
Algorithm1
。
算一下,Algorithm1
在计算时遍历了全部的客户点,对于N个客户点的解而已,算法的复杂度为O(N),嗯!还不赖。
好了如今解 经过一个move,生成了一个邻居解 :
能够看到该move就是将客户19
从路径
中移除,并从新relocate到路径
中客户1
后面。
如今生成了一个邻居解,得知道这个邻居解是好仍是坏对吧,那么咱们得比较
和
的大小吧。前面咱们经过Algorithm1
算出了
的大小,那么如今的问题就是
怎么计算了。敲黑板的重点来了。
利用Algorithm1
对
从新进行计算,时间复杂度前面分析过了,为
。
优化一下
观察上面的解
和
,能够发现一个邻域搜索算法很是显著的特色:邻居解
相比较当前解
而言,只发生了微小
的改变,整个解中有4条路径,只有两条路径发生了改变,所以
的cost能够由原来计算好的一些结果进行换算:
在上面的式子中,只有 和 是须要从新算的:
这样一来,只须要计算变更的路径便可,就不用从新计算全部路径了。大大下降了邻居解的计算时间复杂度。
进一步优化
细细观察一下 和 咱们发现,路径中发生变更的边彷佛也不是不少啊。我给你们标一下:
中发生变更的边我已经用红色实线
标出来了,
中发生变更的边我用红色虚线
标出来,那么
和
是否是能够用以前计算好的
和
得出来呢?固然能够啦,咱们只需减去原来的边,再加上新的边就能够了。
咱们将这两条式子带入下面的式子:
便可获得:
这个时间复杂度为 ,OK。通过重重优化,将时间复杂度从原来的 成功降到了 。
可能你们对这个 降到 没什么感受。我来给你们分析一下,在小规模问题,好比10个、20个节点这样可能还真没啥区别。可是别忘记了启发式算法是针对大规模的优化问题的,邻域搜索类算法的邻域规模每每是随着问题规模的增加而呈爆炸式增加的。好比说在一个100个节点的VRP算例中,对于exchange算子,交换任意两个客户,那么一个解所能造成的邻居解就有 ,大约为5000个邻居解。
-
若是每一个邻居解你都使用 Algorithm1
从新算一遍,那么大概要算5000*100=500000次。 -
若是你用优化过的计算方法,只须要算5000*1=5000次。
这个差距有多大,你们动动屁股都知道了。固然了,这只是理论上的分析。实际上的差距和编程环境以及实现方式等都有很大关系,可是只要差距超过10倍以上,都是能很明显的感受出来的。
5、小结
关于若是去除冗余,不是说一套方法通用的,须要根据算法工程师根据问题的特性,设计合适的解结构,再对邻域算子进行降冗余的思考与实现。降冗余的操做比较适合邻域搜索类的启发式算法,由于这类算法显著特色就是邻居解相比较当前解而言,变化很是细微。
固然了,这须要丰富的编程经验,因此你们有时间仍是好好磨练下吧~固然了此次也会放出相应的学习代码,只须要在公众号回复【VRP去重】便可看到。
还有,写到这里,我忽然想起大一发生的一件糗事,那时候你们统一穿着绿色的军装。我去上完厕因此后忽然不知道本身属于哪一个队伍了。因而当时被教官狠狠训了一顿。后来才知道,是三连。


本文分享自微信公众号 - 程序猿声(ProgramDream)。
若有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一块儿分享。