遗传算法在公园景点最优路线app上的应用(附swift代码)

一、项目介绍

 项目介绍
      公司有个项目,在城市里有80多个景点,用户任意选n个景点,我需要帮他算出最合适的路线

 需求说明
      从80个景点里面,选出n个景点,找到最短的路线

二、思路

最先想到的就是floyd算法,但是这个算法只能找到任意两点的最短路径和时间。不过找到这个数据后,再来解决旅行商问题就更方便了

准备资料
    运营给了我一份85个景点的准确坐标,我用googlemap的测距接口,找到了任意点的距离

三、计算任意两点的最短距离和路径

这个就直接用floyd算法来解决了:
比如这个例子,已知4个点之间的相邻两点距离,求任意两点之间的最短距离:

image.jpeg

1、先把距离数据看做一个矩阵,如下图

image.jpeg

如果必须途径1号顶点,求任意两点之间的最短路程,应该如何求呢?
只需判断e[i][1]+e[1][j]是否比e[i][j]要小即可。e[i][j]表示的是从i号顶点到j号顶点之间的路程。e[i][1]+e[1][j]表示的是从i号顶点先到1号顶点,再从1号顶点到j号顶点的路程之和。其中i是1到n循环,j也是1到n循环,代码实现如下。

for( i=1; i<=n; i++) {       
    for( j=1; j<=n; j++ ) {    
        if ( e[i][j] > e[i][1]+e[1][j] ) {
            e[i][j] = e[i][1]+e[1][j];       
        }
    }   
} 

在只允许经过1号顶点的情况下,任意两点之间的最短路程更新为:

image.jpeg

通过上图我们发现:在只通过1号点中转的情况下,3号顶点到2号顶点(e[3][2])、4号顶点到2号顶点(e[4][2])以及4号顶点到3号顶点(e[4][3])的路程都变短了。
接下来继续求在只允许经过1和2号两个顶点的情况下任意两点之间的最短路程。如何做呢?我们需要在只允许经过1号顶点时任意两点的最短路程的结果下,再判断如果经过2号顶点是否可以使得i号顶点到j号顶点之间的路程变得更短。即判断e[i][2]+e[2][j]是否比e[i][j]要小,代码实现为如下。

//经过1号顶点   
    for(i=1;i<=n;i++){    
        for(j=1;j<=n;j++){  
            if (e[i][j] > e[i][1]+e[1][j])  {
                e[i][j]=e[i][1]+e[1][j];   
            }
        }
    }
//经过2号顶点  
    for(i=1;i<=n;i++) {    
        for(j=1;j<=n;j++){
            if (e[i][j] > e[i][2]+e[2][j]) {
                e[i][j]=e[i][2]+e[2][j]; 
            }
        }
    }

在只允许经过1和2号顶点的情况下,任意两点之间的最短路程更新为:

image.jpeg

通过上图得知,在相比只允许通过1号顶点进行中转的情况下,这里允许通过1和2号顶点进行中转,使得e[1][3]和e[4][3]的路程变得更短了。
同理,继续在只允许经过1、2和3号顶点进行中转的情况下,求任意两点之间的最短路程。任意两点之间的最短路程更新为:

image.jpeg

最后允许通过所有顶点作为中转,任意两点之间最终的最短路程为:

image.jpeg

整个算法过程虽然说起来很麻烦,但是代码实现却非常简单,核心代码只有五行:

for(k=1;k<=n;k++){   
    for(i=1;i<=n;i++){   
        for(j=1;j<=n;j++){       
            if(e[i][j]>e[i][k]+e[k][j]){     
                e[i][j]=e[i][k]+e[k][j];
            }
        }
    }
}

这段代码的基本思想就是:最开始只允许经过1号顶点进行中转,接下来只允许经过1和2号顶点进行中转……允许经过1~n号所有顶点进行中转,求任意两点之间的最短路程。用一句话概括就是:从i号顶点到j号顶点只经过前k号点的最短路程。其实这是一种“动态规划”的思想。

四、TSP和ATSP
计算完最短路径,开始了重点,就是tps问题。
谈到tsp有个最笨得办法就是穷举法,就是排列组合算出所有可能的路径,并找出最短的那条。这个方法实践后发现,当线路点超过8个点的时候,时间就满的不能忍受了,但是少于8个点的时候,这个方法无疑是最好最准确的答案

当大于8个点的时候,tsp问题就需要用技巧来解决了,超越了很多资料,目前人类还没有找到答案,只有在有限时间内找到最接近的精确答案。必经n个点的可能性就由n!种结果。10!就是300万种。目前解决算法有:遗传算法、神经网络算法、蚁群算法、退火堆算法等。我这次使用的时遗传算法不同的是我的路线是非对称的,也就是A到B点,去回的距离是不一样的,不过这个不影响

五、ATSP改进版,
ATSP都是回路的,一个点出发,最后回到这个点。对于不同的起点,终点其实就是算距离的时候,把起点,终点加上就行了在这里,我打算小于7个点的时候用穷举法,因为这样可以在最短时间得到100%准确的答案

下面说说遗传算法吧:

简单的理解就是:把经过的城市当DNA片段,随机造50个人,让他们交叉配对,并且加上一个变异几率,生成第后代,然后优胜劣汰把路线最短的选出来,再生后代,最后选出最优的。这里的重点就是 交叉配对、变异和遗传

遗传算法自身参数设定
遗传算法自身参数有3个,即群体大小n、交叉概率Pc和变异概率Pm。
群体大小n太小时难以求出最优解,太大则增长收敛时间。一般n=30-160。交叉概率Pc太小时难以向前搜索,太大则容易破坏高适应值的结构。一般取Pc=0.25-0.75。变异概率Pm太小时难以产生新的基因结构,太大使遗传算法成了单纯的随机搜索。一般取Pm=0.01—0.2。

交叉配对:

部分匹配法(Partially Matching Crossover, PMX)
以两个父代个体为例:(1 2 3 4 5 6 7 8)和(2 4 6 8 7 5 3 1),随机选择两个交叉的点,假如第一个点为位置4,第二个交叉点为位置6,那么在两个点之间的位置将进行交叉,其它的位置进行复制或者用相匹配的数进行替换。在此实例中,第一个父代个体中4 5 6被选中,第二个父代个体中,8 7 5被选中。那么4 与8,5与7,6与5相匹配。匹配过程和如图2所示。

image.png

图2 PMX交叉操作
首先将4 5 6与8 7 5分别加入到子代2和子代1中相应的位置,然后将其他位置上的数字直接复制到相应的后代中,如果该数字已经在该子代中已经存在,则用相应的匹配法则进行替换,例如子代1中将7复制进去的时候,发现7已经存在在子代中,通过查找相应的匹配法则,发现,7与5匹配,然后复制5,又发现5也已经存在在该子代中,在此查找匹配法则,发现5与6匹配,将6复制,6不存在在该子代中,所以可以将6复制进去,如此反复,知道子代中城市的数目达到定义的长度,该子代创建完成。

变异:
简单倒位变异(Simple Inversion Mutation, SIM)
SIM先从父代个体中选择一个基因串,然后将这个基因串中所有的基因倒位放置,如图11所示。

image.png

其实交叉配对和变异有很多种,详细参见:
http://www.cnblogs.com/biaoyu/archive/2012/10/02/2710267.html
经过多次的进化,最后就得到了最适应的路线

六、代码实现 playground xcode6.3运行通过

import Foundation
import UIKit

/**
 *  TSP-遗传算法
 *  lizyyy@gmail.com
 */
class MGraph{
    var vexs = [String]() //顶点数组
    var arc = Dictionary< String,Dictionary<String,Int> >() //边邻接矩阵,即二维数组
    var arcData = Dictionary<String,Int>() //边的数组信息
    private var infinity = 9999999
    init(vexs :[String] ,arcData: Dictionary<String,Int>){
        self.vexs = vexs
        self.arcData = arcData
        self.initalizeArc()
        self.createArc()
    }
   
    func floyed() -> (path:Dictionary<String,[String]>,distance: Dictionary<String,Int>, points:[String]) {
        var path = Dictionary<String,Dictionary<String,String>>() //路径数组
        var distance = Dictionary<String,Dictionary<String,Int>>() //距离数组
        for (k,v) in arc{
            var tmp1 = Dictionary<String,String>()
            var tmp2 = Dictionary<String,Int>()
            for (k0,v0) in v{
                tmp1[k0] = k0
                tmp2[k0] = v0
            }
            path[k] = tmp1
            distance[k] = tmp2
        }
        for (k1,v1) in enumerate(vexs){
            for (k2,v2) in enumerate(vexs){
                for (k3,v3) in enumerate(vexs){
                    if( distance[ vexs[k2] ]![vexs[k3]]! > distance[ vexs[k2] ]![vexs[k1]]! + distance[ vexs[k1] ]![vexs[k3]]! ){
                        path[ vexs[k2] ]![vexs[k3]]! = path[ vexs[k2] ]![vexs[k1]]!
                        distance[ vexs[k2] ]![vexs[k3]]! = distance[ vexs[k2] ]![vexs[k1]]! + distance[ vexs[k1] ]![vexs[k3]]!
                    }
                }
            }
        }
        //计算路径
        var rsPath = Dictionary<String,[String]>()
        var rsDistance = Dictionary<String,Int>()
        for (ka,va) in enumerate(vexs){
            for (kb,vb) in enumerate(vexs){
                var from = [va]
                route(path, from: va, to: vb, o: &from)
                rsPath["\(va),\(vb)"] = from
                rsDistance["\(va),\(vb)"] = distance[va]?[vb]
            }
        }
        return (path:rsPath,distance:rsDistance,points:vexs)
    }
   
    func route(s:Dictionary<String,Dictionary<String,String>>,from f:String,to t:String, inout o:[String]){
        if s[f]![t]! != t {
            o.append(s[f]![t]!)
            route(s,from: s[f]![t]! ,to: t, o: &o)
        }else{
            if (f != t) { o.append(t)}
        }
    }
   
    func initalizeArc(){
        for (k,v) in enumerate(vexs){
            var tmp = Dictionary<String,Int>()
            for (k1,v1) in enumerate(vexs){
                tmp[v1] = v == v1 ? 0 : infinity
            }
            arc[v] = tmp
        }
    }
   
    func createArc(){
        for (k,v) in arcData{
            var arr = k.componentsSeparatedByString(",")
            var from  = arr[0]
            var to = arr[1]
            arc[from]?[to] = v
        }
    }
}

extension Array {
   
    mutating func shuffle() {
        for _ in 0..<self.count {
            sort { (_,_) in arc4random() < arc4random() }
        }
    }
}

//==================
class TspGenetic{
    var data =  (path:Dictionary<String,[String]>(),distance: Dictionary<String,Int>(), points:[String]() )
    var from  = String()
    var to  = String()
    var through = [String]()å
    var solutions = Array<Array<String>>()
    var initialPopulationSize = 30 //初始50人口数量,50条线路
    var maxEvolutionSteps = 2  //进化次数
    var roundPopulationSize = 10  //每次取进化最好的前10个
    var mutationProbability :Double = 0.5 //突变几率
    init(source :  (path:Dictionary<String,[String]>,distance: Dictionary<String,Int>, points:[String] ) ){
        self.data = source
        for (k,v) in enumerate(source.points){
            data.path["\(v),\(v)"] = ["\(v)","\(v)"]
            data.distance["\(v),\(v)"] = 0
        }
    }
   
    func routeThrough(from f:String,to t:String,through tr:[String]) -> (route:[String],distance:Int,path:[String]) {
        self.from = f;self.to = t;self.through = tr
        if( through.count < 4 ){//4个以下穷举
            var out = (path:Array<[String]>(),distance:Array<Int>())
            getAllPermutation(tr,index: 0,o: &out)
            var index = find(out.distance, minElement(out.distance))
            return (route:routes(out.path[index!]),distance:out.distance[index!],path:out.path[index!])
        }else{//4个以上遗传算法
            var rs = self.evolution() //进化
            self.doSelection(1)
            var route = [from] + solutions[0] + [to]
            return (route:routes(route),distance:distance(route),path:route)
        }
    }
   
    func getAllPermutation(var a:[String],index:Int,inout o:(path:Array<[String]>,distance:Array<Int>)){
        if(index == a.count-1){
            var r = [String]()
            for i in 0 ..< a.count {
                r.append(a[i])
            }
            o.path.append( [from] + r + [to]  )
            o.distance.append(distance([from] + r + [to]))
            return
        }
        for i in index ..< a.count {
            swap(&a[index], &a[i])
            getAllPermutation(a, index: index+1,o: &o)
            swap(&a[index], &a[i])
        }
    }
   
    //进化
    func evolution() -> (route:[String],distance:Int,path:[String]) {
        initializePopulation() //初始50人,即随机选出50条线路
        var iEvolutionStep = 0;
        do {
            // 选中10个优良的市民,即最短路线前十名
            self.doSelection(roundPopulationSize)
            // 对优良路线基因重组,即路线优化?
            self.doRecombination()
            // 基因突变,即路线改变
            self.doMutation(mutationProbability);
            iEvolutionStep++;
        } while(iEvolutionStep < maxEvolutionSteps);
        return (route:[""],distance:10,path:[""])
    }
   
    /**
    *  获取一个点到另一个点的距离
    *  distance(distanceData, route:(outs1 + outs2))
    */
    func distance(route:[String]) -> Int{
        var m = 0
        for i in 0 ..< route.count-1 {
            if ((data.distance["\(route[i]),\(route[i+1])"] ) != nil) {
                m += data.distance["\(route[i]),\(route[i+1])"]!
            }else{  m += 999999 }
        }
        return m
    }
   
    //两点间的路径
    func routes(pointRoute:[String]) -> [String]{
        var route = [String]() //d,c,a
        for i in 0 ..< pointRoute.count-1 {
            var tmp = data.path["\(pointRoute[i]),\(pointRoute[i+1])"]
            if(i < pointRoute.count-2){
                tmp?.removeLast()
            }
            route += tmp!
        }
        return route
    }
   
    func randomInRange(range: Range<Int>) -> Int {
        let count = UInt32(range.endIndex - range.startIndex)
        return  Int(arc4random_uniform(count)) + range.startIndex
    }
   
    func initializePopulation(){
        for i in 0 ..< initialPopulationSize {
            through.shuffle()
            solutions.append(through)
        }
    }
   
    //每次取进化最好的前n名,即线路最短的前n名
    func doSelection(roundPopulationSize:Int) {
        var rs = sorted(solutions, {(s1,s2) -> Bool in
            var dis1 = self.distance([self.from] + s1 + [self.to])
            var dis2 = self.distance([self.from] + s2 + [self.to])
            return dis1<dis2
        })
        self.solutions = Array(rs[0 ..< roundPopulationSize])
    }
   
    // 对优良路交叉匹配(部分匹配法PMX)
    func doRecombination() {
        solutions
        var children = Array<[String]>() //后代新线路
        for (k,v) in enumerate(solutions){
            for (k1,v1) in enumerate(solutions){
                if(k != k1){
                    children.append(getChildWith(v,solutions1:v1))
                }
            }
        }
        self.solutions = children
    }
   
    func getChildWith(solutions:[String],var solutions1:[String]) -> [String]{
        var b = randomInRange(0...solutions1.count-2)
        var e = randomInRange(2...(solutions1.count-b))
        var range = Range(b...(b+e-1))
        var xx = solutions[range]
        var xx1 = solutions1[range]
        solutions1.replaceRange(range, with: Array(count: e, repeatedValue:""))
        pmx(&solutions1,xx:Array(xx),xx1:Array(xx1))
        solutions1.replaceRange(range, with: (xx))
        return solutions1
    }
   
    func pmx(inout output:[String],xx:[String],xx1:[String]) {
        for (k,v) in enumerate(xx){
            if contains(output, v) {
                var i = find(xx, v)
                var ixx = find(output, v)
                output[ixx!] = xx1[i!]
                pmx(&output,xx:xx,xx1:xx1)
            }
        }
    }
   
    func doMutation(mutationProbability:Double){
        for (k,v) in enumerate(solutions){
            var random = Double(randomInRange(0...100))/100.00
            if random < mutationProbability  {
                self.mutate(v)
            }
        }
    }
   
    //倒位变异(Inversion Mutation, IVM)
    func mutate(var solution:[String])->[String] {
        var b = randomInRange(0...solution.count-2)
        var e = randomInRange(2...(solution.count-b))
        var range = Range(b...(b+e-1))
        solution.replaceRange(range, with: reverse(solution[range]))
        return solution
    }
}

var vexs = ["a","b","c","d","e","f","g"]
var arcData = ["a,b":4,"a,c":2,"a,d":3,"b,c":5,"b,e":3,"b,d":4,"c,d":1,"c,f":2,"d,e":6,"d,f":2,"e,f":4,"b,a":4,"c,a":2,"d,a":3,"c,b":5,"e,b":3,"d,b":4,"d,c":1,"f,c":2,"e,d":6,"f,d":2,"f,e":4]
var t = MGraph(vexs: vexs,arcData:arcData)
var rs = t.floyed()
//=======
var from = "a"
var to = "a"

var through = ["f","c","b","e","d"]
// "a到a 必须经过[c, b, e, f, d]的顺序是[a, c, d, f, e, b, a],距离为:16,路径:[a, c, d, f, e, b, a]"

var route = TspGenetic(source:rs)
var rsRoute = route.routeThrough(from: from, to: to, through:through)
println("\(from)到\(to) 必须经过\(through)的顺序是\(rsRoute.2),距离为:\(rsRoute.1),路径:\(rsRoute.0)")
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 161,873评论 4 370
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 68,483评论 1 306
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 111,525评论 0 254
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,595评论 0 218
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 53,018评论 3 295
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,958评论 1 224
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 32,118评论 2 317
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,873评论 0 208
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,643评论 1 250
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,813评论 2 253
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,293评论 1 265
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,615评论 3 262
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,306评论 3 242
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,170评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,968评论 0 201
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 36,107评论 2 285
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,894评论 2 278

推荐阅读更多精彩内容