代码之家  ›  专栏  ›  技术社区  ›  Bob Aman

如何迭代计算0到n的所有可能排列?

  •  30
  • Bob Aman  · 技术社区  · 16 年前

    我需要迭代计算排列。方法签名如下:

    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 回复  |  直到 11 年前
        1
  •  23
  •   juanignaciosl    12 年前

    参见QuickPerm算法,它是迭代的: http://www.quickperm.org/

    编辑:

    为清晰起见,请用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
    
        2
  •  6
  •   Joey Adams    16 年前

    从一个排列到下一个排列的算法与小学加法非常相似——当溢出发生时,“携带一个”。

    下面是我用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;
    }
    
        3
  •  5
  •   Michael Kohl    16 年前

    使用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
  •  4
  •   Zar Shardan    13 年前

    下面是我的泛型版本中的下一个置换算法,它与C语言中的STL的NExtx置换函数非常类似(但是如果它是最大可能的排列,就像C++版本一样,它不会反向收集)。

    理论上,它应该与任何iComparables的iList<gt;一起工作。

        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));
    
        5
  •  2
  •   Drew Noakes    14 年前

    以下是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 是一个自定义测试助手,可以很容易地重新创建。

        6
  •  2
  •   Leon Bouquiet    13 年前

    我发现Joey Adams的版本是最易读的,但由于C处理for循环变量的作用域的方式,我无法将其直接移植到C。因此,这是一个稍微修改过的代码版本:

    /// <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;
    }
    
        7
  •  2
  •   user2880576    11 年前

    我还遇到了另一个答案中引用的快速排列算法。此外,我还想分享这个答案,因为我看到了一些可以立即做出的更改,可以将其缩短。例如,如果索引数组“p”的初始化方式稍有不同,则可以省去在循环之前返回第一个排列的麻烦。而且,所有这些循环和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;
        }
    }
    
        8
  •  1
  •   Tatarize    13 年前

    您可以迭代调用递归算法吗?如果你真的需要这些东西作为一个列表(你应该清楚地将其内联,而不是分配一堆无意义的内存)。你可以简单地通过它的索引计算动态排列。

    很像排列是带着一个加法重新颠倒尾部(而不是还原为0),索引特定的排列值是找到以n为基数的数字,然后是n-1,然后是n-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!<integer.max_值<13!

    --但是,它非常漂亮。如果你做了很多错事,可能会有用。

        9
  •  1
  •   Imran Rashid    11 年前

    我已经用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;
    }
    
        10
  •  0
  •   Matthew    16 年前

    我使用的算法来自 here . 这个页面包含很多有用的信息。

    编辑 :抱歉,这些是递归的。Uray在他的答案中发布了迭代算法的链接。

    我创建了一个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数组视为索引数组。

    推荐文章