一题讲懂C语言解决全排列问题的一般方法

Posted tianxiao719

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一题讲懂C语言解决全排列问题的一般方法相关的知识,希望对你有一定的参考价值。

题目

5位运动员参加了10米台跳水比赛,有人让他们预测比赛结果:
A选手说:B第二,我第三;
B选手说:我第二,E第四;
C选手说:我第一,D第二;
D选手说:C最后,我第三;
E选手说:我第四,A第一;
比赛结束后,每位选手都说对了一半,请编程确定比赛的名次。

思路

1.设数字12345表示名次12345,创建数组a[5],a[0]到a[4]依次存放ABCDE的名次,把12345按一定顺序存到数组里,最终数组的结果就是它们的名次,比如12345就是A=1 B=2 C=3 D=4 E=5
2.给出12345在数组中的全排列。
3.根据题意设置筛选条件,符合条件的排列就是结果。
核心问题一个是怎样用代码实现遍历全排列,另一个是怎样把题干翻译成判断条件

实现全排列

可以使用递归的思想把求n个数的全排列变成求n个数分别固定住以后剩下n-1个数的全排列
(循环法也可,但是代码量庞大,可修改性差,在此重点介绍递归法)

比如本题,我们想实现12345全排列
第一步:把1固定住 求剩下数的全排列
第二步:把2固定住 求剩下数的全排列

第五步:把5固定住 求剩下数的全排列

然后每一步求剩下数的全排列都采取这种先固定单个元素再求子排列的方法,最终即可求解。

程序实现的时候我们要注意逻辑和上文稍有不同
在这里插入图片描述在这里插入图片描述
由于我们使用数组而不是链表存储数据(就是说元素中间不能突然空出来),所以不能直接实现“把2固定住,求1345的全排列”,所以我们把2和数组首元素先交换位置,让剩下的数组成数组去继续实现全排列;同理,把3提出来的时候也是把3和数组首元素先交换位置,来构造子数组;4、5同理

对于把2固定住求1345的全排列这一步,求完了1345的全排列后要把2和首元素的顺序再交换回来,不然原来的数组顺序就变化了。可以理解为上一段加粗字体的步骤是我们自己添加上去的辅助步骤,就像你尺规作图求中位线的时候会画辅助线一样,最后是要把辅助线擦去的,而我们“擦去”程序执行痕迹的办法就是把程序逆着执行,本题中就是把2和首元素再交换回来。
在这里插入图片描述
那第一步就变成了求2345的全排列
2345的全排列也可以这样拆分:
第一步:把2拆分出来 求剩下数的全排列
第二步:把3拆分出来 求剩下数的全排列
第三步:把4拆分出来 求剩下数的全排列
第四步:把5拆分出来 求剩下数的全排列

那上面的第一步就变成了求345的全排列
345的全排列可以这样拆分:
第一步:把3拆分出来 求剩下数的全排列
第二步:把4拆分出来 求剩下数的全排列
第三步:把5拆分出来 求剩下数的全排列
在这里插入图片描述
依次类推,便可以得到所有数的全排列

所以递归函数的步骤就是:
1.把一个数和首元素交换
2.实现剩下数的全排列(调用递归函数)
3.把这个数和首元素交换回来

写成函数:
(exchange函数是内容交换函数 perm函数是求全排列的函数 建议结合文末完整代码理解)

exchange(&str[j], &str[start]);//依次把每个元素放到第一
perm(str, start + 1, str_size);//让剩下的元素全排列
exchange(&str[j], &str[start]);//再把第一的元素还原回去 还原数组顺序

设置判断条件

我们来看一下题目的判断条件:
“A选手说:B第二,我第三;
B选手说:我第二,E第四;
C选手说:我第一,D第二;
D选手说:C最后,我第三;
E选手说:我第四,A第一;
比赛结束后,每位选手都说对了一半”

由于每句话只有一半是对的,也就是说可能是前半句真后半句假或者前半句假后半句真
所以A的话可以解读为:(B第二我不是第三)(B不是第二我是第三)
B的话可以解读为:(我第二E不是第四)(我不是第二E是第四)
同理可解读CDE的话

写成 if 判断:

if (((str[1] == 2 && str[0] != 3) || (str[1] != 2 && str[0] == 3))
 && ((str[1] == 2 && str[4] != 4) || (str[1] != 2 && str[4] == 4))
 && ((str[2] == 1 && str[3] != 2) || (str[2] != 1 && str[3] == 2))
 && ((str[2] == 5 && str[3] != 3) || (str[2] != 5 && str[3] == 3))
 && ((str[4] == 4 && str[0] != 1) || (str[4] != 4 && str[0] == 1)))

代码

(C代码涉及了一些指针数组的知识,如果看的有困难建议先打好基础再看)
强烈建议对照代码回头看前文的方法

#include <stdio.h>

void exchange(int* a, int* b)//exchange函数 交换两个变量的值
{
    int c = *a;
    *a = *b;
    *b = c;
}

void perm(int str[], int start, int str_size)//str传入全排列的对象 start全排列的起点 str_size是元素个数
{
    int i = 0, j = 0;

    if (start == str_size)//start == str_size 意味着 上一轮中的start = str_size - 1 也就是说遍历到头了 跳出递归
    {
        /* 输出当前的排列 */
        if (((str[1] == 2 && str[0] != 3) || (str[1] != 2 && str[0] == 3))
            && ((str[1] == 2 && str[4] != 4) || (str[1] != 2 && str[4] == 4))
            && ((str[2] == 1 && str[3] != 2) || (str[2] != 1 && str[3] == 2))
            && ((str[2] == 5 && str[3] != 3) || (str[2] != 5 && str[3] == 3))
            && ((str[4] == 4 && str[0] != 1) || (str[4] != 4 && str[0] == 1)))
        printf("A=%d B=%d C=%d D=%d E=%d\\n", str[0], str[1], str[2], str[3], str[4]);
    }
    else
    {
        for (j = start; j < str_size; j++)
        {
            exchange(&str[j], &str[start]);//依次把每个元素放到第一
            perm(str, start + 1, str_size);//让剩下的元素全排列
            exchange(&str[j], &str[start]);//再把第一的元素还原回去 还原数组顺序
        }
    }
    return;
}
int main()
{
    int a[5] = { 1,2,3,4,5 };
    perm(a, 0, 5);
    return 0;
}

运行截图:
在这里插入图片描述
所以最终名次就是12345 = BDAEC

总结

解题三部曲:
1.遍历所有全排列
2.根据题意设置筛选条件
3.输出符合题意的排列

ps:本文的思路可以解决一批全排列问题,希望小伙伴们看懂原理,举一反三

以上是关于一题讲懂C语言解决全排列问题的一般方法的主要内容,如果未能解决你的问题,请参考以下文章

C语言数字全排列的问题(急!!)求C代码和算法

求n个数的全排列,n不定。用c语言。用于银行家算法中求安全序列

c语言全排列

C语言的全排列问题!急!

求教C语言回溯法写出八皇后问题的92种解

《LeetCode之每日一题》:191.全排列