前言
最近有一个需求需要用到寻路,于是我查阅了一些资料,最后觉得dijkstra算法最适合我的场景,我需要从一个起始点S导航到终点D,了解了原理之后我使用HTML写了一个小的demo,下文会附上原理和Code部分。
原理
dijkstra算法主要是使用贪心算法的思想,每进行一个步骤都会找到当前最利于结果的方案;dijkstra算法也不适用于存在负方向的weight,这里我们只关注距离distance,不存在这种情况;
1.首先我们得到一个数据集nodes,这些nodes需要具备唯一的name(id),以及与nodes连接的其他node的关系(links),并且还需要距离distance(专业的术语应该是weight: 权重),有了这些之后,我们就可以准备开始啦
2.准备一个数组用来记录node的name,node的距离起点S的distance,以及可追溯的上一个node的name;下表所示
NodeName | mindistance of Start | lastNodeName |
A | Infinity | null |
B | Infinity | null |
C | Infinity | null |
D | Infinity | null |
E | Infinity | null |
F | Infinity | null |
G | Infinity | null |
我们假设所有Node距离起点都是无限大的距离,假设我使用A作为起点,F作为终点,那么可以得到A距离起点的distance为0,lastNodeName为null,这时我们就可以将A标记为已检查的Node,因为其他的Node不可能在距离上小于A,后面的计算中我们也不会再回到A;这是第一步
第二步,我们通过A的数据集中的links找到A的连接点B C D,如图(PS:B 到 G 距离为 6)
根据贪心算法的宗旨,只找当前最好的方案,那么无疑D就是距离最短的那个,但我们也同时在表格中更新B和C,如下
NodeName | mindistance of Start | lastNodeName | isChecked |
A | 0 | null | true |
B | 12 | A | false |
C | 8 | A | false |
D | 7 | A | false |
E | Infinity | null | false |
F | Infinity | null | false |
G | Infinity | null | false |
同理我们需要将最优解D打上已检查的标记,之后在通过D的links去找到最优解,D关联了G, E, C, A,由于A是上一个Node 我们不需要计算,那么其他的3个Node中加上之前的7,分别是G:17,E:11,C:12,其中C我们之前计算的距离是8,这里的C是12,所以我们不用更新表格,E是最优解,更新表格如下
NodeName | mindistance of Start | lastNodeName | isChecked |
A | 0 | null | true |
B | 12 | A | false |
C | 8 | A | false |
D | 7 | A | true |
E | 11 | D | false |
F | Infinity | null | false |
G | 17 | D | false |
此时我们发现没有被检查的Node中距离最短的是C,我们再重复刚刚从操作,最后会得到这样的表格
NodeName | mindistance of Start | lastNodeName | isChecked |
A | 0 | null | true |
B | 12 | A | true |
C | 8 | A | true |
D | 7 | A | true |
E | 11 | D | true |
F | 13 | E | true |
G | 16 | D | true |
最后我们通过回溯的方式,从F一直向上回溯到A,就是 F->E->D->A,之后可以reverse一下,就是最短路径啦!
Code
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Dijkstra Demo</title>
</head>
<body>
<label for="local">LOCAL</label>
<input type="text" id="local">
<label for="dest">DEST</label>
<input type="text" id="dest">
<div id="resultUI"></div>
<script>
// Dijkstra 算法
class Queue {
constructor() {
this.items = [];
}
// 入列
enqueue(element) {
this.items.push(element);
}
// 判断队列是否为空
isEmpty() {
return this.items.length === 0;
}
}
// node
const nodes = [
{
name: 'A',
links: [
{
name: 'B',
weight: 12
},
{
name: 'C',
weight: 8
},
{
name: 'D',
weight: 7
}
],
},
{
name: 'B',
links: [
{
name: 'A',
weight: 12
},
{
name: 'G',
weight: 6
},
],
},
{
name: 'C',
links: [
{
name: 'A',
weight: 8
},
{
name: 'D',
weight: 5
},
{
name: 'E',
weight: 9
},
],
},
{
name: 'D',
links: [
{
name: 'A',
weight: 7
},
{
name: 'C',
weight: 5
},
{
name: 'E',
weight: 4
},
{
name: 'G',
weight: 10
},
],
},
{
name: 'E',
links: [
{
name: 'D',
weight: 4
},
{
name: 'C',
weight: 9
},
{
name: 'F',
weight: 2
},
{
name: 'G',
weight: 5
},
],
},
{
name: 'F',
links: [
{
name: 'E',
weight: 2
},
{
name: 'G',
weight: 9
},
],
},
{
name: 'G',
links: [
{
name: 'B',
weight: 6
},
{
name: 'D',
weight: 10
},
{
name: 'E',
weight: 5
},
{
name: 'F',
weight: 9
},
],
},
]
class Dijkstra {
constructor(nodes, start, end) {
this.nodes = nodes; // 节点
this.start = start; // 起点
this.end = end; // 终点
this.flow = []; // 最优路径
this.queue = new Queue(); // 队列
this.minDistance = Infinity;
}
// 计算最优路径
calcuate() {
this.flow = []
// 初始化队列
for (const node of this.nodes) {
this.queue.enqueue({
name: node.name,
distance: this.start === node.name ? 0 : Infinity,
isChecked: false,
lastNode: null,
links: node.links
})
}
// 计算距离start地点的distance
while (this.queue.items.some(item => !item.isChecked)) {
const tempList = this.queue.items.filter(item => !item.isChecked).sort((a, b) => a.distance - b.distance)
tempList[0].isChecked = true
tempList[0].links.forEach(link => {
if (!this.queue.items.filter(item => item.isChecked).find(item => item.name === link.name)) {
const index = this.queue.items.findIndex(value => link.name === value.name)
const _distance = link.weight + tempList[0].distance
if (this.queue.items[index].distance > _distance) {
this.queue.items[index].distance = link.weight + tempList[0].distance
this.queue.items[index].lastNode = tempList[0].name
}
}
})
}
this.flow.unshift(this.end)
// 找到目的地后追溯至起点
while (this.flow[0]) {
const lastNode = this.queue.items.find(item => this.flow[0] === item.name)
if (this.flow.length === 1) this.minDistance = lastNode.distance
this.flow.unshift(lastNode ? lastNode.lastNode : null)
}
this.flow.shift()
return this.flow.join('->')
}
}
const destDOM = document.querySelector('#dest')
const localDOM = document.querySelector('#local')
const resultDOM = document.querySelector('#resultUI')
const DOMArr = [destDOM, localDOM]
const dijkstraObject = new Dijkstra(nodes, localDOM.value, destDOM.value)
DOMArr.forEach(DOM => {
DOM.addEventListener('input', e => {
const names = nodes.map(node => node.name)
if (!names.includes(localDOM.value) || !names.includes(destDOM.value)) return
dijkstraObject.start = localDOM.value
dijkstraObject.end = destDOM.value
const minPath = dijkstraObject.calcuate()
resultDOM.innerHTML = `
最短的路径: ${minPath} <br />
长度为: ${dijkstraObject.minDistance}
`
})
})
</script>
</body>
</html>