Wikipedia A * алгоритм поиска пути занимает много времени

9

Я успешно реализовал поиск путей A * в C #, но он очень медленный, и я не понимаю, почему. Я даже пытался не сортировать список 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);
    }

Что я делаю неправильно? Целый день я продолжаю смотреть на один и тот же код.

Vee
источник
2
Без эвристики это (обычно) может занять больше времени, поскольку вы проходите больше узлов, пока не найдете конец. Кроме того, попробуйте использовать отсортированный список, который остается отсортированным (предпочтительно отсортированный набор, так что вам не нужно проверять, существует ли элемент в списке, вы можете просто добавить его)
Elva

Ответы:

10

Я вижу три вещи, одну неправильную, две подозрительные.

1) Вы сортируете на каждой итерации. Не. Либо используйте приоритетную очередь, либо, по крайней мере, выполните линейный поиск, чтобы найти минимум. Вам на самом деле не нужно весь список сортировать все время!

2) openNodes.Contains (), вероятно, работает медленно (не уверен насчет специфики C #'s List, но я уверен, что он выполняет линейный поиск). Вы можете добавить флаг к каждому узлу и сделать это в O (1).

3) GetNeighborNodes () может быть медленным.

ggambett
источник
2
2) Да, Contains () будет довольно медленным. Вместо того, чтобы хранить все ваши узлы в списках, используйте Dictionary <int, PGNode>. Затем вы получаете O (1) время поиска и можете повторять список. Если узлы имеют поле идентификатора, используйте его для ключа, иначе PGNode.GetHashCode () будет работать.
снисходительность
2
@ Лентность: не лучше ли словарь <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.
снисходительность
9

В дополнение к уже высказанному замечанию, что вы должны использовать кучу приоритетов, вы неправильно поняли эвристику. У тебя есть

if (isCostBetter)
{
    ...
    сосед. H = GetManhattanHeuristic (текущий, сосед);
}
Но эвристика должна быть оценкой расстояния до места назначения. Вы должны установить его один раз, когда вы впервые добавляете соседа:
if (openNodes.Contains (сосед) == ложь)
{
    сосед. H = GetHeuristic (сосед, mEnd);
    ...
}

И, как еще один дополнительный момент, вы можете упростить A *, отфильтровывая непроходимые узлы в GetNeighbourNodes().

Питер Тейлор
источник
+1, я сосредоточился на алгоритмической сложности и совершенно пропустил неправильное использование эвристики!
ggambett
4

Мета-ответ: вы никогда не должны просто тратить день на разглядывание кода в поисках проблем с производительностью. Пять минут с профилировщиком покажут вам, где именно находятся узкие места. Вы можете скачать бесплатный журнал большинства профилировщиков и подключить его к своему приложению за несколько минут.

необычайно щедрый
источник
3

Не понятно, что вы сравниваете, когда сравниваете F разных узлов. Является ли F свойством, определенным как G + H? Должен быть. (Side-rant: Это пример того, почему принцип единого доступа - это дерьмо.)

Более важно то, что вы сортируете узлы каждый кадр. A * требует использования очереди приоритетов , которая позволяет эффективно - O (lg n) - сортировать вставку одного элемента и набора, который позволяет быстро проверять наличие закрытых узлов. Как вы написали алгоритм, у вас есть O (n lg n) вставка + сортировка, которая увеличивает время выполнения до бесполезных пропорций.

(Вы можете получить O (n) insert + sort, если C # имеет хороший алгоритм сортировки. Это все еще слишком много. Используйте очередь с реальным приоритетом.)


источник
2

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

  • В одном крайнем случае, если h (n) равно 0, то роль играет только g (n), и A * превращается в алгоритм Дейкстры, который гарантированно найдет кратчайший путь.
  • Если 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.

Вы используете «ручное расстояние». Это почти всегда плохая эвристика. Кроме того, глядя на эту информацию со связанной страницы, вы можете догадаться, что ваша эвристика ниже реальной стоимости.

Будет
источник
-1 проблема не в эвристике, а в реализации.
2

В дополнение к другим основным ответам (которые, несомненно, более важны, чем это предложение), еще одной оптимизацией является изменение закрытого «списка» в своего рода хэш-таблицу. Вам не нужно, чтобы это была упорядоченная коллекция, просто чтобы иметь возможность быстро добавлять значения и быстро видеть, существуют ли они в коллекции.

Kylotan
источник
1

Ваша стоимость и ваша эвристика должны иметь отношения. Должно быть подсказка, что H рассчитывается в двух разных точках, но никогда не используется.

Джей Белл
источник
Это предполагает, что свойство реализовано неправильно, что возможно, поскольку его определение не показано, но есть еще две непосредственные проблемы с кодом.