Я реализую алгоритм, который будет довольно сложным в вычислительном отношении, и хочу попытаться убедиться, что я не делаю ненужную работу.
Существует 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) Мы можем рассматривать точку (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).
источник
принимая обозначения ответа выше.
давайте сначала определим симметрию, предложенную функцией 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.
источник
Примечание: я думаю только о зеркальной симметрии, а не о вращательной симметрии.
Давайте предположим, что у нас есть (гипер) куб 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 бита, начиная со всех
0
s. Давайте разделим наш вектор на два 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-й вектор куба (например, крайний левый верхний), мы можем исключить множество векторов одной плоскости (например, верхний) на основе значения конкретного бита. Таким образом, для вектора в зеркально-симметричном положении на плоскости мы можем избежать генерации всех комбинаций, у которых может быть установлен (или не установлен) этот бит, что значительно сократит количество векторов, которые мы должны сгенерировать для конкретной плоскости. Каждый исключенный бит делит пополам число возможных векторов в зеркально отраженном положении. Это дает нам последовательность плоскостей без симметричных аналогов вдоль каждого измерения.
Этот прием может быть применен для дальнейшего ограничения генерации перестановок следующих плоскостей в третьем измерении и т. Д.
Хотя не полный алгоритм, я надеюсь, что это поможет.
источник