Список координат (x, y) для сортировки по спиральному алгоритму
У меня есть список координат для сортировки с помощью спирального алгоритма. Мне нужно начать с середины области и" коснуться " любой координаты.
для упрощения это представление (несортированного) списка координат (x, y, отмеченных "точкой" на следующем изображении).
CSV список координат доступен здесь.
X увеличение слева направо
Y увеличивается сверху вниз
coordinates" src="/images/content/28323023/5517af7738e7516b2dec060e416d4783.png"> Каждый координат не примыкает к следующему, а вместо этого дистанцируется 1 или 2 кубиками (или более в определенном случае).
начиная с центра области, мне нужно коснуться любой координаты со спиральным движением:
spiral approach" src="/images/content/28323023/8406272d11840dfa584d8791617748c2.gif">
чтобы проанализировать каждую координату, я разработал этот алгоритм PHP:
//$missing is an associative array having as key the coordinate "x,y" to be touched
$direction = 'top';
$distance = 1;
$next = '128,127'; //starting coordinate
$sequence = array(
$next;
)
unset($missing[$next]);
reset($missing);
$loopcount = 0;
while ($missing) {
for ($loop = 1; $loop <= 2; $loop++) {
for ($d = 1; $d <= $distance; $d++) {
list($x,$y) = explode(",", $next);
if ($direction == 'top') $next = ($x) . "," . ($y - 1);
elseif ($direction == 'right') $next = ($x + 1) . "," . ($y);
elseif ($direction == 'bottom') $next = ($x) . "," . ($y + 1);
elseif ($direction == 'left') $next = ($x - 1) . "," . ($y);
if ($missing[$next]) {
unset($missing[$next]); //missing is reduced every time that I pass over a coordinate to be touched
$sequence[] = $next;
}
}
if ($direction == 'top') $direction = 'right';
elseif ($direction == 'right') $direction = 'bottom';
elseif ($direction == 'bottom') $direction = 'left';
elseif ($direction == 'left') $direction = 'top';
}
$distance++;
}
но поскольку координаты не равноудалены друг от друга, я получаю этот вывод:
Как ясно видно, движение в середине правильное, тогда как и соответственно с координатной позицией, в определенный момент скачок между координатами больше не является когерентным.
Как я могу изменить свой код, чтобы получить такой подход, как этот?
упростить / уменьшить проблему: представьте, что точки На изображении выше-это города, которые продавец должен посетить cirurarly. Начиная с "города" в центре района, следующие города, которые необходимо посетить, расположены рядом с начальной точкой и расположены на севере, востоке, Сауче и западе от начальной точки. Коммивояжер не может посетить какой-либо другой город, если не были посещены все соседние города в круге отправной точки. Все города должны быть посещены только один раз.
2 ответов
алгоритм проектирования
во-первых, освободите свой ум и не думайте о спирали! :- ) Тогда сформулируем ограничения алгоритмов (воспользуемся точкой зрения продавца):
Я в настоящее время в городе и ищу, куда идти дальше. Я должен найти город:
- где я не был до
- это как можно ближе к центру (чтобы продолжать спирали)
- это как можно ближе к моему текущий город
теперь, учитывая эти три ограничения, вы можете создать детерминированный алгоритм, который создает спираль (ну, по крайней мере, для данного примера, вы, вероятно, можете создавать случаи, требующие больше усилий).
реализация
во-первых, потому, что мы можем идти в любом направлении, позволяет вообще использовать Евклидово расстояние для вычисления расстояния.
затем, чтобы найти следующий город, чтобы на странице:
$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
петля, показанная выше. Рассмотрим эту картину:
вы не захотите идти в эти города сейчас (чтобы продолжать спираль, вы не должны идти назад), поэтому даже не учитывайте их расстояние. Хотя это немного сложнее понять, если ваши точки данных распределены не так равномерно, как в вашем предоставленном например, эта оптимизация должна предоставить вам здоровую спираль для более нарушенных наборов данных.
Обновление: Правильность
сегодня меня вдруг осенило, и я переосмыслил предложенное решение. Я заметил случай, когда использование двух евклидовых расстояний может привести к нежелательному поведению:
легко можно построить случай, когда синяя линия определенно короче желтой и, таким образом, становится предпочтительной. Однако это нарушило бы спиральное движение. Чтобы исключить такие случаи, мы можем использовать направление движения. Рассмотрим следующее изображение (прошу прощения за нарисованные вручную углы):
основная идея состоит в том, чтобы вычислить угол между предыдущим направлением движения и новым направлением движения. В настоящее время мы находимся в желтой точке и должны решить, куда идти дальше. Зная предыдущую точку, мы можем получить вектор, представляющий предыдущее направление движение (например, розовая линия).
затем мы вычисляем вектор для каждого города, который мы рассматриваем, и вычисляем угол к предыдущему вектору движения. Если этот вектор
// 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
обратите внимание, чтобы правильно вычислить угол и векторы. Если вам нужна помощь в этом, я могу продолжить или просто задать новый вопрос.
проблема, кажется, в if-условном, когда вы пропускаете траверс координаты, то есть из-за округления углов. Другое условие с обратным к предыдущему вычислению координаты исправит его.