假设我有一个整数数组int a[] = {0, 1, ... N-1}
,其中N
的大小为a
。现在,我需要为所有a
生成a[i] != i
0 <= i < N
的所有排列。你会怎么做?
答案 0 :(得分:8)
这里有一些C ++实现了一种基于复发的双射证明的算法
!n = (n-1) * (!(n-1) + !(n-2)),
其中!n
是n
项目的紊乱数量。
#include <algorithm>
#include <ctime>
#include <iostream>
#include <vector>
static const int N = 12;
static int count;
template<class RAI>
void derange(RAI p, RAI a, RAI b, int n) {
if (n < 2) {
if (n == 0) {
for (int i = 0; i < N; ++i) p[b[i]] = a[i];
if (false) {
for (int i = 0; i < N; ++i) std::cout << ' ' << p[i];
std::cout << '\n';
} else {
++count;
}
}
return;
}
for (int i = 0; i < n - 1; ++i) {
std::swap(a[i], a[n - 1]);
derange(p, a, b, n - 1);
std::swap(a[i], a[n - 1]);
int j = b[i];
b[i] = b[n - 2];
b[n - 2] = b[n - 1];
b[n - 1] = j;
std::swap(a[i], a[n - 2]);
derange(p, a, b, n - 2);
std::swap(a[i], a[n - 2]);
j = b[n - 1];
b[n - 1] = b[n - 2];
b[n - 2] = b[i];
b[i] = j;
}
}
int main() {
std::vector<int> p(N);
clock_t begin = clock();
std::vector<int> a(N);
std::vector<int> b(N);
for (int i = 0; i < N; ++i) a[i] = b[i] = i;
derange(p.begin(), a.begin(), b.begin(), N);
std::cout << count << " permutations in " << clock() - begin << " clocks for derange()\n";
count = 0;
begin = clock();
for (int i = 0; i < N; ++i) p[i] = i;
while (std::next_permutation(p.begin(), p.end())) {
for (int i = 0; i < N; ++i) {
if (p[i] == i) goto bad;
}
++count;
bad:
;
}
std::cout << count << " permutations in " << clock() - begin << " clocks for next_permutation()\n";
}
在我的机器上,我得到了
176214841 permutations in 13741305 clocks for derange()
176214841 permutations in 14106430 clocks for next_permutation()
恕我直言,洗漱。可能双方都有改进(例如,重新实现next_permutation
,紊乱测试仅扫描改变的元素);这留给了读者一个练习。
答案 1 :(得分:5)
如果您有权访问C ++ STL,请使用next_permutation,并在do-while
循环中对[i]!= i进行额外检查。
答案 2 :(得分:5)
如果您想避免其他人建议的过滤方法(按字典顺序生成排列并跳过具有固定点的排列),那么您应该基于循环符号而不是单行符号生成它们({{3} })。
n
的排列的循环类型是n
的分区,它是弱整数的正整数序列,总和为n
。置换没有固定点的条件等同于没有1
s的循环类型。例如,如果n=5
,那么可能的循环类型是
5
4,1
3,2
3,1,1
2,2,1
2,1,1,1
1,1,1,1,1
其中,只有5
和3,2
对此问题有效,因为其他所有问题都包含1
。因此,策略是生成具有至少2
的最小部分的分区,然后对于每个这样的分区,生成具有该循环类型的所有排列。
答案 3 :(得分:2)
您正在寻找的排列称为紊乱。正如其他人所观察到的那样,可以通过生成均匀随机分布的排列然后拒绝具有固定点的排列(其中a [i] == i)来生成均匀随机分布的排列。拒绝方法在时间e * n + o(n)中运行,其中e是欧拉常数2.71828 ....类似于@ Per的替代算法在时间2 * n + O(log ^ 2 n)中运行。然而,我能够找到的最快算法早期拒绝算法,在时间(e-1)*(n-1)中运行。不是等待排列生成然后拒绝(或不排除)排列,而是在构造排列时测试固定点,从而允许在尽可能早的时刻进行排斥。这是我对Java中紊乱的早期拒绝方法的实现。
public static int[] randomDerangement(int n)
throws IllegalArgumentException {
if (n<2)
throw new IllegalArgumentException("argument must be >= 2 but was " + n);
int[] result = new int[n];
boolean found = false;
while (!found) {
for (int i=0; i<n; i++) result[i] = i;
boolean fixed = false;
for (int i=n-1; i>=0; i--) {
int j = rand.nextInt(i+1);
if (i == result[j]) {
fixed = true;
break;
}
else {
int temp = result[i];
result[i] = result[j];
result[j] = temp;
}
}
if (!fixed) found = true;
}
return result;
}
有关替代方法,请参阅Shuffle list, ensuring that no item remains in same position上的帖子。
答案 4 :(得分:1)
只是预感:我认为可以修改lexicographic permutation来解决这个问题。
通过将奇数和偶数元素对交换成1,2,3,4,5,6,...
来重新排列数组2,1,4,3,6,5,...
,以构造具有最低字典顺序的排列。然后使用标准算法,使用附加约束,您不能将元素i
交换到位置i
。
如果数组具有奇数个元素,则必须在末尾进行另一次交换,以确保元素N-1
不在N-1
位置。
答案 5 :(得分:1)
这是python中的一个小递归方法:
def perm(array,permutation = [], i = 1):
if len(array) > 0 :
for element in array:
if element != i:
newarray = list(array)
newarray.remove(element)
newpermutation = list(permutation)
newpermutation.append(element)
perm(newarray,newpermutation,i+1)
else:
print permutation
运行perm(range(1,5))
将提供以下输出:
[2, 1, 4, 3]
[2, 3, 4, 1]
[2, 4, 1, 3]
[3, 1, 4, 2]
[3, 4, 1, 2]
[3, 4, 2, 1]
[4, 1, 2, 3]
[4, 3, 1, 2]
[4, 3, 2, 1]