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...

相关推荐
轻口味1 小时前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王1 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发2 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
yg_小小程序员3 小时前
vue3中使用vuedraggable实现拖拽
typescript·vue
高山我梦口香糖4 小时前
[react 3种方法] 获取ant组件ref用ts如何定义?
typescript·react
真滴book理喻5 小时前
Vue(四)
前端·javascript·vue.js
程序员_三木5 小时前
Three.js入门-Raycaster鼠标拾取详解与应用
开发语言·javascript·计算机外设·webgl·three.js
prall7 小时前
实战小技巧:下划线转驼峰篇
前端·typescript
开心工作室_kaic7 小时前
springboot476基于vue篮球联盟管理系统(论文+源码)_kaic
前端·javascript·vue.js
川石教育7 小时前
Vue前端开发-缓存优化
前端·javascript·vue.js·缓存·前端框架·vue·数据缓存