Алгоритм поиска двух точек, наиболее удаленных друг от друга
Я ищу алгоритм, который будет использоваться в гоночной игре им делать. Карта / уровень / трек генерируется случайным образом, поэтому мне нужно найти два местоположения, начало и цель, что использует большую часть карты.
- алгоритм должен работать внутри двумерного пространства
- из каждой точки можно перейти в следующую точку только в четырех направлениях: вверх, вниз, влево, вправо
- точки может быть либо заблокирован, либо nonblocked, только очки nonblocked быть пройденным
Что касается расчета расстояния, это не должно быть "Птичий Путь" из-за отсутствия лучшего слова. Путь между A и B должен быть длиннее, если между ними есть стена (или другая блокирующая область).
Im не уверен, с чего начать, комментарии очень приветствуются, и предлагаемые решения предпочтительны в псевдо-коде.
Edit: право. После просмотра код gs я сделал еще один выстрел. Вместо python, я на этот раз написал его на C++. Но все же, даже после прочтения на алгоритм Dijkstras, the floodfill и решение Хосам Алис, я не вижу никакой существенной разницы. Мой код все еще работает, но не так быстро, как вы, похоже, запускаете свой. Полный источник включен накладка. Единственные интересные линии (я думаю) - это сам вариант Дийкстры на линиях 78-118.
но скорость здесь не главная проблема. Я бы очень ценю помощь, если кому-то будет достаточно любезен, чтобы указать на различия в алгоритмах.
- в алгоритме Hosam Alys единственная разница в том, что он сканирует границы вместо каждого узла?
- В Dijkstras вы отслеживаете и переписываете пройденное расстояние, но не в floodfill, но это о нем?
9 ответов
предполагая, что карта прямоугольная, вы можете обойти все пограничные точки и начать заливку, чтобы найти самую отдаленную точку от начальной точки:
bestSolution = { start: (0,0), end: (0,0), distance: 0 };
for each point p on the border
flood-fill all points in the map to find the most distant point
if newDistance > bestSolution.distance
bestSolution = { p, distantP, newDistance }
end if
end loop
я думаю, это будет O(n^2)
. Если я не ошибаюсь, это (L+W) * 2 * (L*W) * 4
, где L
- длина и W
- ширина карты, (L+W) * 2
представляет количество пограничных точек по периметру,(L*W)
- число точек, и 4
является предположением, что flood-fill будет иметь доступ к точке максимум 4 раза (со всех сторон). С n
эквивалентно количеству точек, это эквивалентно (L + W) * 8 * n
, что должно быть лучше, чем O(n
2)
. (Если карта квадратная, порядок будет O(16n
1.5)
.)
обновление: согласно комментариям, поскольку карта больше похожа на лабиринт (чем одна с простыми препятствиями, как я думал изначально), вы можете сделать ту же логику выше, но проверить все точки На карте (в отличие от точки На границе). Это должно быть в порядке O(4n
2)
, что все же лучше, чем и F-W и Dijkstra.
Примечание: флуд заполнения больше подходит для этой задачи, так как все вершины напрямую связаны только через 4 границы. Ширина первого обхода карты может дать результаты относительно быстро (всего за O(n)
). Я предполагаю, что каждый пункт может быть проверен наводнение заполняют от каждого из его 4 соседей, таким образом коэффициент в формулах выше.
обновление 2: я благодарен за все положительные отзывы, которые я получил по поводу этого алгоритма. Отдельное спасибо @Georg за его комментарий.
П. С. любые комментарии или исправления приветствуются.
следите за вопросом о Floyd-Warshall или простом алгоритме Хосам Али:
Я создал тестовую программу, которая может использовать оба метода. Вот эти файлы:
во всех тестовых случаях Floyd-Warshall был на большую величину медленнее, вероятно, это связано с очень ограниченным количеством ребер, которые помогают этот алгоритм для достижения этого.
Это были времена, каждый раз, когда поле было вчетверо, и 3 из 10 полей были препятствием.
Size Hosam Aly Floyd-Warshall (10x10) 0m0.002s 0m0.007s (20x20) 0m0.009s 0m0.307s (40x40) 0m0.166s 0m22.052s (80x80) 0m2.753s - (160x160) 0m48.028s -
время Hosam Aly кажется квадратичным, поэтому я бы рекомендовал использовать этот алгоритм. Также потребление памяти Floyd-Warshall равно N2, явно больше, чем нужно. Если у вас есть идеи, почему Floyd-Warshall так медленно, пожалуйста, оставьте комментарий или отредактируйте этот пост.
PS: Я не написал C или C++ в течение долгого времени, надеюсь, я не сделал слишком много ошибок.
Я удалил свой оригинальный пост, рекомендующий алгоритм Флойда-Уоршелла. :(
gs сделал реалистичный тест и угадайте, что, F-W значительно медленнее, чем алгоритм "заливки" Hosam Aly для типичных размеров карты! Поэтому, хотя F-W-классный алгоритм и намного быстрее, чем Dijkstra для плотных графов, я не могу рекомендовать его больше для проблемы OP, которая включает в себя очень разреженные графы (каждая вершина имеет только 4 ребра).
для запись:
- эффективная реализация алгоритм Дейкстры занимает O (Elog V) время для графа с e ребрами и V вершинами.
- "заливка" Хосам Али-это широта первого поиска, то есть O (V). Это можно рассматривать как частный случай алгоритма Дейкстры, в котором ни одна вершина не может иметь пересмотренной оценки расстояния.
- на Флойд-Warshall алгоритм принимает время о (в^3), очень легко к код, и по-прежнему является самым быстрым для плотных графов (тех графов, где вершины обычно связаны со многими другими вершинами). Но это Не правильный выбор для задачи OP, которая включает в себя очень разреженные графики.
похоже, что вы хотите, это конечные точки, разделенные график диаметром. Довольно хорошо и легко вычислить аппроксимацию, чтобы выбрать случайную точку, найти самую дальнюю точку от нее, а затем найти самую дальнюю точку оттуда. Эти последние две точки должны быть близки к максимальному разделению.
для прямоугольного лабиринта это означает, что две заливки должны получить довольно хорошую пару начальных и конечных точек.
Раймунд Зайдель дает простой метод с использованием матричного умножения для вычисления матрицы расстояний всех пар на невзвешенном, неориентированном графе (что именно то, что вы хотите) в первом разделе его статьи о задаче "все пары-кратчайший путь" в невзвешенных неориентированных графах [pdf].
входной сигнал матрица смежности и выход матрица расстояния коротк-пути все-пар. Время выполнения-O (M(n) * log (n)) для n точек, где M (n)-время выполнения вашего алгоритма умножения матриц.
документ также дает метод вычисления фактических путей (в том же времени выполнения), если вам это тоже нужно.
алгоритм Зайделя классный, потому что время выполнения не зависит от количества ребер, но нам на самом деле все равно, потому что наш график разрежен. Тем не менее, это может быть хорошим выбором (несмотря на немного хуже, чем время выполнения n^2), Если вы хотите матрицу расстояний всех пар, и это также может быть проще реализуйте и отлаживайте, чем floodfill на Лабиринте.
вот псевдокод:
Let A be the nxn (0-1) adjacency matrix of an unweighted, undirected graph, G
All-Pairs-Distances(A)
Z = A * A
Let B be the nxn matrix s.t. b_ij = 1 iff i != j and (a_ij = 1 or z_ij > 0)
if b_ij = 1 for all i != j return 2B - A //base case
T = All-Pairs-Distances(B)
X = T * A
Let D be the nxn matrix s.t. d_ij = 2t_ij if x_ij >= t_ij * degree(j), otherwise d_ij = 2t_ij - 1
return D
чтобы получить пару точек с наибольшим расстоянием, мы просто возвращаем argmax_ij (d_ij)
закончил макет python решения dijkstra для этой проблемы. Код стал немного длинным, поэтому я разместил его где-то еще: http://refactormycode.com/codes/717-dijkstra-to-find-two-points-furthest-away-from-each-other
в размере, который я установил, для запуска алгоритма для одного узла требуется около 1,5 секунд. Запускать его для каждого узла занимает несколько минут.
кажется, не работает, хотя, он всегда отображает верхний и нижний правый угол как самый длинный пути; 58 плитки. Что, конечно, верно, когда у вас нет препятствий. Но даже добавив пару случайно размещенных, программа все равно находит этот самый длинный. Возможно, это все еще верно, трудно проверить без более продвинутых форм.
но, может быть, это может по крайней мере показать мои амбиции.
Ok, "алгоритм Хосама" - это первый поиск ширины с предварительным выбором на узлах. Алгоритм Дейкстры не должен применяться здесь, потому что ваши ребра не имеют Весов.
разница имеет решающее значение, потому что если вес ребер варьируется, вам нужно держать много вариантов (альтернативных маршрутов) открытыми и проверять их на каждом шаге. Это делает алгоритм более сложный. С широтой первого поиска вы просто исследуете все края один раз таким образом, что гарантируете, что вы найти кратчайший путь к каждому узлу. то есть, исследуя края в том порядке, в котором вы их находите.
Так что в принципе разница Дейкстры, чтобы 'возвратиться' и посмотрите на края его изучить, прежде чем чтобы убедиться, что он по кратчайшему пути, а в ширину поиск всегда знает, что он идет по кратчайшему пути.
кроме того, в лабиринте точки на внешней границе не гарантированно являются частью самого длинного маршрута. Например, если у вас есть лабиринт в форма гигантской спирали, но с внешним концом, возвращающимся к середине, вы можете иметь две точки: одну в центре спирали, а другую в конце спирали, обе в середине!
Итак, хороший способ сделать это-использовать широту первого поиска из каждой точки, но удалить начальную точку после поиска (вы уже знаете все маршруты к нему и от него). Сложность ширины сначала равна O (n), где n = |V|+|E|. Мы делаем это один раз для каждого узла в V, поэтому он становится O (n^2).
Если ваши объекты(точки) не перемещаются часто, вы можете выполнить такой расчет за гораздо более короткое время, чем O (n^3).
все, что вам нужно, это разбить пространство на большие сетки и предварительно рассчитать расстояние между сетками. Затем выбор пар точек, которые занимают наиболее удаленные сетки, является вопросом простого поиска таблицы. В среднем случае вам нужно будет попарно проверить только небольшой набор объектов.
Это решение работает, если метрики расстояния непрерывны. Таким образом если, например, на карте много барьеров (как в лабиринтах), этот метод может потерпеть неудачу.