Ответ 1
Разработка алгоритма
Во-первых, освободи свой ум и не думай о спирали!:-) Затем давайте сформулируем ограничения алгоритмов (давайте использовать точку зрения продавца):
Сейчас я в городе, и я смотрю, куда идти дальше. Мне нужно найти город:
- где я не был раньше
- который максимально приближен к центру (чтобы сохранить спираль)
- как можно ближе к моему текущему городу.
Теперь, учитывая эти три ограничения, вы можете создать детерминированный алгоритм, который создает спираль (ну, по крайней мере, для данного примера он должен, вы, вероятно, можете создавать случаи, требующие большего усилия).
Реализация
Во-первых, поскольку мы можем ходить в любом направлении, обычно используйте Евклидово расстояние для вычисления расстояний.
Затем, чтобы найти следующий город для посещения:
$nextCost = INF;
$nextCity = null;
foreach ($notVisited as $otherCity) {
$cost = distance($current_city, $other_city) + distance($other_city, $centerCity);
if ($cost < $nextCost) {
$nextCost = $cost;
$nextCity = $otherCity;
}
}
// goto: $nextCity
Просто повторите это, пока не увидите больше городов.
Чтобы понять, как это работает, рассмотрите следующую картинку:
Сейчас я нахожусь в желтом круге, и мы предположим, что спираль до этого момента верна. Теперь сравните длину желтой, розовой и синей линий. Длина этих строк в основном вычисляется с использованием функций расстояния. Вы обнаружите, что в каждом случае следующий правильный город имеет наименьшее расстояние (ну, по крайней мере, пока у нас столько точек, вы, вероятно, можете легко встретить встречный пример).
Это должно помочь вам начать решение проблемы.
(Корректность) Оптимизация
В текущем проекте вам нужно будет сравнить текущий город со всеми остальными городами на каждой итерации. Однако некоторые города не представляют интереса и даже в неправильном направлении. Вы можете дополнительно оптимизировать правильность алгоритма, исключив некоторые города из поискового пространства, прежде чем вводить цикл foreach
, показанный выше. Рассмотрим эту картину:
Теперь вы не захотите отправиться в эти города (чтобы сохранить спираль, вы не должны идти назад), поэтому даже не учитывайте их расстояния. Хотя это немного сложнее понять, если ваши данные не так распределены так, как в приведенном примере, эта оптимизация должна обеспечить вам здоровую спираль для более тревожных наборов данных.
Обновление: правильность
Сегодня это внезапно поразило меня, и я переосмыслил предлагаемое решение. Я заметил случай, когда использование двух эвклидовых расстояний может привести к нежелательному поведению:
Можно легко построить случай, когда синяя линия определенно короче желтой и, таким образом, становится предпочтительной. Однако это нарушит движение спирали. Чтобы устранить такие случаи, мы можем использовать направление движения. Рассмотрим следующее изображение (извиняюсь за углы, наносимые рукой):
Основная идея состоит в том, чтобы вычислить угол между предыдущим направлением движения и новым направлением движения. Сейчас мы находимся на желтой точке и должны решить, куда идти дальше. Зная предыдущую точку, мы можем получить вектор, представляющий предыдущее направление движения (например, розовую линию).
Затем мы вычисляем вектор в каждом городе, который мы рассматриваем, и вычисляем угол к предыдущему вектору движения. Если этот вектор равен <= 180 град (случай 1 на изображении), то направление в порядке, в противном случае - нет (случай 2 на изображении).
// initially, you will need to set $prevCity manually
$prevCity = null;
$nextCost = INF;
$nextCity = null;
foreach ($notVisited as $otherCity) {
// ensure correct travel direction
$angle = angle(vectorBetween($prevCity, $currentCity), vectorBetween($currentCity, $otherCity));
if ($angle > 180) {
continue;
}
// find closest city
$cost = distance($current_city, $other_city) + distance($other_city, $centerCity);
if ($cost < $nextCost) {
$nextCost = $cost;
$nextCity = $otherCity;
}
}
$prevCity = $currentCity;
// goto: $nextCity
Обратите внимание на правильность вычисления угла и векторов. Если вам нужна помощь в этом, я могу уточнить или просто задать новый вопрос.