算法:全排列问题——n进位法

对于输出1 ~ n这些数组成的所有全排列的方法有最暴力的递归枚举法和相对简单写的递归交换法,但是有时我们只希望可以找到一个全排列的下一个全排列,就这样出现了n进位法。

例题

洛谷1706 全排列问题

题目描述
输出自然数1到n所有不重复的排列,即n的全排列,要求所产生的任一数字序列中不允许出现重复的数字。

输入格式
一个整数n。

输出格式
由1~n组成的所有不重复的数字序列,每行一个序列。
每个数字保留 5个场宽。

输入样例

3

输出样例

    1    2    3
    1    3    2
    2    1    3
    2    3    1
    3    1    2
    3    2    1

全排列问题——n进位法

n进位法实现起来十分简单,就是每次不停地给排列加1(这里是(n + 1)进制的加法,既逢n进1),直到1 ~ n中的所有数字都只出现一次,没有重复出现。

举个例子,假设我们要求213的下一个排列

  • 213加一,变成214,这里是四进制,所以进位处理得到221,有重复数字,所以继续处理下去。
  • 221加一,变成222,这里是四进制,所以不用进位,但有重复数字,所以继续处理下去。
  • 222加一,变成223,这里是四进制,所以不用进位,但有重复数字,所以继续处理下去。
  • 223加一,变成224,这里是四进制,所以进位处理得到231,没有重复数字,符合要求,输出。

这里记得加个特判:如果说第0位不是0了,既a[0] > 0了,代表当前排列之后没有排列,返回false。

最后,算一下算法时间复杂度:根据排列组合求出总共有n^n^个不同排列,每个排列枚举时还要套一层n次的循环,所以解决此题的总时间复杂度是O(n^(n + 1))级别的。

代码

# include <cstdio>
# include <cmath>
# include <cstring>
# include <algorithm>

using namespace std;

const int N_MAX = 10;

int n;
int a[N_MAX + 10];
bool flag[N_MAX + 10]; // flag[i]表示i这个数字是否在排列中出现

bool permutation()
{
    a[n]++;
    for (int i = n; i >= 1 && a[i] > n; i--)
        a[i] -= n, a[i - 1]++;
    if (a[0] > 0) return false;
    memset(flag, 0, sizeof(flag));
    for (int i = 1; i <= n; i++)
        flag[a[i]] = true;
    bool run_next = false;
    for (int i = 1; i <= n; i++)
        if (!flag[i]) {
            run_next = true;
            break;
        }
    if (!run_next) return true;
    return permutation();
}

int main()
{
    scanf("%d", &n);
    for (int i = 1; i <= n; i++)
        a[i] = i;
    do {
        for (int i = 1; i <= n; i++)
            printf("%5d", a[i]);
        printf("\n");
    } while (permutation());
    return 0;
}

猜你喜欢

转载自www.cnblogs.com/000zwx000/p/12450189.html