C/C++:全排列问题

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/test1280/article/details/72083365

C/C++:全排列问题

啥叫全排列?

从n个不同元素中任取m(m≤n)个元素,按照一定的顺序排列起来,叫做从n个不同元素中取出m个元素的一个排列。

当m=n时所有的排列情况叫全排列。

假设现在有三个数字:0 1 2,将其全排列结果为:

0 1 2
0 2 1
1 0 2
1 2 0
2 0 1
2 1 0

我们有几种方式可以实现全排列。

最简单,最暴力的是枚举。

【例1】

#include <stdio.h>
#include <stdlib.h>

void Permutations()
{
    int i, j, k;
    for (i = 0; i < 3; i++)
        for (j = 0; j < 3; j++)
            for (k = 0; k < 3; k++)
            {
                if (i != j && i != k && j != k)
                    printf("%d %d %d\n", i, j, k);
            }
}

int main()
{
    Permutations();
    return 0;
}

其实我们可以优化一点:

【例2】

#include <stdio.h>
#include <stdlib.h>

void Permutations()
{
    int i, j, k;
    for (i = 0; i < 3; i++)
    {
        for (j = 0; j < 3; j++)
        {
            if (i == j)
            {
                continue;
            }
            for (k = 0; k < 3; k++)
            {
                if (i == k || j == k)
                {
                    continue;
                }
                printf("%d %d %d\n", i, j, k);
            }
        }
    }
}

int main()
{
    Permutations();
    return 0;
}

哈哈,只是换汤不换药,但是在嵌套循环很多的时候性能会有很大的提高,毕竟可以提前发现,少做很多的无用功。

另外一种可以使用mark标记数组,当需要全排列的数字很多时比较好用。

假设现在需要对0 1 2 3进行全排列:

【例3】

#include <stdio.h>
#include <stdlib.h>

int Permutations()
{
    int total = 0;
    int a[4];
    for (a[0] = 0; a[0] < 4; a[0]++)
        for (a[1] = 0; a[1] < 4; a[1]++)
            for (a[2] = 0; a[2] < 4; a[2]++)
                for (a[3] = 0; a[3] < 4; a[3]++)
                {
                    int mark[4] = {0, 0, 0, 0};
                    int i;
                    for (i = 0; i < 4; i++)
                    {
                        mark[a[i]]++;
                    }
                    int sum = 0;
                    for (i = 0; i < 4; i++)
                    {
                        if (mark[i])
                        {
                            sum++;
                        }
                    }
                    if (sum == 4)
                    {
                        printf("%d %d %d %d\n", a[0], a[1], a[2], a[3]);
                        total ++;
                    }
                }
    return total;
}

int main()
{
    int result = Permutations();
    printf("result is %d\n", result);
    return 0;
}
[test1280@localhost ~]$ !g
gcc -o main main.c
[test1280@localhost ~]$ ./main
0 1 2 3
0 1 3 2
0 2 1 3
0 2 3 1
0 3 1 2
……

上述方法其实也是枚举,虽然能在数字多时看起来简洁,但是个人认为,性能上还不如第二种方法,毕竟第二种可以减少很多不必要的循环,可以预先检查。

总结上面三点,都是枚举,都很暴力。

有没有更好一点的方法?

我们可以使用DFS来对全排列进行计算。

【例4】

#include <stdio.h>
#include <stdlib.h>

/*****************************************************
 * 函数名称:DFS
 * 参数列表:mark-标记数组;step-当前为第几步;all-一共有多少步;path-踪迹数组
 * 函数描述:深度优先,遍历step步时可能的情况总数
 * 返回值  :step步可能出现的情况总数
 * 备注    :
 * Author  :test1280
 * History :2017/05/14
 * ***************************************************/
int DFS(int *mark, int step, int all, int *path)
{
    if (step == all)
    {
        int i;
        for (i = 0; i < all; i++)
        {
            printf("%d ", path[i]);
        }
        printf("\n");
        return 1;
    }
    int i;
    int total = 0;
    for (i = 0; i <= all; i++)
    {
        if (mark[i])
        {
            continue;
        }
        mark[i] = 1;
        path[step] = i;
        total = total + DFS(mark, step + 1, all, path);
        mark[i] = 0;
    }
    return total;
}

/*******************************************************
 * 函数名称:Permutations
 * 参数列表:all-共有all个数进行全排列
 * 函数描述:全排列all个数字,从0到all-1
 * 返回值  :全排列all个数字的情况总数
 * 备注    :
 * Author  :test1280
 * History :2017/05/14
 * ***************************************************/
int Permutations(int all)
{
    int *mark = (int *)malloc(sizeof(int) * all);
    int *path = (int *)malloc(sizeof(int) * all);
    int total = 0;
    int i;
    for (i = 0; i < all; i++)
    {
        mark[i] = 0;
    }
    for (i = 0; i < all; i++)
    {
        mark[i] = 1;
        // now-step is 0
        path[0] = i;
        total = total + DFS(mark, 1, all, path);
        mark[i] = 0;
    }
    return total;
}

int main()
{
    int all = 10;
    int result = Permutations(all);
    printf("%d\n", result);
    return 0;
}

输出可能会有一会~

我们将那些输出都干掉,只是求结果总数:

【例5】

#include <stdio.h>
#include <stdlib.h>

/*****************************************************
 * 函数名称:DFS
 * 参数列表:mark-标记数组;step-当前为第几步;all-一共有多少步;
 * 函数描述:深度优先,遍历step步时可能的情况总数
 * 返回值  :step步可能出现的情况总数
 * 备注    :
 * Author  :test1280
 * History :2017/05/14
 * ***************************************************/
int DFS(int *mark, int step, int all)
{
    if (step == all)
    {
        return 1;
    }
    int i;
    int total = 0;
    for (i = 0; i <= all; i++)
    {
        if (mark[i])
        {
            continue;
        }
        mark[i] = 1;
        total = total + DFS(mark, step + 1, all);
        mark[i] = 0;
    }
    return total;
}

/*******************************************************
 * 函数名称:Permutations
 * 参数列表:all-共有all个数进行全排列
 * 函数描述:全排列all个数字,从0到all-1
 * 返回值  :全排列all个数字的情况总数
 * 备注    :
 * Author  :test1280
 * History :2017/05/14
 * ***************************************************/
int Permutations(int all)
{
    int *mark = (int *)malloc(sizeof(int) * all);
    int total = 0;
    int i;
    for (i = 0; i < all; i++)
    {
        mark[i] = 0;
    }
    for (i = 0; i < all; i++)
    {
        mark[i] = 1;
        // now-step is 0
        total = total + DFS(mark, 1, all);
        mark[i] = 0;
    }
    return total;
}

int main()
{
    int all = 10;
    int result = Permutations(all);
    printf("%d\n", result);
    return 0;
}

输出如下:

[test1280@localhost ~]$ !g
gcc -o main main.c
[test1280@localhost ~]$ ./main 
3628800

注意一点,DFS在这里面,总是递归调用,从全局程序运行生长来看,他做的也是对all个数,用了all次循环,区别在于,其将【例2】中的预先判断做了极大的发挥,使其结合mark标记数组,再加上递归的特性,使得性能大大增加。

大家不妨将【例3】改为10个数字的全排列,只输出结果,然后和【例4】进行性能比较。

有没有别的方法呢?

阅读更多
换一批

没有更多推荐了,返回首页