A* / Dijkstra算法简单实现(Pascal)

3
我正在尝试使用这篇文章 链接 实现A*寻路算法(目前是没有启发式的Dijkstra算法)。但我无法找出代码中的错误(它找到了错误的路径)。
在空的begin ... end;之后,应该执行以下步骤:
如果它已经在open list上,请检查是否通过G成本衡量更好。较低的G成本意味着这是一条更好的路径。如果是这样,请将方格的父级更改为当前方格,并重新计算方格的G和F分数。
但我认为这不重要,因为没有对角线移动。
uses
    crt;

const
    MAXX = 20;
    MAXY = 25;

type
    TArr = array [0..MAXY, 0..MAXX] of integer;
    
    TCell = record
        x: integer;
        y: integer;
    end;
    
    TListCell = record
        x: integer;
        y: integer;
        G: integer;
        parent: TCell;
    end;
    
    TListArr = array [1..10000] of TListCell;
    
    TList = record
        arr: TListArr;
        len: integer;
    end;

var
    i, j, minind, ind, c: integer;
    start, finish: TCell;
    current: TListCell;
    field: TArr;
    opened, closed: TList;

procedure ShowField;
var
    i, j: integer;
begin
    textcolor(15);
    for i := 0 to MAXX do
    begin
        for j := 0 to MAXY do
        begin
            case field[j, i] of
                99: textcolor(8);  // not walkable
                71: textcolor(14); // walkable
                11: textcolor(10); // start
                21: textcolor(12); // finish
                15: textcolor(2);  // path
                14: textcolor(5);
                16: textcolor(6);
            end;
            write(field[j, i], ' ');
        end;
        writeln;
    end;
    textcolor(15);
end; 


procedure AddClosed(a: TListCell);
begin
    closed.arr[closed.len + 1] := a;
    inc(closed.len);
end;


procedure AddOpened(x, y, G: integer);
begin
    opened.arr[opened.len + 1].x := x;
    opened.arr[opened.len + 1].y := y;
    opened.arr[opened.len + 1].G := G;
    inc(opened.len);
end;

procedure DelOpened(n: integer);
var
    i: integer;
begin
    AddClosed(opened.arr[n]);
    for i := n to opened.len - 1 do
        opened.arr[i] := opened.arr[i + 1];
    dec(opened.len);
end;


procedure SetParent(var a: TListCell; parx, pary: integer);
begin
    a.parent.x := parx;
    a.parent.y := pary;
end;


function GetMin(var a: TList): integer;
var
    i, min, mini: integer;
begin
    min := MaxInt;
    mini := 0;
    for i := 1 to a.len do
        if a.arr[i].G < min then
        begin
            min := a.arr[i].G;
            mini := i;
        end;
    
    GetMin := mini;
end;


function FindCell(a: TList; x, y: integer): integer;
var
    i: integer;
begin
    FindCell := 0;
    for i := 1 to a.len do
        if (a.arr[i].x = x) and (a.arr[i].y = y) then
        begin
            FindCell := i;
            break;
        end;
end;


procedure ProcessNeighbourCell(x, y: integer);
begin
    if (field[current.x + x, current.y + y] <> 99) then    // if walkable
        if (FindCell(closed, current.x + x, current.y + y) <= 0) then // and not visited before
            if (FindCell(opened, current.x + x, current.y + y) <= 0) then // and not added to list already
            begin
                AddOpened(current.x + x, current.y + y, current.G + 10);
                SetParent(opened.arr[opened.len], current.x, current.y);
                //  field[opened.arr[opened.len].x, opened.arr[opened.len].y]:=16;
            end
                else
            begin
                
            end;
end;


begin
    randomize;
    for i := 0 to MAXX do
        for j := 0 to MAXY do
            field[j, i] := 99;
    
    for i := 1 to MAXX - 1 do
        for j := 1 to MAXY - 1 do
            if random(5) mod 5 = 0 then
                field[j, i] := 99
            else field[j, i] := 71;
  
    // start and finish positions coordinates
    start.x := 5;
    start.y := 3;
    finish.x := 19;
    finish.y := 16;
    field[start.x, start.y] := 11;
    field[finish.x, finish.y] := 21;
    
    ShowField;
    
    writeln;
    
    opened.len := 0;
    closed.len := 0;
    AddOpened(start.x, start.y, 0);
    SetParent(opened.arr[opened.len], -1, -1);
    current.x := start.x;
    current.y := start.y;
    
    repeat
        minind := GetMin(opened);
        current.x := opened.arr[minind].x;
        current.y := opened.arr[minind].y;
        current.G := opened.arr[minind].G; 
        DelOpened(minind); 
        
        ProcessNeighbourCell(1, 0);  // look at the cell to the right
        ProcessNeighbourCell(-1, 0); // look at the cell to the left
        ProcessNeighbourCell(0, 1);  // look at the cell above
        ProcessNeighbourCell(0, -1); // look at the cell below
      
        if (FindCell(opened, finish.x, finish.y) > 0) then
            break;
    until opened.len = 0;
    
    // count and mark path
    c := 0;
    while ((current.x <> start.x) or (current.y <> start.y)) do
    begin
        field[current.x, current.y] := 15;
        ind := FindCell(closed, current.x, current.y);
        current.x := closed.arr[ind].parent.x;
        current.y := closed.arr[ind].parent.y;
        inc(c);
    end;
    
    
    ShowField;
    writeln(c);
    readln;
end.

编辑 2012年2月1日:更新代码,还修正了路径标记(应该是或而不是并),现在看起来它可以工作了 :)

2个回答

3

您应该重写程序,使用循环而不是剪切和粘贴访问每个邻居。如果这样做,您将避免以下错误:

if (field[current.x, current.y - 1] <> 99) then
    if (FindCell(closed, current.x, current.y - 1) <= 0) then
        if (FindCell(opened, current.x + 1, current.y) <= 0) then

关于循环,我在想这样的东西(伪代码Python):

(请看最后一行中不一致的current.x + 1, current.y。)


neighbor_offsets = [(0, 1), (0, -1), (1, 0), (-1, 0)]
for offset in neighbor_offsets:
    neighbor = current + offset
    if is_walkable(neighbor) and not is_visited(neighbor):
        # Open 'neighbor' with 'current' as parent:
        open(neighbor, current)

        # Perhaps check if the goal is reached:
        if neighbor == finish:
            goal_reached = True
            break

如果您不编写循环而只是进行重构
ProcessCell(x+1, y); 
ProcessCell(x-1, y); 
ProcessCell(x, y-1); 
ProcessCell(x, y-1);

那么这也是一个很大的改进。

这是个好主意,谢谢,我会重写它 :) 尽管修改这行代码并没有解决问题。但你能解释一下“访问每个邻居的循环”的意思吗?像是 ProcessCell(x+1, y); ProcessCell(X-1, y); ProcessCell(x, y-1); ProcessCell(x, y-1); 这样的东西代替了吗? - Alex P.
1
@Alex11223 注意,程序中的“current.x + 1”不一致性必须至少修复两次。我已经添加了更多关于代码结构的讨论。 - antonakos
更新了代码,同时修正了路径标记(应该是或而不是和),看起来现在可以工作了 :) - Alex P.

2
你发布了相当多的代码,你尝试过缩小它失败的范围吗?
你是否将你的代码与维基百科上的伪代码进行比较?
还要记住,Dijkstra算法只是带有0启发式的A*算法。 编辑: 你链接的文章(我现在意识到这正是我用来学习A*的文章)包含了插图步骤。我建议你重新创建那张地图/网格,并在其上运行你的实现。然后按照以下方式进行:
  1. 八个初始邻居是否添加到了开放列表中?它们是否具有正确的父节点?
  2. 根据启发式准则,是否选择了正确的开放节点作为下一个要扫描的节点?
  3. 关闭节点列表是否正确?
  4. 等等...

失败等同于崩溃吗?程序可以运行,但路径不正确(例如上面的截图)。我无法理解我的算法实现中错在哪里=\ 看起来像是在场/坐标移动实现中的某个地方。 - Alex P.

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