Алгоритм нахождения двух точек, наиболее удаленных друг от друга - PullRequest
28 голосов
/ 25 января 2009

Я ищу алгоритм, который будет использоваться в гоночной игре, которую я создаю. Карта / уровень / трек генерируется случайным образом, поэтому мне нужно найти два местоположения, начало и цель, которые используют большую часть карты.

  • Алгоритм работы внутри двухмерного пространства
  • Из каждой точки можно перейти только к следующей точке в четырех направлениях; вверх, вниз, влево, вправо
  • Точки могут быть заблокированы или неблокированы, только неблокированные точки могут быть пройдены

Что касается расчета расстояния, это не должно быть "птичий путь" из-за отсутствия лучшего слова. Путь между А и В должен быть длиннее, если между ними есть стена (или другая область блокировки).

Я не уверен, с чего начать, комментарии очень приветствуются, и предлагаемые решения предпочтительнее в псевдокоде.

Редактировать: Справа. Посмотрев код gs , я сделал еще один снимок. Вместо python я на этот раз написал это на C ++. Но, тем не менее, даже после прочтения алгоритма Dijkstras , решения floodfill и Hosam Alys я не могу обнаружить каких-либо существенных различий. Мой код все еще работает, но не так быстро, как кажется, вы запускаете свой. Полный источник на pastie . Единственные интересные строки (я полагаю) - это сам вариант Дейкстры в строках 78-118.

Но скорость здесь не главное. Я был бы очень признателен за помощь, если бы кто-нибудь проявил любезность и указал на различия в алгоритмах.

  • В алгоритме Хосама Алиса единственное отличие состоит в том, что он сканирует от границ вместо каждого узла?
  • В Dijkstras вы отслеживаете и перезаписываете пройденное расстояние, но не в заливке, но что по этому поводу?

Ответы [ 9 ]

10 голосов
/ 25 января 2009

Предполагая, что карта прямоугольная, вы можете перебрать все граничные точки и начать заливку, чтобы найти наиболее удаленную точку от начальной точки:

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 - это предположение о том, что заливка будет проходить через точку максимум 4 раза (со всех сторон). Поскольку n эквивалентно количеству точек, это эквивалентно (L + W) * 8 * n, что должно быть лучше, чем O(n 2 ). (Если карта квадратная, порядок будет O(16n 1,5 ).)

Обновление: в соответствии с комментариями, поскольку карта представляет собой скорее лабиринт (чем тот, на котором я думал вначале с простыми препятствиями), вы можете сделать ту же логику выше, но проверяя все точки в карта (в отличие от точек на границе). Это должно быть порядка O(4n 2 ), что все еще лучше, чем у F-W и Dijkstra's.

Примечание: Заполнение потоком больше подходит для этой задачи, поскольку все вершины напрямую связаны только через 4 границы. Первый обход карты в ширину может дать результаты относительно быстро (всего за O(n)). Я предполагаю, что каждая точка может быть проверена в заливке от каждого из его 4 соседей, таким образом, коэффициент в формулах выше.

Обновление 2: Я благодарен за все положительные отзывы, которые я получил относительно этого алгоритма. Отдельное спасибо @Georg за его обзор .

P.S. Любые комментарии или исправления приветствуются.

9 голосов
/ 25 января 2009

Ответьте на вопрос о Флойд-Варшалле или простом алгоритме Хосам Али :

Я создал тестовую программу, которая может использовать оба метода. Это файлы:

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

Это были времена, каждый раз, когда поле было четверным, и 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      -

Время Хосама Али выглядит квадратичным, поэтому я бы рекомендовал использовать этот алгоритм. Кроме того, потребление памяти Floyd-Warshall составляет n 2 , что явно больше, чем необходимо. Если у вас есть идеи, почему Floyd-Warshall такой медленный, пожалуйста, оставьте комментарий или отредактируйте этот пост.

PS: Я давно не писал C или C ++, надеюсь, я не совершил слишком много ошибок.

5 голосов
/ 26 января 2009

Звучит так, как будто вы хотите получить конечные точки, разделенные диаметром графика . Довольно хорошее и простое для вычисления приближение состоит в том, чтобы выбрать случайную точку, найти самую дальнюю точку из нее, а затем найти самую дальнюю точку оттуда. Эти последние две точки должны быть близки к максимально разделенным.

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

5 голосов
/ 25 января 2009

Я удалил свой оригинальный пост, рекомендующий алгоритм Флойд-Варшалла. (

gs сделал реалистичный тест и угадайте, что F-W существенно медленнее, чем алгоритм «заливки» Хосама Али для типичных размеров карт! Таким образом, даже несмотря на то, что F-W - крутой алгоритм и намного быстрее, чем у Дейкстры для плотных графов, я больше не могу рекомендовать его для задачи ОП, которая включает в себя очень разреженные графы (каждая вершина имеет только 4 ребра).

Для записи:

  • Эффективная реализация алгоритма Дейкстры занимает время O (Elog V) для графа с E ребрами и V вершинами.
  • «Заполнение» Хосама Али - это поиск в ширину , то есть O (V). Это можно рассматривать как частный случай алгоритма Дейкстры, в котором ни у одной вершины не может быть пересмотрена оценка расстояния.
  • Алгоритм Флойда-Варшалла занимает время O (V ^ 3), его очень легко кодировать, и он по-прежнему самый быстрый для плотных графов (тех графов, где вершины обычно связаны со многими другими вершинами) , Но это не правильный выбор для задачи ОП, которая включает в себя очень разреженные графики.
3 голосов
/ 26 января 2009

Раймунд Зейдель (Raimund Seidel) дает простой метод, использующий умножение матриц для вычисления матрицы расстояний всех пар на невзвешенном неориентированном графе (что именно вам нужно) в первом разделе своей статьи Проблема кратчайшего пути в невзвешенных неориентированных графах [PDF] .

Входными данными является матрица смежности, а выходными данными является матрица кратчайшего пути для всех пар. Время выполнения равно O (M (n) * log (n)) для n точек, где M (n) - время выполнения вашего алгоритма умножения матриц.

В статье также приводится метод вычисления фактических путей (в том же режиме выполнения), если вам это тоже нужно.

Алгоритм Зейделя хорош, потому что время выполнения не зависит от количества ребер, но нам здесь все равно, потому что наш график разрежен. Тем не менее, это может быть хорошим выбором (несмотря на то, что время выполнения немного хуже, чем n ^ 2), если вам нужна матрица расстояний всех пар, и это также может быть проще реализовать и отладить, чем заливка в лабиринте.

Вот псевдокод:

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)

1 голос
/ 29 января 2009

Хорошо, «алгоритм Хосама» - это поиск в ширину с предварительным выбором по узлам. Алгоритм Дейкстры НЕ должен применяться здесь, потому что у ваших ребер нет весов.

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

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

Кроме того, в лабиринте точки на внешней границе не обязательно являются частью самого длинного маршрута. Например, если у вас есть лабиринт в форме гигантской спирали, но с внешним концом, возвращающимся к середине, у вас может быть две точки, одна в центре спирали, а другая - в конце спирали, обе в середине!

Таким образом, хороший способ сделать это - использовать поиск в ширину в каждой точке, но удалить начальную точку после поиска (вы уже знаете все маршруты к нему и из него). Сложность ширины сначала O (n), где n = | V | + | E |. Мы делаем это один раз для каждого узла в V, поэтому он становится O (n ^ 2).

1 голос
/ 25 января 2009

Закончил макет Python решения dijkstra для задачи. Код стал немного длиннее, поэтому я разместил его где-то еще: http://refactormycode.com/codes/717-dijkstra-to-find-two-points-furthest-away-from-each-other

В указанном размере требуется около 1,5 секунд, чтобы запустить алгоритм для одного узла. Запуск его для каждого узла занимает несколько минут.

Кажется, не работает, он всегда отображает верхний и нижний правый угол как самый длинный путь; 58 плиток. Что, конечно, верно, когда у вас нет препятствий. Но даже добавив пару случайно размещенных, программа все равно находит этот самый длинный. Может быть, это все еще правда, трудно проверить без более продвинутых форм.

Но, может быть, это хотя бы покажет мои амбиции.

0 голосов
/ 25 января 2009

Если ваши объекты (точки) не двигаются часто, вы можете выполнить такой расчет за гораздо более короткое время, чем O (n ^ 3).

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

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

0 голосов
/ 25 января 2009

Ваше описание звучит как проблема лабиринт . Проверьте Алгоритм Ли . Книги о проблемах определения местоположения и маршрутизации в проектировании СБИС могут помочь вам - «Алгоритмы автоматизации физического проектирования СБИС» Шервани - это хорошо, и вы можете найти Автоматизация физического проектирования СБИС от Саита и Юсефа полезно (и дешевле в его версии Google ...)

...