A-Star搜索算法找不到有效路径

3
我正在尝试在我的3D网格中实现A*算法进行路径规划。我一直在按照教程操作,但是没有得到有效的路径。我已经逐步检查了代码以找出问题所在,但是我不知道如何解决问题。对于最基本的测试,我只使用了一个2D网格(它是3D的,但只有一个Z选项,因此基本上是2D)。

enter image description here

所以我们从0,0(橙色)开始,希望到达1,2(绿色)。首先计算橙色方块的两个选项,向北和向东,并得到2和1.414的距离,F值分别为3和2.414。它移动到东侧方块(0,1)。很好。但现在它计算了从0,1开始的两个开放方块,它们是1,1和0,2,它们的g值为2,h值(距离)为1,使它们的F值都为3。
由于它们的F值为3,而我们已经有一个F值为3的选项(起始点的1,0),这两个选项被忽略,尽管它们显然是最佳选项。
然后它继续向前移动,并切换到移动到1,0,然后再次计算1,1为3,2,0为4.236。虽然1,1的f值不大于我们当前的f值,但它被忽略了,我们向上移动到2,0。
2,0只能向右移动,所以它这样做了。

由于2,2是无效的方块,2,1只能向下移动,但移动到1,1的f值为3被保存,因此再次被忽略,使得我们在0,0和1,2之间没有有效路径。我错过了什么吗?

这是我的路径循环的一小段代码。这里有一堆自定义结构体,在使用Unreal Engine中的TMap存储我的关闭列表,但我认为那对问题没有影响。以下是关于这些结构体的简要说明:

  • PCell:保存单元格坐标
  • PPair:将单元格坐标保存为PCell和F值
  • FVectorInt:三维整数向量
  • FPathCell:保存父坐标以及f、g和h值。
  • cellDetails是一个FPathCell的三维动态数组
  • closedMap是一个TMap,其中<key, value><IntVector, bool>

另外,locationIsWalkable(FVectorInt, StepDirection)仅仅是检查玩家是否可以从特定方向走到一个单元的代码。您可以忽略该部分。

    std::set<PPair> openList;
    PPair originPair = PPair();
    originPair.cell = PCell(i, j, k);
    originPair.f = 0.0;
    openList.insert(originPair);

    bool foundDestination = false;
    FPathCell destPair;
    FVectorInt destCell;

    while (!openList.empty() && !foundDestination)
    {
        iterations++;
        PPair p = *openList.begin();
        
        //Remove vertex
        openList.erase(openList.begin());

        //Add vertex to closed list
        i = p.cell.i;
        j = p.cell.j;
        k = p.cell.k;
        closedMap.Remove(FIntVector(i, j, k));
        closedMap.Add(FIntVector(i, j, k), true);

        double gNew, hNew, fNew;

        //Generate movement options
        //Option 1: NORTH (+X)
        //Process if valid movement
        if (locationIsWalkable(FVectorInt(i + 1, j, k), StepDirection::North))
        {
            FVectorInt check = FVectorInt(i + 1, j, k);

            //If this cell is the destination
            if (check == destination)
            {
                foundDestination = true;
                
                //Set the parent of the destination cell
                cellDetails[check.x][check.y][check.z].parent_i = i;
                cellDetails[check.x][check.y][check.z].parent_j = j;
                cellDetails[check.x][check.y][check.z].parent_k = k;
                destPair = cellDetails[check.x][check.y][check.z];
                destCell = check;
                break;
            }

            //Else if this cell is not in the closed list
            else if (!closedMap.FindRef(FIntVector(check.x, check.y, check.z)))
            {
                gNew = cellDetails[i][j][k].g + 1;
                hNew = calculateHValue(check, destination);
                fNew = gNew + hNew;

                if (cellDetails[check.x][check.y][check.z].f == FLT_MAX ||
                    cellDetails[check.x][check.y][check.z].f > fNew) {
                    
                    PPair cellPair = PPair();
                    cellPair.cell = PCell(check.x, check.y, check.z);
                    cellPair.f = fNew;
                    openList.insert(cellPair);

                    cellDetails[check.x][check.y][check.z].f = fNew;
                    cellDetails[check.x][check.y][check.z].g = gNew;
                    cellDetails[check.x][check.y][check.z].h = hNew;
                    cellDetails[check.x][check.y][check.z].parent_i = i;
                    cellDetails[check.x][check.y][check.z].parent_j = j;
                    cellDetails[check.x][check.y][check.z].parent_k = k;
                }
            }
        }

        //11 other movement options
    }

inline bool operator<(const PPair& lhs, const PPair& rhs)
{
    return lhs.f < rhs.f;
}

这里有12种移动选项(北、南、东、西、上+北、下+北等),但它们基本上都使用相同的代码,只是将check向量替换为适当的移动方式。

这是我遵循的教程


你能提供一个 [mcve] 吗?另外,你说你已经逐步执行了代码,那么我想知道为什么你不能说它何时走错了路? - Ulrich Eckhardt
1个回答

4
由于它们的F值为3,而我们已经有一个F值为3的选项(从起点1,0开始),即使它们明显是最佳选项,这两个选项也会被忽略。
这一定是您的错误。 这些选项不应该被“忽略”,而是“推迟到它们成为次佳选项时”。 实现的方式是在每次A*迭代中选择具有最低F分数的开放单元格。
在您的示例中,一旦您扩展了0,1(以获取0,21,1),您的开放集应如下所示:
(1,0):3   (1,1):3   (0,2):3

(它也可以是这些的任何其他排列,因为它们具有相同的分数。)
现在想象一下它选择访问1,0。 它将2,0添加到队列中,但是1,10,2仍应该存在:
(1,1):3   (0,2):3   (2,0):4.236

因为2,0的F值比1,10,2高,所以它还不会被选择。相反,您的算法应在此迭代中选择1,10,2,从而到达目标1,2

至于您的代码,您正在使用std::set作为openList,这可以防止在队列中具有相同得分的多个实例。您可以使用multisetpriority_queue来解决这个问题。但是,A *可以降低开放集中节点的权重,并且任何数据结构都不允许在子线性时间内执行该操作。通过多次插入相同的节点(每次其分数降低时),并忽略在关闭后的任何弹出,您仍将获得正确但次优的算法。

适当的A*实现通常使用二项式堆或斐波那契堆。不幸的是,C ++没有它们。您可以在网上找到实现这些内容的库。


openList的定义是第一个代码块的第一行。它是一个std::set,因此如果要插入的新单元格具有与集合中现有F值匹配的F值,则自动失败。我必须更改该数据类型,以便它不会忽略它们。 - Liftoff
@David:糟糕,我错过了那个:X。是的,您可以使用“multiset”或“priority_queue”,但两者都不允许减少插入节点的分数,因此您应该准备好可能多次弹出相同的节点。您可以忽略第一个之后的所有出现,尽管此解决方案并不是最佳选择。 - Yakov Galka

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接