《算法竞赛入门经典》-【第七章:暴力求解法】-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 
#include 
#include 
#include 
#include  
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

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

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

你可能感兴趣的:(算法)