У меня есть n элементов. Для примера, скажем, 7 элементов, 1234567. Я знаю, что их 7! = 5040 возможных перестановок этих 7 элементов.
Мне нужен быстрый алгоритм, состоящий из двух функций:
f (число) отображает число от 0 до 5039 в уникальную перестановку, а
f '(перестановка) отображает перестановку обратно на число, из которого она была сгенерирована.
Меня не волнует соответствие между числом и перестановкой, при условии, что каждая перестановка имеет свой уникальный номер.
Так, например, у меня могут быть функции, где
f(0) = '1234567'
f'('1234567') = 0
Самый быстрый алгоритм, который приходит на ум, - это перечислить все перестановки и создать таблицу поиска в обоих направлениях, так что после создания таблиц f (0) будет O (1), а f ('1234567') будет поиск по строке. Однако это требует памяти, особенно когда n становится большим.
Может ли кто-нибудь предложить другой алгоритм, который работал бы быстро и без недостатка памяти?
Ответы:
Чтобы описать перестановку n элементов, вы видите, что для позиции, в которой заканчивается первый элемент, у вас есть n возможностей, поэтому вы можете описать это числом от 0 до n-1. Для позиции, в которой заканчивается следующий элемент, у вас остается n-1 возможностей, поэтому вы можете описать это числом от 0 до n-2.
И так далее, пока у вас не будет n номеров.
В качестве примера для n = 5 рассмотрим перестановку, которая приводит
abcde
кcaebd
.a
, первый элемент оказывается во второй позиции, поэтому мы присваиваем ему индекс 1 .b
заканчивается на четвертой позиции, которая будет индексом 3, но это третья оставшаяся позиция, поэтому мы присваиваем ей 2 .c
попадает на первую оставшуюся позицию, которая всегда равна 0 .d
попадает в последнюю оставшуюся позицию, которая (из двух оставшихся позиций) равна 1 .e
попадает в единственную оставшуюся позицию с индексом 0 .Итак, у нас есть индексная последовательность {1, 2, 0, 1, 0} .
Теперь вы знаете, что, например, в двоичном числе xyz означает z + 2y + 4x. Для десятичного числа
это z + 10y + 100x. Каждая цифра умножается на некоторый вес, и результаты суммируются. Очевидная закономерность в весе, конечно, состоит в том, что вес равен w = b ^ k, где b - основание числа, а k - индекс цифры. (Я всегда буду считать цифры справа и начиная с индекса 0 для самой правой цифры. Точно так же, когда я говорю о «первой» цифре, я имею в виду крайнюю правую.)
Причина , почему веса для цифр следовать этому образцу, что наибольшее число , которое может быть представлено цифрами от 0 до к должно быть ровно 1 меньше , чем наименьшее число , которое может быть представлено только с помощью цифр , к + 1. В двоичном формате 0111 должен быть на единицу меньше 1000. В десятичном виде 099999 должен быть на единицу меньше 100000.
Кодирование с использованием переменной базы
. Интервал между последующими числами, равный 1, является важным правилом. Понимая это, мы можем представить нашу последовательность индексов с помощью числа с переменной базой . База для каждой цифры - это количество различных возможностей для этой цифры. Для десятичной дроби каждая цифра имеет 10 возможных вариантов, для нашей системы крайняя правая цифра будет иметь 1 возможность, а крайняя левая - n вариантов. Но поскольку крайняя правая цифра (последнее число в нашей последовательности) всегда равна 0, мы ее опускаем. Это означает, что у нас остались основания от 2 до n. В общем, k-я цифра будет иметь основание b [k] = k + 2. Максимальное допустимое значение для цифры k равно h [k] = b [k] - 1 = k + 1.
Наше правило о весах w [k] цифр требует, чтобы сумма h [i] * w [i], где i идет от i = 0 до i = k, была равна 1 * w [k + 1]. Повторяясь, w [k + 1] = w [k] + h [k] * w [k] = w [k] * (h [k] + 1). Первый вес w [0] всегда должен быть 1. Начиная с этого момента, у нас есть следующие значения:
(Общее соотношение w [k-1] = k! Легко доказывается по индукции.)
Число, которое мы получим при преобразовании нашей последовательности, будет тогда суммой s [k] * w [k], где k изменяется от 0 до n-1. Здесь s [k] - k-й (крайний правый, начиная с 0) элемент последовательности. В качестве примера возьмем наш {1, 2, 0, 1, 0} с удаленным крайним правым элементом, как упоминалось ранее: {1, 2, 0, 1} . Наша сумма равна 1 * 1 + 0 * 2 + 2 * 6 + 1 * 24 = 37 .
Обратите внимание: если мы возьмем максимальную позицию для каждого индекса, у нас будет {4, 3, 2, 1, 0}, и это преобразуется в 119. Поскольку веса в нашей числовой кодировке были выбраны так, что мы не пропускаем любые числа, действительны все числа от 0 до 119. Их ровно 120, то есть n! для n = 5 в нашем примере это ровно количество различных перестановок. Таким образом, вы можете видеть, что наши закодированные числа полностью определяют все возможные перестановки.
Декодирование из переменной базы
Декодирование аналогично преобразованию в двоичное или десятичное. Общий алгоритм таков:
Для нашего числа с переменной базой:
Это правильно декодирует наши 37 обратно в {1, 2, 0, 1} (
sequence
будет{1, 0, 2, 1}
в этом примере кода, но как бы то ни было ... при условии, что вы правильно индексируете). Нам просто нужно добавить 0 в правый конец (помните, что последний элемент всегда имеет только одну возможность для его новой позиции), чтобы вернуть нашу исходную последовательность {1, 2, 0, 1, 0}.Перестановка списка с использованием последовательности индексов
Вы можете использовать приведенный ниже алгоритм для перестановки списка в соответствии с определенной последовательностью индексов. К сожалению, это алгоритм O (n²).
Общее представление перестановок
Обычно вы представляете перестановку не так неинтуитивно, как мы, а просто по абсолютной позиции каждого элемента после применения перестановки. Наш пример {1, 2, 0, 1, 0} для
abcde
tocaebd
обычно представлен как {1, 3, 0, 4, 2}. Каждый индекс от 0 до 4 (или вообще от 0 до n-1) встречается в этом представлении ровно один раз.Применять перестановку в этой форме просто:
Инвертирование очень похоже:
Преобразование из нашего представления в общее представление
Обратите внимание, что если мы возьмем наш алгоритм для перестановки списка, используя нашу последовательность индексов, и применим его к перестановке идентичности {0, 1, 2, ..., n-1}, мы получим обратная перестановка, представленная в общем виде. ( {2, 0, 4, 1, 3} в нашем примере).
Чтобы получить неинвертированную премуляцию, мы применяем алгоритм перестановки, который я только что показал:
Или вы можете просто применить перестановку напрямую, используя алгоритм обратной перестановки:
Обратите внимание, что все алгоритмы работы с перестановками в общей форме - O (n), а применение перестановок в нашей форме - O (n²). Если вам нужно применить перестановку несколько раз, сначала преобразуйте ее в общее представление.
источник
1234
, f (4) = {0, 2, 0, 0} = 1342. И f '(1423) = {0, 1 1, 0} = 3. Этот алгоритм действительно вдохновляет. Интересно, это оригинальная работа из ОП. Я изучал и анализировал его некоторое время. И я считаю, что это правильно :){1, 2, 0, 1, 0}
->{1, 3, 0, 4, 2}
? И наоборот? Является ли это возможным? ( без преобразования между{1, 2, 0, 1, 0}
<-->{C, A, E, B, D}
, для чего требуется O (n ^ 2).) Если «наш стиль» и «общий стиль» нельзя преобразовать, на самом деле это две разные вещи, не так ли? Спасибо xЯ нашел алгоритм O (n), вот краткое объяснение http://antoinecomeau.blogspot.ca/2014/07/mapping-between-permutations-and.html
источник
Сложность может быть уменьшена до n * log (n), см. Раздел 10.1.1 («Код Лемера (инверсионная таблица)», стр.232ff) fxtbook: http://www.jjj.de/fxt/ #fxtbook перейдите к разделу 10.1.1.1 («Вычисления с большими массивами», стр. 235), чтобы узнать о быстром методе. Код (под лицензией GPL, C ++) находится на той же веб-странице.
источник
Это встроенная функция в J :
источник
Задача решена. Однако я не уверен, что вам все еще нужно решение после этих лет. LOL, я просто присоединился к этому сайту, так что ... Проверьте мой класс перестановки Java. Вы можете использовать индекс, чтобы получить перестановку символов, или дать перестановку символов, а затем получить индекс.
Вот мой класс премутации
и вот мой основной класс, показывающий, как его использовать.
Радоваться, веселиться. :)
источник
Каждый элемент может находиться в одной из семи позиций. Чтобы описать положение одного элемента, вам понадобятся три бита. Это означает, что вы можете сохранить положение всех элементов в 32-битном значении. Это далеко не эффективно, поскольку такое представление даже позволяет всем элементам находиться в одной позиции, но я считаю, что битовая маскировка должна быть достаточно быстрой.
Однако с более чем 8 позициями вам понадобится что-то более изящное.
источник
Вы можете кодировать перестановки, используя рекурсивный алгоритм. Если N-перестановка (некоторый порядок чисел {0, .., N-1}) имеет форму {x, ...}, тогда закодируйте ее как x + N *, кодируя (N-1) -перестановка, представленная "..." в числах {0, N-1} - {x}. Похоже на то, что вот код:
Это алгоритм O (n ^ 2). Бонусные баллы, если у кого-то есть алгоритм O (n).
источник
Какой интересный вопрос!
Если все ваши элементы являются числами, вы можете рассмотреть возможность преобразования их из строк в реальные числа. Тогда вы сможете отсортировать все перестановки, расположив их по порядку, и поместить их в массив. После этого вы будете открыты для любого из различных алгоритмов поиска.
источник
Я поспешил в своем предыдущем ответе (удален), но у меня есть фактический ответ. Он обеспечивается аналогичной концепцией, факто-радикальным , и связан с перестановками (мой ответ связан с комбинациями, прошу прощения за эту путаницу). Я ненавижу просто публиковать ссылки на Википедию, но моя запись, которую я сделал некоторое время назад, по какой-то причине непонятна. Так что я могу подробнее остановиться на этом позже, если потребуется.
источник
Об этом написана книга. Извините, но я не помню его названия (вполне вероятно, вы найдете его в Википедии). но в любом случае я написал реализацию этой системы перечисления на python: http://kks.cabal.fi/Kombinaattori Некоторые из них на финском, но просто скопируйте переменные code и name ...
источник
У меня был именно этот вопрос, и я подумал, что предоставлю свое решение Python. Это O (n ^ 2).
Это довольно просто; после создания фактического представления числа я просто выбираю и удаляю символы из строки. Удаление из строки - вот почему это решение O (n ^ 2).
Решение Антуана лучше для производительности.
источник
Связанный с этим вопрос - вычисление обратной перестановки, перестановки, которая восстановит переставленные векторы в исходный порядок, когда известен только массив перестановок. Вот код O (n) (в PHP):
Программное обеспечение Дэвида Спектора Springtime
источник