Wikipedia A * 경로 찾기 알고리즘에 많은 시간이 걸립니다


9

C #에서 A * 경로 찾기를 성공적으로 구현했지만 속도가 느리고 이유를 이해할 수 없습니다. 심지어 openNodes 목록을 정렬하지 않았지만 여전히 동일합니다.

맵은 80x80이며 10-11 개의 노드가 있습니다.

여기에서 의사를했다 위키 백과

그리고 이것은 내 구현입니다.

 public static List<PGNode> Pathfind(PGMap mMap, PGNode mStart, PGNode mEnd)
    {
        mMap.ClearNodes();

        mMap.GetTile(mStart.X, mStart.Y).Value = 0;
        mMap.GetTile(mEnd.X, mEnd.Y).Value = 0;

        List<PGNode> openNodes = new List<PGNode>();
        List<PGNode> closedNodes = new List<PGNode>();
        List<PGNode> solutionNodes = new List<PGNode>();

        mStart.G = 0;
        mStart.H = GetManhattanHeuristic(mStart, mEnd);

        solutionNodes.Add(mStart);
        solutionNodes.Add(mEnd);

        openNodes.Add(mStart); // 1) Add the starting square (or node) to the open list.

        while (openNodes.Count > 0) // 2) Repeat the following:
        {
            openNodes.Sort((p1, p2) => p1.F.CompareTo(p2.F));

            PGNode current = openNodes[0]; // a) We refer to this as the current square.)

            if (current == mEnd)
            {
                while (current != null)
                {
                    solutionNodes.Add(current);
                    current = current.Parent;
                }

                return solutionNodes;
            }

            openNodes.Remove(current);
            closedNodes.Add(current); // b) Switch it to the closed list.

            List<PGNode> neighborNodes = current.GetNeighborNodes();
            double cost = 0;
            bool isCostBetter = false;

            for (int i = 0; i < neighborNodes.Count; i++)
            {
                PGNode neighbor = neighborNodes[i];
                cost = current.G + 10;
                isCostBetter = false;

                if (neighbor.Passable == false || closedNodes.Contains(neighbor))
                    continue; // If it is not walkable or if it is on the closed list, ignore it.

                if (openNodes.Contains(neighbor) == false)
                {
                    openNodes.Add(neighbor); // If it isn’t on the open list, add it to the open list.
                    isCostBetter = true;
                }
                else if (cost < neighbor.G)
                {
                    isCostBetter = true;
                }

                if (isCostBetter)
                {
                    neighbor.Parent = current; //  Make the current square the parent of this square. 
                    neighbor.G = cost;
                    neighbor.H = GetManhattanHeuristic(current, neighbor);
                }
            }
        }

        return null;
    }

내가 사용하는 휴리스틱은 다음과 같습니다.

    private static double GetManhattanHeuristic(PGNode mStart, PGNode mEnd)
    {
        return Math.Abs(mStart.X - mEnd.X) + Math.Abs(mStart.Y - mEnd.Y);
    }

내가 무엇을 잘못하고 있지? 하루 종일 같은 코드를 계속보고 있습니다.


2
휴리스틱이 없으면 끝을 찾을 때까지 더 많은 노드를 거치면서 일반적으로 시간이 더 오래 걸립니다. 또한 정렬 된 상태로 정렬 된 정렬 된 목록을 사용하십시오 (바람직하게는 정렬 된 집합, 항목이 목록에 있는지 여부를 확인할 필요가없는 방법)
Elva

답변:


10

세 가지, 한 가지 잘못, 두 가지 의심스러운 것을 봅니다.

1) 모든 반복마다 정렬하고 있습니다. 하지마 우선 순위 대기열을 사용하거나 최소한 선형 검색을 수행하여 최소값을 찾으십시오. 실제로 전체 목록을 정렬 할 필요는 없습니다.

2) openNodes.Contains ()는 아마도 느릴 것입니다 (C # 목록의 세부 사항에 대해서는 확실하지 않지만 선형 검색을 수행합니다). 각 노드에 플래그를 추가하고 O (1)에서이를 수행 할 수 있습니다.

3) GetNeighborNodes ()가 느려질 수 있습니다.


2
2) 예, Contains ()가 상당히 느릴 것입니다. 모든 노드를 목록에 저장하는 대신 Dictionary <int, PGNode>를 사용하십시오. 그런 다음 O (1) 조회 시간이 표시되고 목록을 계속 반복 할 수 있습니다. 노드에 id 필드가 있으면 키에 해당 필드를 사용하십시오. 그렇지 않으면 PGNode.GetHashCode ()가 작동합니다.
Leniency

2
@Leniency : Dictionary <PGNode, PGNode>가 더 나을까요? 두 객체의 해시 코드는 동일하지만 동일하지 않을 수 있습니다. "따라서이 방법의 기본 구현을 해싱 목적으로 고유 한 객체 식별자로 사용해서는 안됩니다." msdn.microsoft.com/en-us/library/system.object.gethashcode.aspx-.NET 3.5는 HashSet을 제공합니다 . msdn.microsoft.com/en-us/library/bb359438.aspx

좋은 지적, HashSet에 대해 잊어 버렸습니다.
Leniency

9

우선 순위 힙을 사용해야한다는 점 외에도 휴리스틱을 잘못 이해했습니다. 당신은

if (isCostBetter)
{
    ...
    neighbor.H = GetManhattanHeuristic (현재, 이웃);
}
그러나 휴리스틱은 목적지까지의 거리에 대한 추정치입니다. 이웃을 처음 추가 할 때 한 번 설정해야합니다.
if (openNodes.Contains (neighbor) == false)
{
    neighbor.H = GetHeuristic (이웃, mEnd);
    ...
}

그리고 더 작은 지점으로에서 통과 할 수없는 노드를 필터링하여 A *를 단순화 할 수 GetNeighbourNodes()있습니다.


+1, 알고리즘 복잡성에 중점을두고 휴리스틱의 잘못된 사용을 완전히 놓쳤습니다!
ggambett의

4

메타 답변 : 성능 문제를 찾는 코드를 보면서 하루를 보내면 안됩니다 . 프로파일 러를 사용하면 5 분 동안 병목 현상이 발생한 위치를 정확하게 알 수 있습니다. 대부분의 프로파일 러를 무료로 다운로드하여 몇 분 안에 앱에 연결할 수 있습니다.


3

다른 노드의 F를 비교할 때 비교할 내용이 명확하지 않습니다. F는 G + H로 정의 된 속성입니까? 그것은해야한다. (Side-rant : 이것이 균일 한 액세스 원칙이 왜 엉뚱한지를 보여주는 예입니다.)

더 중요한 것은 매 프레임마다 노드를 다시 정렬하는 것입니다. A *는 우선 순위 큐 를 사용하여 단일 요소를 효율적으로 -O (lg n) 정렬하여 삽입하고 닫힌 노드를 신속하게 검사 할 수있는 세트를 사용해야 합니다. 알고리즘을 작성 했으므로 O (n lg n) 삽입 + 정렬이있어 런타임이 쓸모없는 비율로 증가합니다.

C #에 좋은 정렬 알고리즘이 있으면 O (n) 삽입 + 정렬을 얻을 수 있습니다. 여전히 너무 많습니다. 실제 우선 순위 대기열을 사용하십시오.


2

http://theory.stanford.edu/~amitp/GameProgramming/Heuristics.html

  • 극단적으로, h (n)이 0이면, g (n)만이 역할을하며 A *는 Dijkstra의 알고리즘으로 바뀌어 최단 경로를 찾을 수 있습니다.
  • h (n)이 항상 n에서 목표로 이동하는 비용보다 낮거나 같으면 A *는 가장 짧은 경로를 찾도록 보장됩니다. h (n)이 낮을수록 더 많은 노드 A *가 확장되어 속도가 느려집니다.
  • h (n)이 n에서 목표로 이동하는 비용과 정확히 같은 경우 A *는 최상의 경로 만 따르고 다른 것을 확장하지 않으므로 매우 빠릅니다. 모든 경우에이를 수행 할 수는 없지만 일부 특수한 경우에는 정확하게 수행 할 수 있습니다. 완벽한 정보가 주어지면 A *가 완벽하게 작동한다는 것을 아는 것이 좋습니다.
  • h (n)이 때때로 n에서 목표로 이동하는 비용보다 큰 경우 A *는 최단 경로를 찾을 수는 없지만 더 빨리 실행될 수 있습니다.
  • 다른 극단적 인 경우, h (n)이 g (n)에 비해 매우 높으면, h (n)만이 역할을하며 A *는 Best-First-Search로 바뀝니다.

'manhatten distance'를 사용하고 있습니다. 이것은 거의 항상 나쁜 휴리스틱입니다. 또한 링크 된 페이지에서 해당 정보를 보면 휴리스틱이 실제 비용보다 낮은 것으로 추측 할 수 있습니다.


-1, 문제는 휴리스틱이 아니라 구현입니다.

2

다른 제안 (이 제안보다 의심 할 여지없이 더 중요한 답변) 외에도 닫힌 '목록'을 일종의 해시 테이블로 변경하는 것이 또 다른 최적화입니다. 값을 빠르게 추가하고 컬렉션에 값이 있는지 빠르게 확인할 수 있도록 정렬 된 컬렉션 일 필요는 없습니다.


1

비용과 휴리스틱은 관계가 있어야합니다. H가 두 개의 다른 지점에서 계산되지만 절대 액세스되지 않는다는 단서가되어야합니다.


이것은 속성이 잘못 구현되었다고 가정합니다. 정의가 표시되지 않았기 때문에 가능하지만 코드에 즉각적인 문제가 두 가지 더 있습니다.
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.