dikjstra寻路算法

至今,我偶尔会遇到一些寻路的寻求,虽然是偶尔,但时间久了,也就多了。

我对寻路是没有太多研究的,所以就去B站搜了一下,搜到一个很棒的dikjstra教程

我照着这个教程,用ts写了一下这个逻辑,分享给大家。

整体代码如下:

scss 复制代码
/* 节点关系图
 * id 唯一标志符,与其key值一致
 * adjacency 邻点
 * distance 当前点到起点的路径距离
 * prev 前面点
 */
type MapData = {
    id: number
    adjacency: number[]
    distance: number
    prev: undefined | MapData
}
const map: Map<number, MapData> = new Map([
    [0, { id: 0, adjacency: [1, 7], distance: Infinity, prev: undefined }],
    [1, { id: 1, adjacency: [0, 2, 7], distance: Infinity, prev: undefined }],
    [2, { id: 2, adjacency: [1, 3, 5, 8], distance: Infinity, prev: undefined }],
    [3, { id: 3, adjacency: [2, 4, 5], distance: Infinity, prev: undefined }],
    [4, { id: 4, adjacency: [3, 5], distance: Infinity, prev: undefined }],
    [5, { id: 5, adjacency: [2, 3, 4, 6], distance: Infinity, prev: undefined }],
    [6, { id: 6, adjacency: [5, 7, 8], distance: Infinity, prev: undefined }],
    [7, { id: 7, adjacency: [0, 1, 6, 8], distance: Infinity, prev: undefined }],
    [8, { id: 8, adjacency: [2, 6, 7], distance: Infinity, prev: undefined }],
])

/* 线段长度图
 * key用'-'符号将两个自个升序的id链接起来。
 */
const segmentLengthMap: { [k: string]: number } = {
    '0-1': 4,
    '0-7': 8,
    '1-2': 8,
    '1-7': 11,
    '2-3': 7,
    '2-5': 4,
    '2-8': 2,
    '3-4': 9,
    '3-5': 14,
    '4-5': 10,
    '5-6': 2,
    '6-7': 1,
    '6-8': 6,
    '7-8': 7,
}

let start = 0
const end = 4

// 标记节点集合
const mark = new Set<number>()
// 未标记节点集合
let unmark = new Set<number>()

// 最优路径
let optimal: number[] = []

// 初始化
init()
console.log(optimal)

/* 初始化
 * 定义起点start
 * 对起点做标记,将起点添加到最优路径mark中
 * 起点的路径距离默认为0
 * 起点的前面点默认为undefined
 * 让未标记点集合unmark等于map中除start之外的所有点
 * 寻路
 * 回溯
 */
function init(n: number = 0) {
    start = n
    const startNode = map.get(n)
    if (!startNode) {
        console.error('起点未找到')
        return
    }
    mark.add(n)
    startNode.distance = 0
    unmark = new Set([...map.values()].map((ele) => ele.id))
    unmark.delete(n)
    explorer(startNode)
    optimal = backtrack(map.get(end))
}

/* 寻路 */
function explorer(markNode: MapData) {
    /* 更新当前标记点的邻点的距离与前面点 */
    for (let n of markNode.adjacency) {
        // 若标记点集合中包含此点,则跳过此点
        if (mark.has(n)) {
            continue
        }
        // 根据id获取当前邻点
        const curNode = map.get(n)
        if (!curNode) {
            console.error('当前邻点未找到')
            continue
        }
        // 获取当前邻点过当前标记点后的路径距离
        const dist = getDistance2(curNode, markNode)
        /* 若dist小于当前邻点的已记录过的路径距离,则:
         * 更新当前邻点的路径距离为dist
         * 前面点为当前标记点
         */
        if (dist < curNode.distance) {
            curNode.distance = dist
            curNode.prev = markNode
        }
    }

    /* 从未标记点集合unmark中寻找到start的路径距离最短的点 */
    // 基于路径距离对unmark进行升序排序,然后取第一个点
    const nearest = [...unmark].sort((a, b) => {
        const [an, bn] = [map.get(a), map.get(b)]
        if (!an || !bn) {
            console.error('节点不存在')
            return 0
        }
        return getDistance1(an) - getDistance1(bn)
    })[0]

    /* 标记nearest,将其添加到标记点集合mark中,并从unmark中删除 */
    const nearestNode = map.get(nearest)
    if (!nearestNode) {
        console.error('最近点获取失败')
        return
    }
    mark.add(nearest)
    unmark.delete(nearest)

    /* 若unmark中还有没标记完的点,继续寻路 */
    if (unmark.size) {
        explorer(nearestNode)
    }
}

/* 获取node到起点start的路径距离 */
function getDistance1(node: MapData): number {
    // 当前节点的前面点
    const { prev } = node
    if (!prev) {
        // 没有前面点,具备此情况的点除了起点还有所有未探索过的点
        return Infinity
    }
    return getDistance2(node, prev)
}

/* 获取node过prev后,到起点start的路径距离 */
function getDistance2(node: MapData, prev: MapData): number {
    /* 获取当前点与前一个点的距离
     * 基于两个节点的id拼成segmentLengthMap中的key
     */
    const [n, p] = [node.id, prev.id]
    const key = [n, p].sort().join('-')
    const d = segmentLengthMap[key]
    if (d === undefined) {
        console.error('节点距离未找到')
        return Infinity
    }
    /*
     * 若前面点是起点,返回d
     * 否则,进行线段距离的累加
     */
    if (p === start) {
        return d
    } else {
        const ppNode = prev.prev
        if (!ppNode) {
            console.error('前面点的前一个点未找到')
            return Infinity
        }
        return d + getDistance2(prev, ppNode)
    }
}

/* 从结束点回溯路径 */
function backtrack(node: MapData | undefined): number[] {
    if (!node) {
        console.error('回溯点的前面点未找到')
        return []
    }
    const { id } = node
    if (id === start) {
        return [id]
    }
    return [node.id, ...backtrack(node.prev)]
}

最终,console.log(optimal) 输出的结果就是:[4, 5, 6, 7, 0]

这与dikjstra教程里的结果是一致的。

根据这个教程内容,做个笔记。

已知:

  • 节点关系map,即每个节点的邻点
  • 节点距离segmentLengthMap,存在联系的两个节点间的距离
  • 起点start为0
  • 终点end为4

注:上图的节点距离并非通过两点间的距离公式算出来的,所以我们不能通过线段长度来区分距离的大小。

求:起点到终点的最短路径

解:

1.对起点做标记,将起点添加到标记点集合mark中。

起点的路径距离默认为0,路径距离就是节点到起点的路径距离,所以起点到起点的默认距离为0。

起点的前面点默认为undefined,因为起点就是最源头的点,没有前面点。

2.让未标记点集合unmark等于map中除start之外的所有点。

3.遍历当前标记点(第一个标记点就是起点)的邻点。

若邻点在标记点集合mark中,跳过此点;

否则,若当前邻点过当前标记点的路径距离小于当前节点的已记录过的路径距离,则:

  • 更新当前邻点的路径距离为当前邻点过当前标记点的路径距离。
  • 更新当前邻点的前面点为当前标记点。

4.遍历未标记点集合unmark,从中找出路径最短的点nearest。

对nearest点做标记,将其添加到mark中,并从unmark中删除。

5.若unmark不为空,以nearest点为标记点,重复3,4,5步骤。

当unmark为空的时候,所有的节点就都完成了标记。

6.对终点进行回溯,寻找其前面点的前面点的前面点的前面点......,直到找到其源头的点-起点。

把上面的一堆前面点按序连在一起,就是最短路径了。

参考链接:www.bilibili.com/video/BV1zz...

相关推荐
北岛寒沫3 小时前
JavaScript(JS)学习笔记 1(简单介绍 注释和输入输出语句 变量 数据类型 运算符 流程控制 数组)
javascript·笔记·学习
everyStudy3 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
无心使然云中漫步5 小时前
GIS OGC之WMTS地图服务,通过Capabilities XML描述文档,获取matrixIds,origin,计算resolutions
前端·javascript
Bug缔造者5 小时前
Element-ui el-table 全局表格排序
前端·javascript·vue.js
xnian_5 小时前
解决ruoyi-vue-pro-master框架引入报错,启动报错问题
前端·javascript·vue.js
麒麟而非淇淋6 小时前
AJAX 入门 day1
前端·javascript·ajax
2401_858120536 小时前
深入理解MATLAB中的事件处理机制
前端·javascript·matlab
阿树梢6 小时前
【Vue】VueRouter路由
前端·javascript·vue.js
随笔写8 小时前
vue使用关于speak-tss插件的详细介绍
前端·javascript·vue.js
快乐牌刀片888 小时前
web - JavaScript
开发语言·前端·javascript