heuristic寻路

1-heuristic寻路简介

heuristic是启发式的意思,因此heuristic寻路就是启发式寻路,其所用的算法是贪心算法。

启发式寻路之所以是启发式的,是因为在寻路的过程中,我们会给它一个提示,比如你每迈出一步的时候,要朝离目标点最近的位置迈出。

在上图中,从start节点向end节点寻路的时候,它所迈出的每一步都是离end最近的。

可是每一步最近不代表整条路就是最近的,比如它从上面越过障碍物会更近。

这种只在乎眼前的利益,不考虑全局得失的算法,就是贪心算法。

启发式寻路虽然有很大的缺陷,但它是寻路的基础算法之一,以后我们可以以此为基础进行不断优化。

2-ts代码实现

以下代码是寻路逻辑的实现。至于效果的显示,我是用canvas做的,不算重点,我就不贴出来了,想要的可以微信我(1051904257)。

typescript 复制代码
/* 地图尺寸 */
const mapSize = new Vector2(10, 10)

/* 起点 */
const start = 41
/* 终点 */
const end = 48

/* 障碍物 */
const obstruction: number[] = [33, 34, 44, 54, 64, 74]

/* 探索出的路 */
const road: number[] = []

/* 探索基点 */
let basic: number | null = start

/*  探索状态:finding探索中、fail探索失败、success探索成功 */
let state: 'finding' | 'fail' | 'success' = 'finding'

/* 开始探索 */
// 循环探索
while (state === 'finding') {
    explorer()
}
// 逐步探索
/* let n = 0
while (n < 2) {
    explorer()
    n++
} */

/* 寻路 */
function explorer() {
    if (basic === null) {
        return
    }
    const { width, height } = mapSize
    const p = getPos(basic)

    const x1 = Math.max(0, p.x - 1)
    const x2 = Math.min(width, p.x + 1)
    const y1 = Math.max(0, p.y - 1)
    const y2 = Math.min(height, p.y + 1)

    let minPoint: number | undefined
    let minDistance = Infinity

    for (let y = y1; y <= y2; y++) {
        for (let x = x1; x <= x2; x++) {
            const current = y * width + x
            // 判断是否是终点
            if (current === end) {
                state = 'success'
                return
            }
            if (
                current !== basic &&
                current !== start &&
                !road.includes(current) &&
                !obstruction.includes(current)
            ) {
                // 获取到目标最近的点
                const distance = getPos(end).distanceToSquared(new Vector2(x, y))
                if (distance < minDistance) {
                    minDistance = distance
                    minPoint = current
                }
            }
        }
    }
    
    if (minPoint === undefined) {
        state = 'fail'
    } else {
        /* 添加路线 */
        road.push(minPoint)
        //* 更新基点 */
        basic = minPoint
    }
}

/*获取点位 */
function getPos(n: number) {
    const { width } = mapSize
    return new Vector2(n % width, Math.floor(n / width))
}

解释一下上面的代码。

mapSize是地图尺寸,它没有单位的概念,不要将其理解为像素。

start起点、end终点的概念无需多说,其值是其在地图中的索引位。

我们可以通过getPos(n: number)方法获取索引位所对应的行列位。

arduino 复制代码
function getPos(n: number) {
    const { width } = mapSize
    return new Vector2(n % width, Math.floor(n / width))
}

obstruction 是障碍物,其内元素是构成障碍物的节点的索引位。

road 是探索出的道路,其内元素是构成道路的节点的索引位。

basic 是探索基点,比如一开始的探索基点是起点,从起点迈出离终点最近的一步后,我们会以这一步的节点为基点,继续往前探索。

state 是探索状态,finding探索中、fail探索失败、success探索成功。

explorer() 是寻路方法,它会遍历基点周边的8个节点,然后找到离终点最近的点,并记下探索状态state。state为finding时,会继续探索,否则停止探索。

ini 复制代码
while (state === 'finding') {
    explorer()
}

当然,为了便于查看寻路的过程,我们也可以逐步寻路。

scss 复制代码
let n = 0
while (n < 2) {
    explorer()
    n++
}

整体的heuristic寻路逻辑便是如此,不过它还有一个严重的bug,那就是可能会被死胡同堵死。

在数字为8的黑色格子处,已经无路可走了,便认为探索失败了。

在这种情况下,我可以尝试让它回头,回到上一个路口4,不再走这条死路,而是走离终点较近的另一条路。

接下来,我们就解决一下这个Bug。

3-回头是岸

突然想起一句话:学海无涯,回头是岸。人生路漫漫哪有一帆风顺的,遇到过不去的可以绕道试试。

言归正传,继续说一下heuristic遇到死胡同时,如何回头是岸,绕道而行。

整体代码如下:

typescript 复制代码
/* 地图尺寸 */
const mapSize = new Vector2(10, 10)

/* 起点 */
const start = 40
/* 终点 */
const end = 49

/* 障碍物 */
const obstruction: number[] = [
    31, 32, 42, 52, 62, 72, 33, 34, 44, 54, 64, 74, 36, 37, 38, 48, 58, 68, 78,
]

/* 探索过的基点集合 */
const explored: Map<number, number | undefined> = new Map([[start, undefined]])

/* 探索出的路 */
const road: number[] = []

/* 探索基点 */
let basic: number | null = start

/*  探索状态:finding探索中、fail探索失败、success探索成功 */
let state: 'finding' | 'fail' | 'success' = 'finding'

/* 开始探索 */
// 循环探索
while (state === 'finding') {
    explorer()
}
// 逐步探索
/* let n = 0
while (n < 22) {
    explorer()
    n++
} */

/* 寻路 */
function explorer(extrude?: number) {
    if (basic === null) {
        return
    }
    const { width, height } = mapSize
    const p = getPos(basic)

    const x1 = Math.max(0, p.x - 1)
    const x2 = Math.min(width, p.x + 1)
    const y1 = Math.max(0, p.y - 1)
    const y2 = Math.min(height, p.y + 1)

    let minPoint: number | undefined
    let minDistance = Infinity

    for (let y = y1; y <= y2; y++) {
        for (let x = x1; x <= x2; x++) {
            const current = y * width + x
            if (current === end) {
                state = 'success'
                return
            }
            if (
                current !== basic &&
                current !== start &&
                !road.includes(current) &&
                !obstruction.includes(current) &&
                ((extrude !== undefined && extrude !== current) ||
                    extrude === undefined)
            ) {
                // 获取到目标最近的点
                const distance = getPos(end).distanceToSquared(new Vector2(x, y))
                if (distance < minDistance) {
                    minDistance = distance
                    minPoint = current
                }
            }
        }
    }

    /* 基于前面点寻找除当前点之外的点 */
    if (minPoint === undefined) {
        const prev = explored.get(basic)
        if (prev !== undefined) {
            extrude = basic
            basic = prev
            explorer(extrude)
        } else {
            state = 'fail'
            return
        }
    } else {
        if (extrude !== undefined) {
            const ind = road.indexOf(extrude)
            if (ind !== -1) {
                road.splice(ind)
            }
        }
        /* 设置标记点 */
        explored.set(minPoint, basic)
        /* 添加路线 */
        road.push(minPoint)
        //* 更新基点 */
        basic = minPoint
    }
}

/* 获取点位 */
function getPos(n: number) {
    const { width } = mapSize
    return new Vector2(n % width, Math.floor(n / width))
}

效果如下:

说一下我们添加了哪些代码。

explored 是探索过的基点集合,其中元素的key就是节点的索引位,value 是其前面的点。

typescript 复制代码
const explored: Map<number, number | undefined> = new Map([[start, undefined]])

explored 可以让我们记住来路,以便回头。

explorer(extrude?: number) 方法中有了一个extrude参数,表示要在遍历周围8点时,排除的点。此点用于在会回头时,不再走过去被堵死的老路,而是走一条新路。

其排除逻辑如下:

yaml 复制代码
(extrude !== undefined && extrude !== current)

被堵时的回头逻辑如下:

ini 复制代码
if (minPoint === undefined) {
    const prev = explored.get(basic)
    if (prev !== undefined) {
        extrude = basic
        basic = prev
        explorer(extrude)
    } else {
        state = 'fail'
        return
    }
}else{
    ......
}

走出死胡同时,需要把死胡同里的路删掉。

scss 复制代码
if (minPoint === undefined) {
    ......
} else {
    if (extrude !== undefined) {
        const ind = road.indexOf(extrude)
        if (ind !== -1) {
            road.splice(ind)
        }
    }
    ......
}

关于heuristic寻路咱们就说到这,下一章我们会再说更优良的寻路方法。

相关推荐
轻口味33 分钟前
命名空间与模块化概述
开发语言·前端·javascript
前端小小王1 小时前
React Hooks
前端·javascript·react.js
迷途小码农零零发1 小时前
react中使用ResizeObserver来观察元素的size变化
前端·javascript·react.js
娃哈哈哈哈呀2 小时前
vue中的css深度选择器v-deep 配合!important
前端·css·vue.js
旭东怪2 小时前
EasyPoi 使用$fe:模板语法生成Word动态行
java·前端·word
xiaoshiguang33 小时前
LeetCode:222.完全二叉树节点的数量
算法·leetcode
爱吃西瓜的小菜鸡3 小时前
【C语言】判断回文
c语言·学习·算法
别NULL3 小时前
机试题——疯长的草
数据结构·c++·算法
TT哇3 小时前
*【每日一题 提高题】[蓝桥杯 2022 国 A] 选素数
java·算法·蓝桥杯