您将如何迭代地计算 0 到 N 的所有可能排列?
Posted
技术标签:
【中文标题】您将如何迭代地计算 0 到 N 的所有可能排列?【英文标题】:How would you calculate all possible permutations of 0 through N iteratively? 【发布时间】:2011-01-24 08:37:53 【问题描述】:我需要迭代地计算排列。方法签名如下:
int[][] permute(int n)
以n = 3
为例,返回值为:
[[0,1,2],
[0,2,1],
[1,0,2],
[1,2,0],
[2,0,1],
[2,1,0]]
您将如何以最有效的方式迭代地执行此操作?我可以递归地执行此操作,但我有兴趣看到许多替代方法来迭代地执行此操作。
【问题讨论】:
正如我在回答中提到的(在我按照 uray 的建议编辑使用 QuickPerm 算法之后),最有效的方法是实时迭代排列。建立一个完整的列表可能不是很有用,因为您可以只处理当前的迭代。 对,这就是我添加到 uray 的答案中的 Ruby 代码使用 yield 和 blocks 的原因。它在计算下一个排列之前将每个排列传递给提供的代码块。 查看这个问题和答案:***.com/questions/352203/… @Bob,我发布的 C# 版本使用相同的方法来生成可用的结果。希望它可以帮助某人。 【参考方案1】:我使用了来自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 数组都视为索引数组。
【讨论】:
【参考方案2】:我已经用 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;
【讨论】:
【参考方案3】:这是 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
方法是一个自定义测试助手,可以很容易地重新创建。
【讨论】:
【参考方案4】:我发现 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;
【讨论】:
【参考方案5】:下面是我在 C# 中的下一个排列算法的泛型版本,与 STL 的 next_permutation
函数非常相似(但如果它已经是最大可能排列,它不会反转集合,就像 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));
【讨论】:
【参考方案6】:从一个排列到下一个排列的算法与小学加法非常相似 - 当发生溢出时,“carry the one”。
这是我用 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;
【讨论】:
【参考方案7】:我还遇到了另一个答案中引用的 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;
【讨论】:
不错。我不得不将最后一个for
中的停止条件更改为i < l && p[i] == 0
。【参考方案8】:
参见 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
【讨论】:
我偷偷溜进去,附上了这个算法的 Ruby 实现,供我个人参考。会把它放在 cmets 中,但你不能在那里突出显示语法。 顺便说一下,当前版本的 Ruby 内置了这个:(0...n).to_a.permutation |a| puts a.inspect
这个的时间复杂度是多少?【参考方案9】:
可以迭代调用的递归算法怎么样?如果你真的需要那些东西作为这样的列表(你应该清楚地内联而不是分配一堆毫无意义的内存)。您可以简单地通过其索引即时计算排列。
就像排列是进位加法重新反转尾部(而不是恢复为 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!
-- 但是,它非常非常漂亮。如果你做错了很多事情可能会有用。
【讨论】:
20! 如果事情多一点,可能会使用大数字类。【参考方案10】:是否使用 1.9 的 Array#permutation 选项?
>> 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]]
【讨论】:
不,算法本身就是我要找的。正是出于这个原因,我将其标记为与语言无关。以上是关于您将如何迭代地计算 0 到 N 的所有可能排列?的主要内容,如果未能解决你的问题,请参考以下文章