如何生成 a[i] != i 的排列?

发布于 2024-12-19 12:06:15 字数 210 浏览 0 评论 0原文

假设我有一个整数数组 int a[] = {0, 1, ... N-1},其中 Na< 的大小/代码>。现在我需要为所有 0 <= i 生成 a 的所有排列 a[i] != i 。 N。你会怎么做?

Suppose I have an array of integers int a[] = {0, 1, ... N-1}, where N is the size of a. Now I need to generate all permutations of a s that a[i] != i for all 0 <= i < N. How would you do that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(6

迷乱花海 2024-12-26 12:06:15

下面是一些 C++ 实现的算法,该算法基于递归的双射证明,

!n = (n-1) * (!(n-1) + !(n-2)),

其中 !nn 项的混乱数量。

#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);这留给读者作为练习。

Here's some C++ implementing an algorithm based on a bijective proof of the recurrence

!n = (n-1) * (!(n-1) + !(n-2)),

where !n is the number of derangements of n items.

#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";
}

On my machine, I get

176214841 permutations in 13741305 clocks for derange()
176214841 permutations in 14106430 clocks for next_permutation()

which IMHO is a wash. Probably there are improvements to be made on both sides (e.g., reimplement next_permutation with the derangement test that scans only the elements that changed); that's left as an exercise to the reader.

王权女流氓 2024-12-26 12:06:15

如果您有权访问 C++ STL,请使用 next_permutation,并对[i] != i 在 do-while 循环中。

If you have access to C++ STL, use next_permutation, and do an additional check of a[i] != i in a do-while loop.

一指流沙 2024-12-26 12:06:15

如果您想避免其他人建议的过滤方法(按字典顺序生成排列并跳过具有固定点的排列),那么您应该基于循环符号而不是单行符号(符号讨论)。

n 排列的循环类型是 n 的划分,即总和为 n 的正整数的弱递减序列。排列没有不动点的条件等价于其循环类型没有1。例如,如果 n=5,则可能的循环类型为

5
4,1
3,2
3,1,1
2,2,1
2,1,1,1
1,1,1,1,1

其中,只有 53,2 对于此问题有效因为所有其他都包含 1。因此,策略是生成最小部分至少为 2 的分区,然后对于每个这样的分区,生成具有该循环类型的所有排列。

If you want to avoid the filter approach that others have suggested (generate the permutations in lexicographic order and skip those with fixed points), then you should generate them based on cycle notation rather than one-line notation (discussion of notation).

The cycle-type of a permutation of n is a partition of n, that is a weakly decreasing sequence of positive integers that sums to n. The condition that a permutation has no fixed points is equivalent to its cycle-type having no 1s. For example, if n=5, then the possible cycle-types are

5
4,1
3,2
3,1,1
2,2,1
2,1,1,1
1,1,1,1,1

Of those, only 5 and 3,2 are valid for this problem since all others contain a 1. Therefore the strategy is to generate partitions with smallest part at least 2, then for each such partition, generate all permutations with that cycle-type.

べ繥欢鉨o。 2024-12-26 12:06:15

您正在寻找的排列称为混乱。正如其他人所观察到的,均匀随机分布的排列可以通过生成均匀随机分布的排列然后拒绝具有固定点(其中 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;
  }

有关替代方法,请参阅我的帖子 随机播放列表,确保没有任何项目保持在同一位置

The permutations you are looking for are called derangements. As others have observed, uniformly randomly distributed derangements can be generated by generating uniformly randomly distributed permutations and then rejecting permutations that have fixed points (where a[i] == i). The rejection method runs in time e*n + o(n) where e is Euler's constant 2.71828... . An alternative algorithm similar to @Per's runs in time 2*n + O(log^2 n). However, the fastest algorithm I've been able to find, an early rejection algorithm, runs in time (e-1)*(n-1). Instead of waiting for the permutation to be generated and then rejecting it (or not), the permutation is tested for fixed points while it is being constructed, allowing for rejection at the earliest possible moment. Here's my implementation of the early rejection method for derangements in 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;
  }

For an alternative approach, see my post at Shuffle list, ensuring that no item remains in same position.

無心 2024-12-26 12:06:15

只是一个预感:我认为词典排列可能可以通过修改来解决这个问题。

通过将奇数和偶数元素对交换为 2,1,4,3,6,5 来重新排列数组 1,2,3,4,5,6,... ,... 以最低字典顺序构造排列。然后使用标准算法,并附加一个约束,即您不能将元素 i 交换到位置 i

如果数组有奇数个元素,则必须在末尾再次进行交换,以确保元素 N-1 不在位置 N-1 中。

Just a hunch: I think lexicographic permutation might be possible to modify to solve this.

Re-arrange the array 1,2,3,4,5,6,... by swapping pairs of odd and even elements into 2,1,4,3,6,5,... to construct the permutation with lowest lexicographic order. Then use the standard algorithm, with the additional constraint that you cannot swap element i into position i.

If the array has an odd number of elements, you will have to make another swap at the end to ensure that element N-1 is not in position N-1.

ζ澈沫 2024-12-26 12:06:15

这是 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]

Here's a small recursive approach in 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

Running perm(range(1,5)) will give the following output:

[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]
~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文