## 1.6 迪克斯特拉算法
1.概念:什么是迪克斯特拉算法,这是在有些情况下,我们虽然使用广度优先搜索找到了路程或者说路径最短的道路,但是不一定是用时最少的,也就是说在某种情况下,如果出现了具有权重的图,那么广度优先搜索就无法做到了,而迪克斯特拉可以将权重考虑到,计算出用时,或者说开销最小的路径,在这里需要说明的是,迪克斯特拉不适合循环引用的图,以及权值为负值的图。
2.前言:写这篇博客我自己试过很多很多的方法去验算迪克斯特拉算法,可能是笔者很菜,我发现自己对于这个算法的理解是很浅显的,所以每次开始写的时候有一点思路写了一个函数,但都以失败告终,我尝试过只用循环写,尝试过调整数据结构,但是我忽略了一个致命的点,那就是更新最短开销这个概念,所以每次都不知道该如何进行下去,即便如此我还是花费了大量时间,去啃这个点,最终我使用js类的概念完成了一个简易版的迪克斯特拉算法。下面我谈谈自己书写的思路,并最终将代码奉上。
`类`:我使用了类的概念,我看了很多视频和博客,我发现我之前想用纯数据结构,比如繁琐的嵌套表示权值关系是错误了,这样的数据类型没有自我维护的能力,所以我调整了一下思路,能不能将节点的信息使用类自我维护起来,下面是我的封装,较为粗糙,读者们也可以自行进行丰富。
<script>
class Node{
constructor(neighbors,name,nodeAll){
this.parent = -1 // 初始化时是没有前驱的
this.neighbors = neighbors // {A:2,B:3}
this.visited = false
this.name = name //
this.nodeAll = nodeAll // [ a,b,c ... ]
this.distanceToAny = {}
this.init()
}
init(){
for(var key in this.neighbors){
this.distanceToAny[key] = this.neighbors[key]
}
let neighborsArr = Object.keys(this.neighbors)
this.nodeAll.forEach(node => {
if(!neighborsArr.includes(node) &&node!==this.name){
this.distanceToAny[node] = Infinity
}
if(node === this.name){
this.distanceToAny[node] = 0
}
});
}
update(node,distance){
// console.log(node,distance)
if(this.distanceToAny[node] >= distance){
this.distanceToAny[node] = distance
}
}
updateParent(parent){
this.parent = parent
}
updateVisited(){
this.visited = !this.visited
}
}
</script>
`小结`:这个类具备几个能力,首先每个节点维护着自己的相邻节点,距离(权值),自己距离任意节点的开销,如果是相邻节点那么直接是距离,如果不是相邻节点,为无穷大,算法的核心就是不断的更新这个距离,将我们关心的节点作为关注节点,不断使其距离终点的距离从无穷大变为越来越小直到最后便是最优解。因此这个节点类需要一个更新自己父节点的方法,需要一个查看自己是否被处理过的方法, 这个是临界条件。
`流程`:这个算法我的思路是定义好一个类之后,我们还需要一个方法,这个方法有两个作用,其一是可以找出当前传入节点的相邻最小开销的节点和距离。第二,还可以更新我们关注的那个节点的到任意节点的距离的最优解,这一步是非常关键的。
<script>
function findLowest(node,base,startNode,parent){
// if(Object.keys(node.neighbors).length == 0){
// return {[node.name]:base}
// }
node.updateVisited()
node.updateParent(parent)
let costs = Object.values(node.neighbors)
let min = Math.min(...costs)
let target = {}
for(var key in node.neighbors){
if(node.neighbors[key] === min){
target = {[key]:(node.neighbors[key]+base)}
}
}
let newDistance = Object.keys(node.neighbors).map(neighbor=>{
return {[neighbor]:(base+node.neighbors[neighbor])}
})
newDistance.forEach(e=>{
let nodeName = Object.keys(e)[0]
let cost = e[nodeName]
startNode.update(nodeName,cost)
})
return target
}
</script>
`小结`:上面这个函数,有两个作用,其一可以修改节点的状态,只要是通过这个函数进行处理的节点,都将其visited状态进行改变,这个是至关重要的,当所有节点都被处理之后,就是我们找出最优解的时候,其次这个函数需要传入我们关注的那个节点,也就是起点的那个节点,在不断的循环中我们就是不断的更新起点到终点的距离,将其越变越小来实现最终的解的过程,因此这个很重要,其次它需要返回一个节点,这个节点是当前节点的去下一个节点的最优解,也是至关重要的。下一次循环才知道将谁作为要处理的节点。
`完整代码`:
<script>
let self = 'self'
class Node{
constructor(neighbors,name,nodeAll){
this.parent = -1 // 初始化时是没有前驱的
this.neighbors = neighbors // {A:2,B:3}
this.visited = false
this.name = name //
this.nodeAll = nodeAll // [ a,b,c ... ]
this.distanceToAny = {}
this.init()
}
init(){
for(var key in this.neighbors){
this.distanceToAny[key] = this.neighbors[key]
}
let neighborsArr = Object.keys(this.neighbors)
this.nodeAll.forEach(node => {
if(!neighborsArr.includes(node) &&node!==this.name){
this.distanceToAny[node] = Infinity
}
if(node === this.name){
this.distanceToAny[node] = 0
}
});
}
update(node,distance){
// console.log(node,distance)
if(this.distanceToAny[node] >= distance){
this.distanceToAny[node] = distance
}
}
updateParent(parent){
this.parent = parent
}
updateVisited(){
this.visited = !this.visited
}
}
let neighbors = {A:4,B:2}
let nodeAll = ['A','B','end','start']
let node1 = new Node(neighbors,name,nodeAll)
let map = {}
map.start = new Node(neighbors,'start',nodeAll)
map.A = new Node({end:5},'A',nodeAll)
map.B = new Node({A:1,end:10},'B',nodeAll)
map.end = new Node({},'end',nodeAll)
function findLowest(node,base,startNode,parent){
// if(Object.keys(node.neighbors).length == 0){
// return {[node.name]:base}
// }
node.updateVisited()
node.updateParent(parent)
let costs = Object.values(node.neighbors)
let min = Math.min(...costs)
let target = {}
for(var key in node.neighbors){
if(node.neighbors[key] === min){
target = {[key]:(node.neighbors[key]+base)}
}
}
let newDistance = Object.keys(node.neighbors).map(neighbor=>{
return {[neighbor]:(base+node.neighbors[neighbor])}
})
newDistance.forEach(e=>{
let nodeName = Object.keys(e)[0]
let cost = e[nodeName]
startNode.update(nodeName,cost)
})
return target
}
function checkFinish(map){
let res = true
for(var key in map){
if(map[key].visited === false){
res = false
}
}
return res
}
let areadyHandle = ['start']
let willHandle = ['A','B','end']
let target = findLowest(map[areadyHandle[0]],0,map.start,'self')
// 第一步
let nodeName,cost
// nodeName = Object.keys(target)[0]
// cost = target[nodeName]
// target = findLowest(map[nodeName],cost,map.start,'start')
// // 第二步
// nodeName = Object.keys(target)[0]
// cost = target[nodeName]
// target = findLowest(map[nodeName],cost,map.start,'start')
// // 第三步
// nodeName = Object.keys(target)[0]
// cost = target[nodeName]
// target = findLowest(map[nodeName],cost,map.start,'start')
// let canDo = true
while(!checkFinish(map)){
nodeName = Object.keys(target)[0]
cost = target[nodeName]
target = findLowest(map[nodeName],cost,map.start,'start')
}
console.log(map.start.distanceToAny.end) //8
// console.log(map)
</script>
`总结`:最终是可以找出最优解8的,但找到路径还需要再完善一下,这个只能找到值,却不能找到路径
`最后的话`:想声明一下,我对这个算法的理解其实还是很浅,希望看到这篇博客的同学,能够认真阅读,如果看到不妥或者可以改进的地方欢迎批评指正,如果能够一起交流那就更好了,前端学习一直在路上,我们一起加油,最后感谢你的观看,谢谢!