《算法竞赛入门经典》-【第七章:暴力求解法】-7.2:枚举排列

一、问题:
给出包含n个数字(可能重复)的数组P,打印出其全排列
二、思路:
首先想的是能不能用数学的方法来解决这个问题,很遗憾的我们只记得可以算出全排列的个数,要把排列结果全部输出是不可能的。
那么再考虑一下
暴力求解(brute force)的方法,也就是最naive,最直接的办法:从P中
每次取出一个与已经取出的数字不重复的数字
这里需要保证不重复
,经过n次(这里已经保证了不会遗漏),就可以得到一个排列结果(这里需要保证排列结果不重复不遗漏)。这种思路很好理解,但是存在着很明显需要解决的问题:
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1. 每次生成排列时,如何实现不遗漏不重复的“取”数字?
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
只要取n次,每次取得数字不重复,那么自然就不会遗漏,因此关键在于不重复:可以考虑把取出的数字放在一个数组A中,下次取的时候通过和此数组对比来决定取哪个数字。这里面还有个细节,如果P中的n个数是互不重复的,那么只需要取与A中数字不等的数字即可,但是如果P中的n个数字有重复的话,则可以取
与A中数字
相等的数,只要这个相等的数字在A中的出现次数小于原数组P中的总次数即可。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2. 如何不遗漏
不重复
地取得所有的排列结果?
如果天马行空地取数,很有可能不断地取到重复的排列,最后导致永远都无法不遗漏取出所有的不重复的排列。为了达到不遗漏不重复的目的,对于不同排列的构造顺序是有要求的。这个构造顺序的设计就是本算法的核心所在。我们耐心的来分析,简单起见,先考虑n个数字不重复的情况,再简化一下,假设就是1,2,3,4这四个数字(特例,简化是思维中最常见,最自然但是也最有效的方法)。我们就来列出它的全排列,首先自然而然我们把排列分为了4组,1开头的,2开头的,3开头的和4开头的,下面分别把他们列出来:
先列1开头的:
1 2 3 4
1 2 4 3
1 3 2 4
1 3 4 2
1 4 2 3
1 4 3 2
再列2开头的:
2 1 3 4
2 1 4 3
2 3 1 4
2 3 4 1
2 4 1 3
2 4 3 1
3开头的和4开头的不用列了,因为在自动动手列的过程中,我们已经感受到了其中的规律对于每个排列,我们脑海中做的事情是这样的,确定第1个数字(分4组就是在做这个事情),确定剩下的数字(以1开头的排列为例,我们很自然的把剩下的2,3,4这三个数字做了全排列)而对于2,3,4这三个数字的全排列,我们的做法仍然是:确定第一个数字,然后确定剩下的数字。这里的逻辑最合适的实现的方法就是递归。因为
适合用递归来解决的问题的特点是:
可以分步骤来解决的问题,而且每一步里面都有一个不变的逻辑。而本问题可以完美地匹配到这些特点。对于更复杂的情况,也满足这个结论。
三、代码
1. 伪代码
cur:当前步数
A:当前已经存放了cur个数字的数组
不变逻辑:从1~n中选出不属于A的数组,放入A,cur++,进入下一步。
递归边界:cur==n

2.排序函数和测试主函数
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>
#include <algorithm> 
using namespace std;
int int_compare(const void* _a,const void* _b);
void Permute1(int n,int* A);// 使用STL里面的库函数
void Permute2(int n, int* P, int* A, int cur);// 从n由小到大的横向分割排列的角度

#define MAX_PERMUTATION_LEN 20
int Permutation()
{
FILE* fp;
int n;
int A[MAX_PERMUTATION_LEN],P[MAX_PERMUTATION_LEN];
        fp = fopen("Permutation.txt","rt");
        if (NULL == fp)
        {
                printf("Can not find input file!");
                return -4;
        }
        while(1){
                if (1 != fscanf(fp,"%d",&n))
                {
                        printf("Can not read in length!");
                        fclose(fp);
                        return -1;
                }       

                if (n>MAX_PERMUTATION_LEN)
                {
                        printf("Too long!");
                        fclose(fp);
                        return -2;
                }
                if (n == 0) // end flag
                {
                        return 0;
                }
                for (int i=0; i<n; i++)
                {
                        if (1 != fscanf(fp,"%d",&P[i]))
                        {
                                printf("Can not read in number!");
                                fclose(fp);
                                return -3;
                        }       
                }

                qsort(P,n,sizeof(int),int_compare);
                //Permute1(n,P);
                Permute2(n,P,A,0);
                printf("-------------------\n");
        }

        
        //Permute2(n,A,0);
        fclose(fp);
        return 0;
}

void Permute1(int n,int* A)
{
        do 
        {
                for (int i=0; i<n; i++)
                {
                        printf("%d ",A[i]);
                }
                printf("\n");
        } while (std::next_permutation(A,A+n));
}

void Permute2(int n, int* P, int* A, int cur)
{
        int i,j;
        if(n == cur){
                for (i=0; i<n; i++)
                {
                        printf("%d ",A[i]);
                }
                printf("\n");
        }else {
                for (i=0; i<n;i++)
                        if (i==0 || P[i] !=P[i-1])
                        {
                                int c1=0,c2=0;
                                for (j=0; j<cur; j++) if (A[j] == P[i]) c1++;
                                for (j=0; j<n; j++) if (P[j] == P[i]) c2++;

                                if (c1<c2)
                                {
                                        A[cur] = P[i];
                                        Permute2(n,P,A,cur+1);
                                }
                        }
        }
}


int main()
{
        Permutation();

        return 0;
}

3.测试文件
3
1 1 1
4
1 1 2 2
5
1 2 3 4 5
0

四、总结
1. 一个问题,不是由人,而是交给计算机解决,这就是算法要做的事情。由于计算机的特殊性,必须把人头脑里面的方法用代码的方式告诉计算机。这个代码的最大特点之一就是:必须是具可重复的模式。
2. 解答树:如果一个问题的解可以由多个步骤获得,而每个步骤都有若干选择(这些选择可能依赖于先前的选择),且可以递归枚举实现,这可以用解答树来描述。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值