Получить перестановку с заданной степенью по номеру индекса

я работал над этим в течение нескольких часов, но не мог понять.

определите степень перестановки как минимальное количество транспозиций, которые необходимо составить для ее создания. Так что степень (0, 1, 2, 3) равно 0, степень (0, 1, 3, 2) равно 1, степень (1, 0, 3, 2) - 2 и т. д.

посмотрите на пространство Snd как пространство всех перестановок последовательности длины n, которые имеют степень d.

мне нужны два алгоритма. Тот, который берет перестановка в этом пространстве и присваивает ему номер индекса, а другой, который принимает номер индекса элемента в Snd и извлекает его перестановку. Номера индексов, очевидно, должны быть последовательными (т. е. в диапазоне 0 to len(Snd)-1, С каждой перестановкой, имеющей отдельный номер индекса.)

Я бы хотел, чтобы это реализовано в O(sane); что означает, что если вы просите номер перестановки 17, алгоритм не должен проходить все перестановки между 0 и 16, чтобы получить ваш перестановка.

есть идеи, как это решить?

(если вы собираетесь включить код, я предпочитаю Python, спасибо.)

обновление:

Я хочу решение, в котором

  1. перестановки упорядочиваются в соответствии с их лексикографическим порядком (и не вручную, а эффективным алгоритмом, который дает им лексикографический порядок для начала) и
  2. я хочу, чтобы алгоритм примите также последовательность различных степеней, поэтому я мог бы сказать: "я хочу, чтобы число перестановок 78 из всех перестановок степеней 1, 3 или 4 из пространства перестановок диапазона(5)". (В основном функция будет принимать кортеж градусов.) Это также повлияет на обратную функцию, которая вычисляет индекс из перестановки; на основе набора градусов индекс будет отличаться.

Я пытался решить это в течение последних двух дней, и я не был успешным. Если вы смогли обеспечить Код Python, это было бы лучше всего.

7 ответов


этот ответ менее элегантен/эффективен, чем мой другой, но он описывает алгоритм полиномиального времени, который справляется с дополнительными ограничениями на упорядочение перестановок. Я собираюсь описать подпрограмму, которая, учитывая префикс перестановки n-элемента и набор степеней, подсчитывает, сколько перестановок имеют этот префикс и степень, принадлежащую множеству. Учитывая эту подпрограмму, мы можем выполнить n-ary поиск перестановки указанного ранга в указанном подмножестве, расширяя известный префикс по одному элементу за раз.

мы можем визуализировать N-элементную перестановку p как N-вершинный, N-дуговой ориентированный граф, где для каждой вершины v существует дуга от v до p(v). Этот орграф состоит из набора вершинно-непересекающихся циклов. Например, перестановка 31024 выглядит как

 _______
/       \
\->2->0->3
 __     __
/  |   /  |
1<-/   4<-/ .

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

2->0->3
 __
/  |
1<-/ .

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

перестановки, упомянутые в (2), относятся к следующему набору элементов. Начнем с исходный набор, удалите все элементы, участвующие в циклах, которые завершены в префиксе, и введите новый элемент для каждого пути. Например, если префикс равен 310, то мы удаляем полный цикл 1 и вводим новый элемент A для пути 2->0->3, в результате чего получаем множество {4, A}. Теперь, учитывая перестановку в множестве (1), мы получаем перестановку в множестве (2), удаляя известные циклы и заменяя каждый путь его новым элементом. Например, перестановка 31024 соответствует перестановке 4 - >4, A - >A, а перестановка 31042 соответствует перестановке 4->A, A - >4. Я утверждаю (1), что эта карта является биекцией и (2) что она сохраняет степени, как описано выше.

определение, более или менее, (n, k)-го числа Стирлинга первого рода, написанное

[n]
[ ]
[k]

(квадратные скобки ASCII art) - количество N-элементных перестановок степени n-k. Чтобы вычислить число расширений префикса r-элемента перестановки n-элемента, подсчитайте c, число полных циклов в приставке. Сумма, для каждой степени d в указанном наборе, число Стирлинга

[  n - r  ]
[         ]
[n - d - c]

первого рода, принимая термины с" невозможными " индексами равными нулю (некоторые аналитически мотивированные определения чисел Стирлинга ненулевые в неожиданных местах).

чтобы получить ранг из перестановки, мы делаем N-ary поиск снова, за исключением этого времени, мы используем перестановку, а не ранг, чтобы направлять поиск.

вот некоторые Python код для обоих (включая тестовую функцию).

import itertools

memostirling1 = {(0, 0): 1}
def stirling1(n, k):
    ans = memostirling1.get((n, k))
    if ans is None:
        if not 1 <= k <= n: return 0
        ans = (n - 1) * stirling1(n - 1, k) + stirling1(n - 1, k - 1)
        memostirling1[(n, k)] = ans
    return ans

def cyclecount(prefix):
    c = 0
    visited = [False] * len(prefix)
    for (i, j) in enumerate(prefix):
        while j < len(prefix) and not visited[j]:
            visited[j] = True
            if j == i:
                c += 1
                break
            j = prefix[j]
    return c

def extcount(n, dset, prefix):
    c = cyclecount(prefix)
    return sum(stirling1(n - len(prefix), n - d - c) for d in dset)

def unrank(n, dset, rnk):
    assert rnk >= 0
    choices = set(range(n))
    prefix = []
    while choices:
        for i in sorted(choices):
            prefix.append(i)
            count = extcount(n, dset, prefix)
            if rnk < count:
                choices.remove(i)
                break
            del prefix[-1]
            rnk -= count
        else:
            assert False
    return tuple(prefix)

def rank(n, dset, perm):
    assert n == len(perm)
    rnk = 0
    prefix = []
    choices = set(range(n))
    for j in perm:
        choices.remove(j)
        for i in sorted(choices):
            if i < j:
                prefix.append(i)
                rnk += extcount(n, dset, prefix)
                del prefix[-1]
        prefix.append(j)
    return rnk

def degree(perm):
    return len(perm) - cyclecount(perm)

def test(n, dset):
    for (rnk, perm) in enumerate(perm for perm in itertools.permutations(range(n)) if degree(perm) in dset):
        assert unrank(n, dset, rnk) == perm
        assert rank(n, dset, perm) == rnk

test(7, {2, 3, 5})

перестановки длины n и степени d-это именно те, которые могут быть записаны как композиция из K = N - D циклов, которые разбивают n элементов. Число таких перестановок задается числа Стирлинга первого рода, написано N поверх k в квадратных скобках.

числа Стирлинга первого рода удовлетворяют рекуррентному отношению

[n]           [n - 1]   [n - 1]
[ ] = (n - 1) [     ] + [     ]
[k]           [  k  ]   [k - 1],

что означает, интуитивно, количество способов разбиения n элементов на K циклов-это разделите N-1 не-максимальных элементов на K циклов и соедините максимальный элемент одним из n - 1 способов или поместите максимальный элемент в свой собственный цикл и разделите n - 1 не-максимальных элементов на K - 1 циклов. Работая с таблицей значений повторения, можно проследить решения вниз по линии.

memostirling1 = {(0, 0): 1}
def stirling1(n, k):
    if (n, k) not in memostirling1:
        if not (1 <= k <= n): return 0
        memostirling1[(n, k)] = (n - 1) * stirling1(n - 1, k) + stirling1(n - 1, k - 1)
    return memostirling1[(n, k)]

def unrank(n, d, i):
    k = n - d
    assert 0 <= i <= stirling1(n, k)
    if d == 0:
        return list(range(n))
    threshold = stirling1(n - 1, k - 1)
    if i < threshold:
        perm = unrank(n - 1, d, i)
        perm.append(n - 1)
    else:
        (q, r) = divmod(i - threshold, stirling1(n - 1, k))
        perm = unrank(n - 1, d - 1, r)
        perm.append(perm[q])
        perm[q] = n - 1
    return perm

Я думаю, что вы ищете вариант расстояние Левенштейна который используется для измерения количества изменений между двумя строками. Эффективный способ вычислить это-использовать метод, называемый динамическим программированием-псевдо-алгоритм для "нормального" расстояния Левенштейна представлен в связанной статье. Вам нужно будет адаптировать это для учета того факта, что вместо добавления, удаления или замены символа единственной разрешенной операцией является обмен элементы в двух позициях.

Что касается вашего второго алгоритма: это не соотношение 1:1 между степенями перестановки и результирующей перестановкой "a", вместо этого количество возможных результатов растет экспоненциально с количеством свопов: для последовательности k элементы, есть k*(k-1)/2 возможные пары индексов, между которыми нужно поменять местами. Если мы назовем этот номер l, после d ОСП у вас есть l^d возможные результаты (хотя некоторые из них могут быть идентичными, как в сначала замена 01, затем 23 или сначала 2 3, затем 01).


Я написал этот ответ stackoverflow на аналогичную проблему:https://stackoverflow.com/a/13056801/10562 . Может ли это помочь?

разница может быть в бит подкачки для генерации perms, но функция index-to-perm и perm-to-index задана в Python.

позже я продолжил создавать эту задачу кода Rosetta, которая дополнена ссылками и большим количеством кода: http://rosettacode.org/wiki/Permutations/Rank_of_a_permutation.

надеюсь, что это помогает :-)


первая часть прямо вперед, если вы работаете полностью в лексиографической стороне вещей. Учитывая мой ответ на другой поток, вы можете мгновенно перейти от перестановки к факторному представлению. В принципе, вы представляете список {0,1,2,3}, а число, которое мне нужно, - это факториальное представление, поэтому для 1,2,3,4 я продолжаю брать нулевой элемент и получаю 000 (0*3+0*!2+0*!1!).

0,1,2,3, => 000

1032 = 3!+1! = 8-й permuation (как 000-это первую перестановку) => 101

и вы можете разработать степень тривиально, как каждая транспозиция, которая меняет пару чисел (a, b) a

So 0123 - > 1023 is 000 - > 100.

Если a>b вы меняете номера, а затем вычитаете один из номера правой руки.

учитывая две перестановки / лексиографические числа, я просто переставляю цифры справа налево, как сортировка пузырьков, подсчитывая степень, которая мне нужна, и создавая новый лексиографический номер, как я идти. Итак, чтобы перейти от 0123 к 1032, я сначала перемещаю 1 влево, затем ноль находится в правой позиции, а затем я перемещаю 2 в позицию, и у обоих из них были пары с числом rh, большим, чем число левой руки, поэтому оба добавляют 1, поэтому 101.

Это касается вашей первой проблемы. Второе гораздо сложнее, так как числа второй степени распределены неравномерно. Я не вижу ничего лучше, чем получить глобальный лексиографический номер (глобальный смысл здесь количество без каких-либо исключений) перестановки вы хотите, например, 78 в вашем примере, а затем пройти через все lexiographic цифры и каждый раз, что вы получите тот, который является степенью 2, а затем прибавить к вашей глобальной lexiographic количество, например, 78 -> 79, Когда вы находите первое число 2-й степени. Очевидно, это будет не быстро. В качестве альтернативы вы можете попробовать генерировать все числа степени. Учитывая набор из n элементов, существуют(n-1) (n-2) числа степени 2, но не ясно, что это по крайней мере, для меня, что может быть намного меньше работы, чем вычисление всех чисел до вашей цели. и вы могли бы просто посмотреть, какие из них имеют лексиографический номер меньше, чем ваш целевой номер, и снова добавить один к его глобальному лексиографическому номеру.

посмотрим, смогу ли я придумать что-нибудь получше.


это казалось забавным, поэтому я подумал об этом еще немного.

давайте возьмем пример Дэвида 31042 и найдем его индекс. Сначала мы определяем степень, которая равна сумме мощностей циклов перестановок, каждый из которых вычитается на 1.

01234
31042

permutation cycles (0342)(1)
degree = (4-1) + (1-1) = 3

def cycles(prefix):
  _cycles = []
  i = j = 0
  visited = set()

  while j < len(prefix):
    if prefix[i] == i:
      _cycles.append({"is":[i],"incomplete": False})
      j = j + 1
      i = i + 1
    elif not i in visited:
      cycle = {"is":[],"incomplete": False}
      cycleStart = -1
      while True:
        if i >= len(prefix):
          for k in range(len(_cycles) - 1,-1,-1):
            if any(i in cycle["is"] for i in _cycles[k]["is"]):
              cycle["is"] = list(set(cycle["is"] + _cycles[k]["is"]))
              del _cycles[k]
          cycle["incomplete"] = True
          _cycles.append(cycle)
          break
        elif cycleStart == i:
          _cycles.append(cycle)
          break
        else:
          if prefix[i] == j + 1:
            j = j + 1
          visited.add(i)
          if cycleStart == -1:
            cycleStart = i
          cycle["is"].append(i)
          i = prefix[i]
    while j in visited:
      j = j + 1
    i = j
  return _cycles

def degree(cycles):
  d = 0
  for i in cycles:
    if i["incomplete"]:
      d = d + len(i["is"])
    else:
      d = d + len(i["is"]) - 1
  return d

Далее мы определяем, сколько перестановок степени 3 начинаются с нуля, одного или двух; используя формулу Дэвида:

number of permutations of n=5,d=3 that start with "0" = S(4,4-3) = 6
number of permutations of n=5,d=3 that start with "1" = S(4,4-2) = 11

[just in case you're wondering, I believe the ones starting with "1" are:
 (01)(234)
 (01)(243)
 (201)(34)
 (301)(24)
 (401)(23)
 (2301)(4)
 (2401)(3)
 (3401)(2)
 (3201)(4)
 (4201)(3)
 (4301)(2) notice what's common to all of them?]

number of permutations of n=5,d=3 that start with "2" = S(4,4-2) = 11

мы задаемся вопросом, Может ли быть лексикографически-нижняя перестановка степень 3, которая также начинается с "310". Единственная возможность, кажется, 31024:

01234
31024 ?
permutaiton cycles (032)(4)(1)
degree = (3-1) + (1-1) + (1-1) = 2
since its degree is different, we will not apply 31024 to our calculation

перестановки степени 3, которые начинаются с "3 "и лексикографически ниже 31042, должны начинаться с префикса"30". Их количество равно числу способов, которыми мы можем поддерживать " три "перед" нулем "и" ноль "перед" одним " в наших циклах перестановок, сохраняя сумму мощности циклов, каждая из которых вычитается на 1 (т. е. степень), на 3.

(031)(24)
(0321)(4)
(0341)(2)
count = 3

это кажется, что есть 6 + 11 + 11 + 3 = 31 перестановка n=5, d=3 до 31042.

def next(prefix,target):
  i = len(prefix) - 1
  if prefix[i] < target[i]:
    prefix[i] = prefix[i] + 1
  elif prefix[i] == target[i]:
    prefix.append(0)
    i = i + 1
  while prefix[i] in prefix[0:i]:
    prefix[i] = prefix[i] + 1
  return prefix

def index(perm,prefix,ix):
  if prefix == perm:
    print ix
  else:
    permD = degree(cycles(perm))
    prefixD = degree(cycles(prefix))
    n = len(perm) - len(prefix)
    k = n - (permD - prefixD)
    if prefix != perm[0:len(prefix)] and permD >= prefixD:
      ix = ix + S[n][k]
    index(perm,next(prefix,perm),ix)

S = [[1]
    ,[0,1]
    ,[0,1,1]
    ,[0,2,3,1]
    ,[0,6,11,6,1]
    ,[0,24,50,35,10,1]]

(давайте попробуем подтвердить с помощью программы Дэвида (я использую ПК с windows):

C:\pypy>pypy test.py REM print(index([3,1,0,4,2],[0],0))
31

C:\pypy>pypy davids_rank.py REM print(rank(5,{3},[3,1,0,2,4]))
31

немного поздно и не в Python, а в C#...

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

algo вычисляет индекс перестановки и ее обратный.

using System;
using System.Collections.Generic;

namespace WpfPermutations
{
    public class PermutationOuelletLexico3<T>
    {
        // ************************************************************************
        private T[] _sortedValues;

        private bool[] _valueUsed;

        public readonly long MaxIndex; // long to support 20! or less 

        // ************************************************************************
        public PermutationOuelletLexico3(T[] sortedValues)
        {
            if (sortedValues.Length <= 0)
            {
                throw new ArgumentException("sortedValues.Lenght should be greater than 0");
            }

            _sortedValues = sortedValues;
            Result = new T[_sortedValues.Length];
            _valueUsed = new bool[_sortedValues.Length];

            MaxIndex = Factorial.GetFactorial(_sortedValues.Length);
        }

        // ************************************************************************
        public T[] Result { get; private set; }

        // ************************************************************************
        /// <summary>
        /// Return the permutation relative to the index received, according to 
        /// _sortedValues.
        /// Sort Index is 0 based and should be less than MaxIndex. Otherwise you get an exception.
        /// </summary>
        /// <param name="sortIndex"></param>
        /// <param name="result">Value is not used as inpu, only as output. Re-use buffer in order to save memory</param>
        /// <returns></returns>
        public void GetValuesForIndex(long sortIndex)
        {
            int size = _sortedValues.Length;

            if (sortIndex < 0)
            {
                throw new ArgumentException("sortIndex should be greater or equal to 0.");
            }

            if (sortIndex >= MaxIndex)
            {
                throw new ArgumentException("sortIndex should be less than factorial(the lenght of items)");
            }

            for (int n = 0; n < _valueUsed.Length; n++)
            {
                _valueUsed[n] = false;
            }

            long factorielLower = MaxIndex;

            for (int index = 0; index < size; index++)
            {
                long factorielBigger = factorielLower;
                factorielLower = Factorial.GetFactorial(size - index - 1);  //  factorielBigger / inverseIndex;

                int resultItemIndex = (int)(sortIndex % factorielBigger / factorielLower);

                int correctedResultItemIndex = 0;
                for(;;)
                {
                    if (! _valueUsed[correctedResultItemIndex])
                    {
                        resultItemIndex--;
                        if (resultItemIndex < 0)
                        {
                            break;
                        }
                    }
                    correctedResultItemIndex++;
                }

                Result[index] = _sortedValues[correctedResultItemIndex];
                _valueUsed[correctedResultItemIndex] = true;
            }
        }

        // ************************************************************************
        /// <summary>
        /// Calc the index, relative to _sortedValues, of the permutation received
        /// as argument. Returned index is 0 based.
        /// </summary>
        /// <param name="values"></param>
        /// <returns></returns>
        public long GetIndexOfValues(T[] values)
        {
            int size = _sortedValues.Length;
            long valuesIndex = 0;

            List<T> valuesLeft = new List<T>(_sortedValues);

            for (int index = 0; index < size; index++)
            {
                long indexFactorial = Factorial.GetFactorial(size - 1 - index);

                T value = values[index];
                int indexCorrected = valuesLeft.IndexOf(value);
                valuesIndex = valuesIndex + (indexCorrected * indexFactorial);
                valuesLeft.Remove(value);
            }
            return valuesIndex;
        }

        // ************************************************************************
    }
}