Алгоритм генерации всех множеств m точек в nxnxn кубической решетке, которые уникальны по симметрии


10

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

Существует nxnxn кубическая решетка, например, если n = 2, она состоит из (0,0,0), (0,1,0), (1,0,0), (1,1,0), (0, 1,1), (0,0,1), (1,0,1), (1,1,1).

Из этой решетки я буду рекурсивно генерировать все множества m точек, что-то вроде:

solve(set_of_points) {
     if set_of_points.size = m, finish

     do some useful computation here

     for each point in lattice not in set_of_points:
         solve(set_of_points + new_point);
}

Затем это можно вызвать, начиная с пустого set_of_points.

Природа проблемы такова, что на самом деле мне не нужны все перестановки из m точек, а только те, которые уникальны при естественных симметриях куба.

Например, возьмите куб 2x2x2 и предположим, что мы хотим, чтобы все множества были равны 1 точке. В соответствии с базовым алгоритмом, приведенным выше, существует 8 различных наборов по 1 точке.

Однако, используя симметрии куба, мы можем уменьшить его до 1 уникального набора из 1 точек, поскольку все исходные 8 эквивалентны при симметрии куба (в этом случае все они являются «углами»).

Если куб имеет размер 2x2x2 и m = 2, в базовом алгоритме 28 наборов, но при симметрии он уменьшается до 3 (например, {(0,0,0), (1,0,0)}, {(0 , 0,0), (1,1,0)}, {(0,0,0), (1,1,1)})

Очевидно, что вычисление трех наборов точек намного лучше, чем 28, поэтому мой вопрос: как мне не генерировать наборы точек, которые симметрично эквивалентны уже сгенерированному набору? Или, если это невозможно, как я могу хотя бы немного уменьшить количество подходов.

(Обратите внимание - если m = 1, это относительно просто - просто выберите точки, которые ближе к (0,0,0), чем любая из других вершин, с небольшим перемалыванием на границах. Это для m> 1 это быть настоящей проблемой)


1
К симметрично эквивалентным вы относите какие операции: Зеркальные плоскости через центр? Точка-Инверсия через центр? Все три оси вращения по центру?
BmyGuest

Любая изометрия сработает
rbennett485

Если вы все еще рядом, будет ли разрешено повторение в m-наборе точек? Например, для m = 3 {{0,0,0), (1,1,1), (0,0,0)} считается одним допустимым выбором?
черная ручка

@blackpen нет, должно быть 3 уникальных пункта
rbennett485

Ответы:


1

Основная концепция:

(1) Мы можем рассматривать точку (0,0,0) просто как 000. Каждая точка в решетке теперь находится в простой последовательности. Первая точка - 000, затем 001, затем 010 011 100 101 110 и 111. В этом порядке вы попытаетесь добавить их в набор точек.

(2) Аналогично, множество {(0,0,0), (0,0,1), (0,1,0)} можно просто рассматривать как 000001010, а множество {(0,0,0) , (0,1,0), (0,0,1)} можно просто увидеть как 000010001. Два разных набора не могут иметь одинаковую последовательность, и вы можете легко увидеть, что 000001010 численно или в алфавитном порядке меньше, чем 000010001. Давайте назовем это установленным значением. Каждый возможный набор из N точек теперь имеет заданное значение, и все возможные наборы из N точек теперь попадают в простой упорядоченный список.

(3) Каждая изоморфная группа наборов точек имеет ровно один член, который будет иметь наименьшее значение набора. Это единственные, где мы действительно делаем «полезные вычисления».

(4) Вот часть, которая потребует значительной работы. Перед тем, как вы начнете решать (set_of_points + new_point), вы хотите посмотреть, снизит ли какой-либо изоморфизм установленное значение для set_of_points + new_point. Если какой-либо изоморфизм понизит установленное значение, то это НЕ элемент с наименьшим значением изоморфного множества. Мы пропускаем любую работу над этим new_point. Мы также пропускаем всю рекурсивную работу, которую мы сделали бы внутри этого решения (set_of_points ,андидат_point).

solve(set_of_points,new_point) {
 set_of_points = set_of_points + new_point
 do some useful computation here
 if set_of_points.size = m, compute how many isomophisms exist, apply that multiple, finish
 for(candidate_point = new_point+1 to last_point) { /skips point-permutations for free!/
  if ISOMORPH_TESTS_CANNOT_LOWER_VALUE_OF(set_of_points+candidate_point) {
   solve(set_of_points,candidate_point);
  }
 }
}

1

принимая обозначения ответа выше.

давайте сначала определим симметрию, предложенную функцией rotate (direction, number_of_time)

решение:

(1) создать хэш всего набора перестановок с флагом = 0 на каждом. например, для n = 2, m = 2 000 001 = 0 000 010 = 0 000 011 = 0 и т. д.

(2) начать с начального набора, например, я = 000,001

(3) поверните набор i во все направления, используя функцию поворота (или любую другую симметрию, которая вам нравится), например, функция поворота должна вызываться 24 раза для каждой перестановки поворота.

введите описание изображения здесь

объяснение: любое число 1-6 может быть перед вами, и каждое число может вращаться 4 раза, поэтому 6 * 4 = 24

(4) для каждого набора, восстановленного из комбинации, установите флаг хеш-значения в 1 (у него уже есть симметричный набор)

(5) обновить i до следующего набора, например i = 000,010

(6) если набор i в хэше уже отмечен, перейдите к (5), в противном случае перейдите к (3)

мы закончили, когда весь хеш помечен как 1.


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

1

Примечание: я думаю только о зеркальной симметрии, а не о вращательной симметрии.

Давайте предположим, что у нас есть (гипер) куб d измерений, каждый длиной n единиц (куб Рубика будет d = 3, n = 3 ).

Наивный алгоритм будет генерировать n ^ d комбинаций точек и проверять каждую на предмет симметрийного столкновения со всеми остальными.

Если мы представляем комбинацию точек в виде битового вектора длиной n ^ d бит, мы можем использовать карту (битовый вектор -> логический), чтобы пометить все симметрии битового вектора как true . Затем мы можем пропустить комбинацию, если она уже отмечена на карте.

Этот подход очень неэффективен: требуется карта с 2 ^ (n ^ d) записями, то есть битовая карта с таким количеством битов. (Для кубика Рубика это будет 2 ^ 27 = 128 Мбит = 16 МБ.)

Мы можем помнить только канонические представления, то есть такие битовые векторы, которые имеют наименьшее целочисленное значение, если они представлены как n ^ d -битное беззнаковое слово. Когда мы генерируем новую перестановку точек, мы генерируем все ее симметрии и только проверяем, видели ли мы симметрию с наименьшим числовым значением. Это позволит нам хранить карту только с 2 ^ n битами (всего 1 байт для кубика Рубика), потому что у нас есть 2 ^ d симметрии. Это заставляет нас генерировать эти 2 ^ d симметрии на каждом шаге, поэтому мы тратим O (2 ^ (d ^ n + d)) = O (2 ^ (d ^ n) * 2 ^ d) времени. Все еще беден.

Мы можем применить идею из предыдущего абзаца к одномерному случаю. Чтобы сгенерировать все комбинации в векторе длины d , мы можем просто увеличить двоичное число длиной d бита, начиная со всех 0s. Давайте разделим наш вектор на два d / 2- длинных сегмента, например, левый и правый. Мы можем заметить, что для каждого 1бита в левом сегменте нам нужно видеть только комбинации, 1биты которых находятся в симметричной позиции правой части. Иначе мы бы уже сгенерировали симметричную комбинацию раньше, когда позиции битов менялись местами, а 0перед ними стояли 1. Таким образом, для каждой позиции бита в правой половине (r) и симметричной позиции в левой половине(l) нам нужно только сгенерировать 3 комбинации: (l = 0, r = 0); (l = 1, r = 1); (l = 1, r = 0) . Таким образом, нам нужно только генерировать 2 ^ (d / 2) перестановок вектора длины d , что дает 3 комбинации для каждой перестановки.

Куб из d измерений можно построить из n ^ (d-1) векторов. Уловка выше дает нам векторы дешевле, чем наивный подход. Чтобы сгенерировать куб, нам нужно время O (n ^ (d-1) * 2 ^ (d / 2)) .

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

Теперь, если мы посмотрим на это измерение, мы можем использовать тот же трюк.

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

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

Хотя не полный алгоритм, я надеюсь, что это поможет.

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.