先抽象出路线source,顺便一提有向图和无向图最大的区别在于,0-4和4-0无向图表示的是同一个连通权重,有向图则不然(不仅权重不同,甚至可能只有一个方向连通)
const source=[{key:[0,1],value:3},{key:[0,7],value:1},{key:[0,4],value:5},{key:[0,3],value:2},{key:[1,6],value:7},{key:[2,6],value:2},{key:[2,3],value:4},{key:[3,4],value:8},{key:[4,5],value:1},{key:[5,6],value:4}]; //各条边
const points=[0,1,2,3,4,5,6,7]; //各点
//其他点一样的,此处为了看起来舒服设置0为起点6为终点,7是孤立点
const start =0;
const end =6;
此处的key,如果成为两个点的经纬度坐标,value成为距离或者时间形成的权重,则成为了GIS地图路网简单的最优解算法
let paths = points.filter(v=>{return v!=0}).map(v=>{let obj=source.find(vv=>{return vv.key.join("")==('0'+v)||vv.key.join("")==(v+"0");}); return {key:[0,v],value:obj && obj.value}}); //此处获取初始的从0到各个点的连通值(没有的直接连通的就是undefined)
let alreadyCalcPoint=[0]; //存储已经作为松弛顶点的点(算法核心就在于为每个点都去做一遍相邻点最小权重,并更新paths方案)
//得到上一步中value最小值的key(也可以在上一步里做,此处分开做,多遍历了一次)
//写成函数,后续反复会用到
function getMinSide(paths){
let min=null;
paths.map(v=>{
let otherPoint = v.key[0]==start?v.key[v.key.length-1]:v.key[0]; //去除start的
if(alreadyCalcPoint.indexOf(otherPoint)<0 && v.value){
if(min){
min=min.value<v.value?min:v
}
else{
min=v;
}
}
});
return min;
}
function findNextPoint(){
//找下一个松弛点
//先一步步计算,后面再抽象出通用方法
let minItem = getMinSide(paths); //得出的是 0-7 权重最小只有1
if(minItem==null){
return null; //算法结束的时候就是找不到还没有经过松弛操作的顶点
}
let nextPoint=minItem.key[0]==start?minItem.key[minItem.key.length-1]:minItem.key[0]; //例如后续minItem.key=[1,2,3],即路径中要经过3,则实际上该路径还是[1,3]
alreadyCalcPoint.push(nextPoint);
return minItem; //返回的是path对象
}
//计算得出以对应point为后顶点的权重
function calcPath(point){
paths.map(v=>{
let key=v.key[0]+''+v.key[v.key.length-1]; //实际的路径起止
if(key==(start+''+point)||key==(point+''+start)){
source.filter(vv=>{
return (vv.key[0]==point||vv.key[1]==point)&&vv.key.join("")!=(start+''+point)&& vv.key.join("")!=(start+''+point);
}).map(vvv=>{
//例如0-3-2的情况 point是3 anotherPoint则是2,tgObj表示的是0-?-2的路线
let anotherPoint = vvv.key[0]==point?vvv.key[1]:vvv.key[0];
let tgObj=paths.find(vvvv=>{
return vvvv.key[vvvv.key.length-1]==anotherPoint;
})
if(tgObj){
if(!(tgObj.value && tgObj.value<(v.value+vvv.value))){
tgObj.value=((v.value||0)+vvv.value);
tgObj.key = nextItem.key.concat(tgObj.key[tgObj.key.length-1]) //即当前松弛顶点的路径替换原有的路径
}
}
});
}
});
}
最后就是用while循环一下,最优解的算法最大的特点(也是最容易产生性能消耗的一点)就在于,只有全部点都经历过一次松弛(即试一遍是否最优)才能得出准确的最优路径.优化空间几乎无,但是,如果只是求次优解,那么可操作模式很多,例如分块设置关键点(即最近路线很可能走的一些点,在导航中则为重点交通枢纽或者主要道路交叉口)
let nextItem = {};
while(nextItem !=null){
nextItem = findNextPoint();
if(nextItem!=null){
let nextPoint = alreadyCalcPoint[alreadyCalcPoint.length-1];
calcPath(nextPoint);
console.log(nextPoint,JSON.stringify(paths)); //输出一下每次的顶点和0起始的最优解情况
}
}
执行结果,最后的6那行就是最后结果,只需要取[0,3,2,6]的路径就是0-6的最优解
7 "[{\"key\":[0,1],\"value\":3},{\"key\":[0,2]},{\"key\":[0,3],\"value\":2},{\"key\":[0,4],\"value\":5},{\"key\":[0,5]},{\"key\":[0,6]},{\"key\":[0,7],\"value\":1}]"
3 "[{\"key\":[0,1],\"value\":3},{\"key\":[0,3,2],\"value\":6},{\"key\":[0,3],\"value\":2},{\"key\":[0,4],\"value\":5},{\"key\":[0,5]},{\"key\":[0,6]},{\"key\":[0,7],\"value\":1}]"
1 "[{\"key\":[0,1],\"value\":3},{\"key\":[0,3,2],\"value\":6},{\"key\":[0,3],\"value\":2},{\"key\":[0,4],\"value\":5},{\"key\":[0,5]},{\"key\":[0,1,6],\"value\":10},{\"key\":[0,7],\"value\":1}]"
4 "[{\"key\":[0,1],\"value\":3},{\"key\":[0,3,2],\"value\":6},{\"key\":[0,3],\"value\":2},{\"key\":[0,4],\"value\":5},{\"key\":[0,4,5],\"value\":6},{\"key\":[0,1,6],\"value\":10},{\"key\":[0,7],\"value\":1}]"
5 "[{\"key\":[0,1],\"value\":3},{\"key\":[0,3,2],\"value\":6},{\"key\":[0,3],\"value\":2},{\"key\":[0,4],\"value\":5},{\"key\":[0,4,5],\"value\":6},{\"key\":[0,4,5,6],\"value\":10},{\"key\":[0,7],\"value\":1}]"
2 "[{\"key\":[0,1],\"value\":3},{\"key\":[0,3,2],\"value\":6},{\"key\":[0,3],\"value\":2},{\"key\":[0,4],\"value\":5},{\"key\":[0,4,5],\"value\":6},{\"key\":[0,3,2,6],\"value\":8},{\"key\":[0,7],\"value\":1}]"
6 "[{\"key\":[0,1],\"value\":3},{\"key\":[0,3,2],\"value\":6},{\"key\":[0,3],\"value\":2},{\"key\":[0,4],\"value\":5},{\"key\":[0,4,5],\"value\":6},{\"key\":[0,3,2,6],\"value\":8},{\"key\":[0,7],\"value\":1}]"
Dijkstra算法每一次找松弛点的过程都是贪心算法的完美实践,每次都寻找到最优路径起始点到对应点的最优路径,直到全部点都遍历一遍,得出起始点到其他点的最有路线