原理可参考
MOUSE:助力国赛 | 第7弹 模拟退火算法zhuanlan.zhihu.com看了一下知乎的文章,很少有用js作为语言来进行求解的。考虑到js代码量少,并且可视化容易的有点。所以用js来实现退火算法,求解31城最短路径问题。
简单叙述一下我所认知的退火算法:
由于31城最短路径,没有很好的简单算法可以实现。采用粗暴的穷举法的时间复杂度非常高,是31城的全排列,可以达到n!的复杂度。
因此如果通过有限的计算次数,得到一个可接受的局部最优解,是退火算法解决的问题之一。
核心算法在于根据交换任意两城:
1、如果距离减少,则在该条链路上继续预演
2、根据增加的距离,按照一定概率(负相关于增加的距离,增加的距离越小则概率越大)去进行预演。
tips:浏览器运行,可打开console看到前后的路径长度和链路。上下两个图分别代表随机生成的路径,以及根据随机生成路径优化后的结果。——往往会得到一条较为短的路径
<!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>
</body>
<script>
const T0 = 5000.0 //初始温度
const T_end = 1e-8
const q = 0.98 //退火系数
const L = 1000 // 每个温度的迭代次数,链长。
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市坐标点
//初始温度越高,退火系数越低,链长越长。————> 得出全局最优解的概率越大,所需时间越多
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(){//计算31城依次相连的距离
let path = 0;
for(let i=0;i<city_pos.length-1;i++){
let dis = distance(city_pos[i],city_pos[i+1])
path += dis
}
path += distance(city_pos[0],city_pos[city_pos.length-1])
return path
}
function create_new(){//随机交换两城顺序
let tag1 = ~~(Math.random()*city_pos.length)
let tag2 = ~~(Math.random()*city_pos.length)
while(tag1===tag2){
tag2 = ~~(Math.random()*city_pos.length)
}
let temp = city_pos[tag2]
city_pos[tag2] = city_pos[tag1]
city_pos[tag1] = temp
}
function main(){//主函数
let count = 0 //记录降温次数
let t = T0 //初始温度
let tag // 0-1的随机数 用来决定是否接收新解
let canvasBase = new CanvasBase({name:"my_canvas_base"}) //初始化canvas
let canvasChange = new CanvasBase({name:"my_canvas_change"}) //初始化canvas
city_pos = city_pos.sort(()=>Math.random()-0.5)
let startResult = [city_pos,path_len()]
canvasBase.drawLine()
console.log("初始路径长度为:",startResult[1],",路径为:",city_pos)
// 算法核心就只有下面几行,Math.exp(-df/t) <= r,代表退火接收概率和相差距离
while(t > T_end){
for(let i=0;i<L;i++){
let list = [...city_pos]
let f1 = path_len()
create_new()
let f2 = path_len()
let df = f2-f1
if(df >= 0){
let r = Math.random()
if(Math.exp(-df/t) <= r){
city_pos = list
}
}
}
t *= q; //降温
count++;
}
canvasChange.drawLine()
console.log("退火算法过后路径长度为:",path_len(),",路径为:",city_pos)
}
class CanvasBase {//canvas绘制初始链路和最终链路
constructor(props){
this.props = props;
this.canvas = document.getElementById(props.name);
this.canvas.width = 1000;
this.canvas.height = 1000;
if (this.canvas.getContext) {
this.context = this.canvas.getContext('2d');
}
}
drawLine(){
this.context.lineWidth = 2;
this.context.strokeStyle = "#F5270B";
city_pos.forEach((i,index)=>{
let startIndex = index;
let endIndex = index < city_pos.length-1 ? (index + 1) : 0
let start = [this.transformPointToPixelX(city_pos[startIndex]),this.transformPointToPixelY(city_pos[startIndex])]
let end = [this.transformPointToPixelX(city_pos[endIndex]),this.transformPointToPixelY(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]
}
}
main()
// by:知乎 lee
</script>
</html>