Список координат (x, y) будет сортироваться по спиральному алгоритму

У меня есть список координат, которые нужно сортировать по спиральному алгоритму. Мне нужно начинать посередине области и "прикасаться" к любой координате.

Для упрощения это представление (unsorted) списка координат (x, y, отмеченных "точкой" на следующем изображении).

Список координат CSV доступен здесь.
X увеличить слева направо
Y увеличивается с TOP до BOTTOM

unsorted list of coordinates Каждая координата не смежна с следующей, но вместо этого распределяется 1 или 2 кубиками (или более в определенном случае).

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

spiral approach

для разбора каждой координаты Я разработал этот алгоритм 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++;
 }

но поскольку координаты не равноудалены друг от друга, я получаю этот вывод:

sorted list of coordinates

Как ясно видно, движение в середине правильное, тогда как, соответственно, с координатным положением, в определенный момент скачок между каждой координатой больше не когерентен.

Как я могу изменить свой код, чтобы получить такой подход, как этот?

expected sorted list of coordinates

Чтобы упростить/уменьшить проблему.. Представьте, что точки на показанном выше изображении являются городами, которые продавец должен посетить cirurarly. Начиная с "города" в центре области, следующие города, которые нужно посетить, расположены вблизи начальной точки и расположены на севере, востоке, юго-востоке и западе от начальной точки. Продавец не может посещать какой-либо другой город, если не были посещены все прилегающие города в районе начальной точки. Все города должны посещаться только один раз.

Ответы

Ответ 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

Просто повторите это, пока не увидите больше городов.

Чтобы понять, как это работает, рассмотрите следующую картинку:

enter image description here

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

Это должно помочь вам начать решение проблемы.

(Корректность) Оптимизация

В текущем проекте вам нужно будет сравнить текущий город со всеми остальными городами на каждой итерации. Однако некоторые города не представляют интереса и даже в неправильном направлении. Вы можете дополнительно оптимизировать правильность алгоритма, исключив некоторые города из поискового пространства, прежде чем вводить цикл foreach, показанный выше. Рассмотрим эту картину:

enter image description here

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

Обновление: правильность

Сегодня это внезапно поразило меня, и я переосмыслил предлагаемое решение. Я заметил случай, когда использование двух эвклидовых расстояний может привести к нежелательному поведению:

enter image description here

Можно легко построить случай, когда синяя линия определенно короче желтой и, таким образом, становится предпочтительной. Однако это нарушит движение спирали. Чтобы устранить такие случаи, мы можем использовать направление движения. Рассмотрим следующее изображение (извиняюсь за углы, наносимые рукой):

enter image description here

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

Затем мы вычисляем вектор в каждом городе, который мы рассматриваем, и вычисляем угол к предыдущему вектору движения. Если этот вектор равен <= 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

Обратите внимание на правильность вычисления угла и векторов. Если вам нужна помощь в этом, я могу уточнить или просто задать новый вопрос.

Ответ 2

Проблема, по-видимому, связана с if-условием, когда вы пропускаете координату, из-за округления углов. А еще условный с обратным к предыдущему вычислению координаты зафиксировал бы его.