快速pow算法c语言_蚁群算法-求解TSP问题的前端可视化实现(以及决策优化实现)...

8de7b3acdff673e3d5b2988f92958f4e.png

原理可参考:

tigerqin1980:干货|十分钟快速get蚁群算法(附代码)​zhuanlan.zhihu.com
80472c0f9aaaf49936eb7dac4b16ad07.png

对于31城市的最短路径的得出可行的较优解,有很多种方法可以实现。上一期做了关于退火算法的实现,整体表现不错。那么我们也来看看同样问题下蚁群算法的表现吧。

关于上期退火算法:

lee:退火算法-求解TSP问题的前端可视化实现​zhuanlan.zhihu.com
4d6bbf965bdf6a788951c17dd97a9227.png

一、蚁群算法的核心在于:

每只蚂蚁分别独立的自主决策选择一条路径,根据已知的节点长度(也可未知)和信息素浓度来影响选择的路径。并根据最终的结果优劣,在路径上留下不同浓度的信息素,以便让后续的大多数的蚂蚁(决策者)更倾向于选择通过较优解的方向继续寻找局部更优解,和让少数蚂蚁(决策者)选择不那么优的解跳出局部寻求全局最有解。即使没有找到最有解,也能够很大概率找到局部最有解的一种算法。

那我们可以抓到几个关键字:

蚁群、蚂蚁、信息素、结果。

那我们一个个对他们剖析之后,进一步进行建模。

二、关键字分析

蚁群:我们需要抽象多个蚂蚁组成一个蚁群,每当一只蚂蚁走完所有路径之后。我们会在蚁群的知识库里记录下来当前的信息素浓度,以便下一只蚂蚁做决策用。并且我们需要设置挥发参数,信息素浓度受到时间的流逝会挥发,以免出现极端情况导致算法求解方向出现偏差。

蚂蚁:每一只蚂蚁需要设置一个出生地点,并且拥有决策方法,可以根据当前情况来选择路径。并且有一个记忆空间,用来储存自己走过的路径,避免重复踏足同一片地区。

信息素:我们需要初始化信息素的树状结构,以便提取和设置信息素的值。

结果:我们需要根据蚂蚁选择的路径,来对这条路径的满意程度打分。以便用于对信息素产生足量的影响,进而影响下一个蚂蚁的行为。

三、代码

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>
</head>
<body>
<canvas id="my_canvas_base"></canvas>
<canvas id="my_canvas_change"></canvas>
<button onclick="restart()">restart</button>
<span>长度为<span id="output"></span></span>
</body>
<script>
    let city_pos = [[1304,2312],[3639,1315],[4177,2244],[3712,1399],[3488,1535],[3326,1556],[3238,1229],[4196,1004],[4312,790],[4386,570],[3007,1970],[2562,1756],[2788,1491],[2381,1676],[1332,695],[3715,1678],[3918,2179],[4061,2370],[3780,2212],[3676,2578],[4029,2838],[4263,2931],[3429,1908],[3507,2367],[3394,2643],[3439,3201],[2935,3240],[3140,3550],[2545,2357],[2778,2826],[2370,2975]] //初始31市坐标点
    const P = 0.98 // 信息素挥发因子
    const C = 0 // 初始信息素浓度
    const RANDOM = 0.1
    const SMART = true //蚂蚁个体是否智能和我们一样开启上帝模式,了解到未知节点之间的距离。(或者叫地图模式和洞穴模式)
    const POWEROFPHEROMONE = 1 //信息素权重
    const POWEROFDISTANCE = 1 //距离权重
    const ANT_NUMBER = 30000 // 蚂蚁个数
    let pheromone

    function distance(city1,city2){//计算两城距离
        let xPow = Math.pow(city1[0]-city2[0],2)
        let yPow = Math.pow(city1[1]-city2[1],2)
        return Math.sqrt(xPow+yPow)
    }

    function path_len(citys){//计算31城依次相连的距离
        let path = 0;
        for(let i=0;i<citys.length-1;i++){
            let dis = distance(citys[i],citys[i+1])
            path += dis
        }
        path += distance(citys[0],citys[citys.length-1])
        return path
    }

    function city_traval_trend(city1,city2){//由于城市距离迁移的期望程度,反比于距离
        let trend = SMART ? (Math.pow(5000/distance(city1,city2),7) / 1000) + RANDOM: RANDOM //采用发散函数让距离带来的影响扩大
        return trend
    }

    function getLevelPheromone(antDistance){//根据总长度的值留下的信息素浓度
        let trend = 50000/antDistance
        return Math.pow(trend,8)/10
    }

    function main(){//主函数
        pheromone = new Pheromone() // 创建信息素单例
        let ants = new Ants({number:ANT_NUMBER})
        ants.start()
    }

    function getAntDistance(memory){//根据蚂蚁记忆获得迁移距离
        let array = memory.map(i=>{
            return city_pos[i]
        })
        let distance = path_len(array)
        return distance
    }

    class Pheromone{// 城市信息素单例
        constructor(){
            let citysData = {} //城市信息素数据
            city_pos.forEach((i,iIndex)=>{
                citysData[iIndex] = {}
                for(let j=iIndex+1;j<=city_pos.length-1;j++){
                    citysData[iIndex][j] = C
                }
            })
            this.data = citysData
        }

        getRealOrder([city1,city2]){
            let start = city1 < city2 ? city1 : city2
            let end = city1 < city2 ? city2 : city1
            return [start,end]
        }

        getData([city1,city2]){//city1 city2均为index
            let start = this.getRealOrder([city1,city2])[0]
            let end = this.getRealOrder([city1,city2])[1]
            return this.data[start][end]
        }

        setData([city1,city2],data=1){
            let start = this.getRealOrder([city1,city2])[0]
            let end = this.getRealOrder([city1,city2])[1]
            this.data[start][end] += data
        }

        volatilize(){
            let citysIndex = Object.keys(this.data)
            citysIndex.forEach(cityIndex=>{
                let toCitysIndex = Object.keys(this.data[cityIndex])
                toCitysIndex.forEach(toCityIndex=>{
                    this.data[cityIndex][toCityIndex] = this.data[cityIndex][toCityIndex] * P
                })
            })
        }
    }

    class Ants{//蚁群模型
        constructor(props){
            this.number = (props && props.number) || 100
        }

        start(){
            let endDistance = -1
            let array,saveArray
            for(let i=0;i<this.number;i++){ // 蚁群活动
                let ant = new Ant()
                let memory = ant.move();
                array = memory.map(i=>{
                    return city_pos[i]
                })
                let distance = path_len(array)
                endDistance = endDistance===-1 ? distance : (distance>endDistance ? endDistance : distance)
                saveArray = endDistance===-1 ? array : (distance>endDistance ? saveArray : array)
            }
            // canvas渲染
            let canvasBase = new CanvasBase({name:"my_canvas_base",city_pos:city_pos}) //初始化canvas
            let canvasChange = new CanvasBase({name:"my_canvas_change",city_pos:saveArray}) //初始化canvas
            canvasBase.drawLine()
            canvasChange.drawLine()
            // console.log(pheromone) //结束时候的信息素权重
            document.getElementById("output").innerHTML = endDistance
        }
    }

    class Ant{//单只蚂蚁
        constructor(props){
            let startIndex = ~~(Math.random()*city_pos.length) //起始点index
            this.memory = [startIndex]; //小蚂蚁同学脑子里记录的自己走过的路
        }

        move(){
            this.decision()
            return this.memory
        }

        decision(){//决策算法
            // 选择一条路径尽可能选择:1、距离近 2、信息素浓度高的路径
            let array = [] //此时可选的路径
            let startIndex = this.memory[this.memory.length-1]
            array = new Array(city_pos.length).fill(0).map((i,index)=>index)
            array = array.filter(i=>{
                let tag = true
                this.memory.forEach(j=>{
                    if(i===j){
                        tag = false
                    }
                })
                return tag
            })
            let decisionP = this.decisionP(startIndex,array) // 和可选路径同构的信息素权重数组
            let decisionD = this.decisionD(startIndex,array)
            let decisionTotal = decisionP.map((i,index)=>{
                return ~~(i + decisionD[index])
            })
            let toIndex = this.randomWithPower(array,decisionTotal) //将要去的城市的index
            // 返回决策选择的index
            this.memory.push(toIndex);
            // 录入蚂蚁的记忆中,走过这个城市了————坏人太多以后不来了
            if(this.memory.length<city_pos.length){
                this.decision()
                //继续决策,还未走完
            }else{
                let antDistance = getAntDistance(this.memory)
                this.savePheromone(antDistance)
                this.volatilize()
                //等到下一个蚂蚁来的时候,全局信息素会挥发一部分
            }
        }

        decisionP(start,array){//决策算法获得信息素权重的部分
            let arrayNew = array.map(i=>{
                return pheromone.getData([start,i]) * POWEROFPHEROMONE
            })
            return arrayNew
        }

        decisionD(start,array){//决策算法获得距离权重的部分
            let arrayNew = array.map(i=>{
                let data = city_traval_trend(city_pos[start],city_pos[i]) //根据距离差来生成权重
                return data * POWEROFDISTANCE
            })
            return arrayNew
        }

        randomWithPower(baseArray,array){//带权随机
            let newArray = []
            array.forEach((i,index)=>{
                newArray[index] = index ===0 ? i : newArray[index-1] + i
            })
            let luckNumber = ~~(Math.random()*newArray[newArray.length-1])
            //再对该数组进行插入排序找到对应的index即可,由于数字较少暂时用的遍历(插入排序应该是最好的算法)
            let index = newArray.findIndex(i=>{
                return i>=luckNumber
            })
            return baseArray[index]
        }

        savePheromone(antDistance){
            //根据路径长短记录信息素
            let lengthArray = []
            this.memory.forEach((i,index)=>{
                if(index === this.memory.length-1){
                    lengthArray.push([i,this.memory[0]])
                }else{
                    lengthArray.push([i,this.memory[index+1]])
                }
            })
            let pheromoneData = getLevelPheromone(antDistance) //信息素浓度和距离成反比
            lengthArray.forEach(i=>{
                pheromone.setData(i,pheromoneData)
            })
        }

        volatilize(){
            //小蚂蚁走完路径后,信息素挥发。下一个蚂蚁上
            pheromone.volatilize()
        }
    }

    class CanvasBase {//canvas绘制模型
        constructor(props){
            this.props = props;
            this.canvas = document.getElementById(props.name);
            this.canvas.width = 1000;
            this.canvas.height = 1000;
            this.city_pos = props.city_pos
            if (this.canvas.getContext) {
                this.context = this.canvas.getContext('2d');
            }
        }

        drawLine(){
            this.context.lineWidth = 2;
            this.context.strokeStyle = "#F5270B";
            this.city_pos.forEach((i,index)=>{
                let startIndex = index;
                let endIndex = index < this.city_pos.length-1 ? (index + 1) : 0
                let start = [this.transformPointToPixelX(this.city_pos[startIndex]),this.transformPointToPixelY(this.city_pos[startIndex])]
                let end = [this.transformPointToPixelX(this.city_pos[endIndex]),this.transformPointToPixelY(this.city_pos[endIndex])]
                this.context.moveTo(start[0],start[1]);
                this.context.lineTo(end[0],end[1]);
                this.context.stroke();
                //点
                this.context.beginPath();
                this.context.beginPath();
                this.context.arc(start[0],start[1],10,0,360,false);
                this.context.fillStyle="red";//填充颜色,默认是黑色
                this.context.fill();//画实心圆
                this.context.closePath();
            })
        }

        transformPointToPixelX(point){
            return this.transformPointToPixel(point)[0]
        }

        transformPointToPixelY(point){
            return this.transformPointToPixel(point)[1]
        }

        transformPointToPixel(point){
            // 坐标为0-5000 canvas大小为1000
            return [point[0]/5,point[1]/5]
        }

    }

    function restart(){
        main()
    }
    main()
    // by:知乎 lee
</script>
</html>

四、优化算法

该算法可以优化的点在于:

1、根据常识和观察可知:总路径为31个城市路径之和,好的路径平均距离短。因此倾向选择短的路径,更容易获得较优解。——代码中SMART代表了蚂蚁是否采用该决策。

2、蚂蚁留下的信息素浓度反比于总距离,根据信息素浓度做决策时,也有可优化的空间:比如浓度为4和5的两条路径,在不考虑1的情况下,是否对浓度采取放大或缩小原则来进行加权。比如exp、pow用来放大,sqrt方法用来缩小。——经过尝试采用放大更容易得到较优解

3、权值目前只根据距离权重decisionD 和信息素权重decisionP共同影响结果,也可加入适当的随机值,来产生一些特立独行的蚂蚁,可能会降低局部最优解的质量,但也更容易跳出局部最优解得到更好的结果。

五、总结

蚁群算法建模相比退火算法复杂,数据不容易量化。对于地图模式下(每只蚂蚁知道不同节点之间的距离),可以较好的得到局部最优解。而在洞穴模式下(每只蚂蚁不知道不同节点之间的距离,只能通过信息素浓度来选择路线)需要较多尝试。

tips:文章分析和概念的内容较少,更倾向于有一定算法基础的小伙伴,着重介绍对算法抽象和建模的思路。也希望大家多提一些意见,下一期再见啦~

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值