具有寻路网格生成的边缘情况的困难

问题描述

我正在尝试开发 this answer 的实现,以支持可能大于单个单元的单元的 A-star 寻路。

我遇到的问题是目前我的输出中存在一个错误,我不确定我是否实施不当,或者算法是否没有正确处理这种边缘情况。

这是一个完整的复制品,演示了这个问题:

function createThickGrid(inputGrid,width,height) {
    let outputGrid = [];

    let largeInteger = 100000000;

    for (let i = 0; i < width * height; i++) {
        if (inputGrid[i] === 0) { // wall
            outputGrid[i] = -1;
        } else {
            outputGrid[i] = largeInteger;
        }
    }

    for (let i = 0; i < width * height; i++) {
        if (outputGrid[i] > 0) {
            outputGrid[i] = getSmallestNeighbor(outputGrid,i,height) + 2;
        }
    }

    return outputGrid;
}

function getSmallestNeighbor(grid,idx,height) {
    let col = idx % width;
    let row = Math.floor(idx / width);

    let smallest = 99999999999999;

    if (row <= 0 || row >= height - 1 || col <= 0 || col >= width - 1) {
        return -1;
    }

    let northValue = grid[idx - width];
    if (northValue < smallest) {
        smallest = northValue;
    }

    let northWestValue = grid[idx - width - 1];
    if (northWestValue < smallest) {
        smallest = northWestValue;
    }

    let northEastValue = grid[idx - width + 1];
    if (northEastValue < smallest) {
        smallest = northEastValue;
    }

    let southValue = grid[idx + width];
    if (southValue < smallest) {
        smallest = southValue;
    }

    let southWestValue = grid[idx + width - 1];
    if (southWestValue < smallest) {
        smallest = southWestValue;
    }

    let southEastValue = grid[idx + width + 1];
    if (southEastValue < smallest) {
        smallest = southEastValue;
    }

    let westValue = grid[idx - 1];
    if (westValue < smallest) {
        smallest = westValue;
    }

    let eastValue = grid[idx + 1];
    if (eastValue < smallest) {
        smallest = eastValue;
    }

    return smallest;
}

// You can ignore this,it's just for pretty printing
function convert1DTo2D(grid,height) {
    let arr = [];

    for (let row = 0; row < height; row++) {

        let newRow = [];

        for (let col = 0; col < width; coL++) {
            newRow.push(grid[row * width + col]);
        }

        arr.push(newRow);
    }

    return arr;
}

let width = 5;
let height = 5;

// 0 == wall and 1 == free space

grid = [1,1,1];

let thickGrid = createThickGrid(grid,height);

// console table works better but I don't kNow if Stackoverflow supports it
// console.table(convert1DTo2D(thickGrid,height));

console.log(convert1DTo2D(thickGrid,height));

如您所见,以下输出是这样的:

[1,1]
[1,3,5,1]

除了倒数第二行的中间值外,这似乎几乎完全正确。我认为应该是 3 而不是 5

这些数字指的是它们与最近的墙之间的半瓦距离(在这种情况下,最近的墙是阵列的边缘)。我认为应该是以下内容

[1,1]

但我不确定为什么它不是这样。是算法错误还是我的实现有缺陷?

解决方法

确实,您的算法不会那样工作。更新距离时,这可能会影响您已经处理过的单元格(北)的距离,应根据当前单元格刚刚获得的距离来减少该距离。

解决这个问题的算法是面包优先遍历:这将使您按照距离的顺序访问一个单元格。所以你从墙壁开始,找到他们的邻居并将它们放在队列的末尾。从队列的开始处理单元格。这样,您将找到从墙壁到任何单元格的最短路径,并可以正确确定它们的距离。

有几种方法可以实现这种广度优先遍历。我个人喜欢用两个普通数组而不是单个队列来做到这一点。每次将所有邻居添加到数组时,您都会将该数组作为下一次迭代的基础,其中距离将增加 2。下一次迭代将使用邻居填充 new 数组。在迭代结束时,新数组被当作旧数组,一切重复,直到找不到更多的邻居:

function createThickGrid(inputGrid,width) {
    let height = inputGrid.length / width; // height can be derived...
    let outputGrid = [];

    // Add all walls to a list
    let wave = [-1]; // special wall value to denote the "wall" outside the grid
    for (let i = 0; i < width * height; i++) {
        if (inputGrid[i] === 0) { // Wall
            outputGrid[i] = -1; // Mark wall in the output
            wave.push(i);
        }
    }
    let distance = 1;
    while (wave.length) { // Each iteration we deal with the next distance
        let nextWave = [];
        for (let i of wave) {
            // Collect unvisited neighbors of a cell
            let neighbors = getNeighbors(i);
            // Mark their distance
            for (let i of neighbors) outputGrid[i] = distance;
            // Schedule their treatment for next outer iteration
            nextWave.push(...neighbors);
        }
        // Prepare next iteration
        wave = nextWave;
        distance += 2;
    }
        
    return outputGrid;

    function getNeighbors(i) {
        let neighbors = [];
        if (i >= 0) { // normal case
            if (i % width) neighbors.push(i - 1 - width,i - 1,i - 1 + width);
            neighbors.push(i - width,i + width);
            if ((i+1) % width) neighbors.push(i + 1 - width,i + 1,i + 1 + width);
        } else { // all edge cells neighbor a "wall"
            neighbors = [...Array(width).keys(),...Array.from({length: height-2},(_,i) => (i + 1) * width),i) => (i + 2) * width - 1),...Array.from({length: width},i) => (height - 1) * width + i)
            ];
        }
        return neighbors.filter(i => 
            i >= 0 && i < width * height && outputGrid[i] === undefined
        );
    }
}

function convert1DTo2D(grid,width) {
    return Array.from({length: grid.length / width},i) =>
        grid.slice(i*width,(i+1)*width)
    );
}

let width = 5;
// 0 == wall and 1 == free space
let grid = [
    1,1,];

let thickGrid = createThickGrid(grid,width);

for (let row of convert1DTo2D(thickGrid,width)) {
    console.log(JSON.stringify(row));
}