Вікіпедія Алгоритм накладання маршрутів займає багато часу


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);
    }

Що я роблю неправильно? Цілий день я продовжую дивитись на той самий код.


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

Відповіді:


10

Я бачу три речі, одна неправильна, дві підозрілі.

1) Ви сортуєте за кожною ітерацією. Не варто. Або скористайтеся чергою пріоритетів, або, принаймні, виконайте лінійний пошук, щоб знайти мінімум. Насправді вам не потрібно весь список сортувати за весь час!

2) openNodes.Contains (), ймовірно, повільний (не впевнений у специфіці списку C #, але я думаю, що це робить лінійний пошук). Ви можете додати прапор до кожного вузла і зробити це в O (1).

3) GetNeighborNodes () може бути повільним.


2
2) Так, вміст () буде досить повільним. Замість того, щоб зберігати всі ваші вузли у списках, використовуйте словник <int, PGNode>. Тоді ви отримуєте час пошуку O (1) і все ще можете повторити список. Якщо у вузлах є поле id, використовуйте це для ключа, інакше 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 (сусід) == false)
{
    сусід.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) вставки + сортування, якщо 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.

Ви використовуєте "manhatten distance". Це майже завжди погана евристика. Крім того, переглянувши цю інформацію з пов’язаної сторінки, ви можете здогадатися, що ваша евристика нижча за справжню вартість.


-1, проблема полягає не в евристиці, а в реалізації.

2

На додаток до інших головних відповідей (які, безсумнівно, є більш значущими, ніж ця пропозиція), ще одна оптимізація полягає у зміні закритого "списку" в якусь хеш-таблицю. Вам не потрібно, щоб це була впорядкована колекція, просто щоб мати можливість швидко додавати значення та швидко перевірити, чи існують вони у колекції.


1

Ваша вартість та ваша евристична потреба мати стосунки. Має бути поняття, що Н обчислюється у двох різних місцях, але до них ніколи не звертаються.


Це передбачає, що властивість реалізовано неправильно, що можливо, оскільки її визначення не показано, але є ще дві негайні проблеми з кодом.
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.