问题描述
我正在尝试开发 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));
}