Как бы вы вычислили все возможные перестановки от 0 до N итеративно? - PullRequest
30 голосов
/ 06 марта 2010

Мне нужно вычислять перестановки итеративно.Подпись метода выглядит следующим образом:

int[][] permute(int n)

Например, для n = 3 возвращаемое значение будет:

[[0,1,2],
 [0,2,1],
 [1,0,2],
 [1,2,0],
 [2,0,1],
 [2,1,0]]

Как бы вы поступили такитеративно наиболее эффективным способом?Я могу сделать это рекурсивно, но мне интересно увидеть множество альтернативных способов сделать это итеративно.

Ответы [ 10 ]

23 голосов
/ 06 марта 2010

см. Алгоритм QuickPerm, он итеративный: http://www.quickperm.org/

Edit:

Переписано на Ruby для ясности:

def permute_map(n)
  results = []
  a, p = (0...n).to_a, [0] * n
  i, j = 0, 0
  i = 1
  results << yield(a)
  while i < n
    if p[i] < i
      j = i % 2 * p[i] # If i is odd, then j = p[i], else j = 0
      a[j], a[i] = a[i], a[j] # Swap
      results << yield(a)
      p[i] += 1
      i = 1
    else
      p[i] = 0
      i += 1
    end
  end
  return results
end
6 голосов
/ 06 марта 2010

Алгоритм перехода от одной перестановки к другой очень похож на добавление в начальную школу - когда происходит переполнение, «нести одну».

Вот реализация, которую я написал на C:

#include <stdio.h>

//Convenience macro.  Its function should be obvious.
#define swap(a,b) do { \
        typeof(a) __tmp = (a); \
        (a) = (b); \
        (b) = __tmp; \
    } while(0)

void perm_start(unsigned int n[], unsigned int count) {
    unsigned int i;
    for (i=0; i<count; i++)
        n[i] = i;
}

//Returns 0 on wraparound
int perm_next(unsigned int n[], unsigned int count) {
    unsigned int tail, i, j;

    if (count <= 1)
        return 0;

    /* Find all terms at the end that are in reverse order.
       Example: 0 3 (5 4 2 1) (i becomes 2) */
    for (i=count-1; i>0 && n[i-1] >= n[i]; i--);
    tail = i;

    if (tail > 0) {
        /* Find the last item from the tail set greater than
            the last item from the head set, and swap them.
            Example: 0 3* (5 4* 2 1)
            Becomes: 0 4* (5 3* 2 1) */
        for (j=count-1; j>tail && n[j] <= n[tail-1]; j--);

        swap(n[tail-1], n[j]);
    }

    /* Reverse the tail set's order */
    for (i=tail, j=count-1; i<j; i++, j--)
        swap(n[i], n[j]);

    /* If the entire list was in reverse order, tail will be zero. */
    return (tail != 0);
}

int main(void)
{
    #define N 3
    unsigned int perm[N];

    perm_start(perm, N);
    do {
        int i;
        for (i = 0; i < N; i++)
            printf("%d ", perm[i]);
        printf("\n");
    } while (perm_next(perm, N));

    return 0;
}
5 голосов
/ 06 марта 2010

Можно ли использовать перестановку Array # 1.9?

>> a = [0,1,2].permutation(3).to_a
=> [[0, 1, 2], [0, 2, 1], [1, 0, 2], [1, 2, 0], [2, 0, 1], [2, 1, 0]]
4 голосов
/ 07 октября 2012

Ниже приведена моя обобщенная версия следующего алгоритма перестановки в C #, очень похожая на функцию next_permutation STL (но она не переворачивает коллекцию, если это уже максимально возможная перестановка, как в версии C ++)

Теоретически он должен работать с любым IList <> из IComparables.

    static bool NextPermutation<T>(IList<T> a) where T: IComparable
    {
        if (a.Count < 2) return false;
        var k = a.Count-2;

        while (k >= 0 && a[k].CompareTo( a[k+1]) >=0) k--;
        if(k<0)return false;

        var l = a.Count - 1;
        while (l > k && a[l].CompareTo(a[k]) <= 0) l--;

        var tmp = a[k];
        a[k] = a[l];
        a[l] = tmp;

        var i = k + 1;
        var j = a.Count - 1;
        while(i<j)
        {
            tmp = a[i];
            a[i] = a[j];
            a[j] = tmp;
            i++;
            j--;
        }

        return true;
    }

И демонстрационный / тестовый код:

        var src = "1234".ToCharArray();
        do
        {
            Console.WriteLine(src);
        } 
        while (NextPermutation(src));
2 голосов
/ 28 октября 2014

Я также натолкнулся на алгоритм QuickPerm, на который ссылается другой ответ.Я хотел бы поделиться этим ответом дополнительно, потому что я увидел некоторые немедленные изменения, которые можно сделать, чтобы написать его короче.Например, если индексный массив «p» инициализируется немного по-другому, это избавляет от необходимости возвращать первую перестановку перед циклом.Кроме того, все эти циклы while и if заняли гораздо больше места.

void permute(char* s, size_t l) {
    int* p = new int[l];
    for (int i = 0; i < l; i++) p[i] = i;
    for (size_t i = 0; i < l; printf("%s\n", s)) {
        std::swap(s[i], s[i % 2 * --p[i]]);
        for (i = 1; p[i] == 0; i++) p[i] = i;
    }
}
2 голосов
/ 09 мая 2013

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

/// <summary>
/// Performs an in-place permutation of <paramref name="values"/>, and returns if there 
/// are any more permutations remaining.
/// </summary>
private static bool NextPermutation(int[] values)
{
    if (values.Length == 0)
        throw new ArgumentException("Cannot permutate an empty collection.");

    //Find all terms at the end that are in reverse order.
    //  Example: 0 3 (5 4 2 1) (i becomes 2)
    int tail = values.Length - 1;
    while(tail > 0 && values[tail - 1] >= values[tail])
        tail--;

    if (tail > 0)
    {
        //Find the last item from the tail set greater than the last item from the head 
        //set, and swap them.
        //  Example: 0 3* (5 4* 2 1)
        //  Becomes: 0 4* (5 3* 2 1)
        int index = values.Length - 1;
        while (index > tail && values[index] <= values[tail - 1])
            index--;

        Swap(ref values[tail - 1], ref values[index]);
    }

    //Reverse the tail set's order.
    int limit = (values.Length - tail) / 2;
    for (int index = 0; index < limit; index++)
        Swap(ref values[tail + index], ref values[values.Length - 1 - index]);

    //If the entire list was in reverse order, tail will be zero.
    return (tail != 0);
}

private static void Swap<T>(ref T left, ref T right)
{
    T temp = left;
    left = right;
    right = temp;
}
2 голосов
/ 05 декабря 2011

Вот реализация на C # в качестве метода расширения:

public static IEnumerable<List<T>> Permute<T>(this IList<T> items)
{
    var indexes = Enumerable.Range(0, items.Count).ToArray();

    yield return indexes.Select(idx => items[idx]).ToList();

    var weights = new int[items.Count];
    var idxUpper = 1;
    while (idxUpper < items.Count)
    {
        if (weights[idxUpper] < idxUpper)
        {
            var idxLower = idxUpper % 2 * weights[idxUpper];
            var tmp = indexes[idxLower];
            indexes[idxLower] = indexes[idxUpper];
            indexes[idxUpper] = tmp;
            yield return indexes.Select(idx => items[idx]).ToList();
            weights[idxUpper]++;
            idxUpper = 1;
        }
        else
        {
            weights[idxUpper] = 0;
            idxUpper++;
        }
    }
}

И юнит-тест:

[TestMethod]
public void Permute()
{
    var ints = new[] { 1, 2, 3 };
    var orderings = ints.Permute().ToList();
    Assert.AreEqual(6, orderings.Count);
    AssertUtil.SequencesAreEqual(new[] { 1, 2, 3 }, orderings[0]);
    AssertUtil.SequencesAreEqual(new[] { 2, 1, 3 }, orderings[1]);
    AssertUtil.SequencesAreEqual(new[] { 3, 1, 2 }, orderings[2]);
    AssertUtil.SequencesAreEqual(new[] { 1, 3, 2 }, orderings[3]);
    AssertUtil.SequencesAreEqual(new[] { 2, 3, 1 }, orderings[4]);
    AssertUtil.SequencesAreEqual(new[] { 3, 2, 1 }, orderings[5]);
}

Метод AssertUtil.SequencesAreEqual - это пользовательский помощник по тестированию, который можно воссоздать достаточно легко.

1 голос
/ 19 мая 2015

Я реализовал алгоритм в Javascript.

var all = ["a", "b", "c"];
console.log(permute(all));

function permute(a){
  var i=1,j, temp = "";
  var p = [];
  var n = a.length;
  var output = [];

  output.push(a.slice());
  for(var b=0; b <= n; b++){
    p[b] = b;
  }

  while (i < n){
    p[i]--;
    if(i%2 == 1){
      j = p[i];
    }
    else{
      j = 0;
    }
    temp = a[j];
    a[j] = a[i];
    a[i] = temp;

    i=1;
    while (p[i] === 0){
      p[i] = i;
      i++;
    }
    output.push(a.slice());
  }
  return output;
}
1 голос
/ 25 мая 2013

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

Подобно тому, как перестановка представляет собой сложение «несущий-один», возвращающее хвост (а не возвращающееся к 0), индексация определенного значения перестановки находит цифры числа в базе n, а затем n-1n-2 ... через каждую итерацию.

public static <T> boolean permutation(List<T> values, int index) {
    return permutation(values, values.size() - 1, index);
}
private static <T> boolean permutation(List<T> values, int n, int index) {
    if ((index == 0) || (n == 0))  return (index == 0);
    Collections.swap(values, n, n-(index % n));
    return permutation(values,n-1,index/n);
}

Логическое значение возвращает ли значение индекса вне пределов.А именно, в нем заканчивалось n значений, но оставался оставшийся индекс.

И он не может получить все перестановки для более чем 12 объектов.12!

- Но это очень, очень красиво.И если вы делаете много плохого, это может быть полезно.

0 голосов
/ 06 марта 2010

Я использовал алгоритмы здесь .Страница содержит много полезной информации.

Редактировать : Извините, это было рекурсивно.В своем ответе Юрай разместил ссылку на итерационный алгоритм.

Я создал пример PHP.Если вам не нужно возвращать все результаты, я бы создал только итеративный класс, подобный следующему:

<?php
class Permutator implements Iterator
{
  private $a, $n, $p, $i, $j, $k;
  private $stop;

  public function __construct(array $a)
  {
    $this->a = array_values($a);
    $this->n = count($this->a);
  }

  public function current()
  {
    return $this->a;
  }

  public function next()
  {
    ++$this->k;
    while ($this->i < $this->n)
    {
      if ($this->p[$this->i] < $this->i)
      {
        $this->j = ($this->i % 2) * $this->p[$this->i];

        $tmp = $this->a[$this->j];
        $this->a[$this->j] = $this->a[$this->i];
        $this->a[$this->i] = $tmp;

        $this->p[$this->i]++;
        $this->i = 1;
        return;
      }

      $this->p[$this->i++] = 0;
    }

    $this->stop = true;
  }

  public function key()
  {
    return $this->k;
  }

  public function valid()
  {
    return !$this->stop;
  }

  public function rewind()
  {
    if ($this->n) $this->p = array_fill(0, $this->n, 0);
    $this->stop = $this->n == 0;
    $this->i = 1;
    $this->j = 0;
    $this->k = 0;
  }

}

foreach (new Permutator(array(1,2,3,4,5)) as $permutation)
{
  var_dump($permutation);
}
?>

Обратите внимание, что он обрабатывает каждый массив PHP как индексированный массив.

...