枚举

什么是枚举,百科上的解释:在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的SUNDAY、MONDAY、TUESDAY、WEDNESDAY、THURSDAY、FRIDAY、SATURDAY就是一个枚举。

简单的理解,枚举就是逐个尝试答案的一种问题求解策略。

平时我们解决问题有很好的方法,比如数学方法,用数学方法解决问题我们往往是找规律,推出公式。如果用公式直接能得到答案,那就基本上没有什么时间复杂度,所以数学方法是很高明的。

但是现实生活中有很多是没有规律可循的,我们只好用笨办法,就一个一个的试,去看看哪个是真实的答案。

例如:求小于N的最大素数。这种问题就找不到一个数学公式,使根据N就可以计算出这个素数。所以我们得判断N-1是不是素数?N-2是不是素数?……这样依次下去,判断N-i是不是素数。这就是所谓的枚举的思想。

下面例出几个问题。题目皆来自网络。

例一

公鸡每只五元,母鸡每只三元,小鸡三只一元,用一百元买一百只鸡,问公鸡,母鸡,小鸡各多少只?

利用枚举法解决该问题,以三种鸡的个数为枚举对象,分别设x,y,z,用三种鸡的总数 和买鸡钱的总数作为判定条件,穷举各种鸡的个数。

x + y + z = 100
5*x + 3*y + z/3 = 100

分析1

如果用解方程的方式解这道题需要进行多次猜解,计算机的一个优势就是计算速度特别暴力。因此我们用穷举法的方式来解题,需要 101^3 次猜解,但对于计算机来说,完全没问题。

程序1:

#include <stdio.h>

int main()
{
    int x, y, z;
    
    for( x=0; x <= 100; x++ )
        for( y=0; y <= 100; y++ )
            for( z=0; z <= 100; z++ )
            {
                if( 5*x+3*y+z/3==100 && z%3==0 && x+y+z==100 )
                {
                    printf("公鸡 %2d 只,母鸡 %2d 只,小鸡 %2d 只
", x, y, z);
                }
            }

    return 0;
}

运行结果:

公鸡  0 只,母鸡 25 只,小鸡 75 只
公鸡  4 只,母鸡 18 只,小鸡 78 只
公鸡  8 只,母鸡 11 只,小鸡 81 只
公鸡 12 只,母鸡  4 只,小鸡 84

回看上面这个算法,我们使用了3层循环,时间复杂度为:o(n^3),在实际生活中,这种复杂度的算法我们根本不会用。那么我们有没有可能继续优化这个算法呢?

分析2

我试着分析了初始式子,两个式子,三个未知数,我们应该可以确定每个未知数的定义域。于是我得到:

x <= 20;
y <= 33;
z <=99;

程序2:

#include <stdio.h>

int main()
{
    int x, y, z;
    
    for( x=0; x <= 20; x++ )
        for( y=0; y <= 33; y++ )
            for( z=0; z <= 99; z++ )
            {
                if( 5*x+3*y+z/3==100 && z%3==0 && x+y+z==100 )
                {
                    printf("公鸡 %2d 只,母鸡 %2d 只,小鸡 %2d 只
", x, y, z);
                }
            }

    return 0;
}

这样虽然还是没有改变时间复杂度为:o(n^3)的情况,但是我们的运算次数从101^3缩小到了20*33*99次,大概减少了十五六倍的运算。

分析3

然后我再试着是否能用x,y来表示z以减少循环次数。

z = 100 - x - y;

程序3:

#include <stdio.h>

int main()
{
    int x, y, z;
    
    for( x=0; x <= 20; x++ )
        for( y=0; y <= 33; y++ )
        {
            z = 100 - x - y;
            if( 5*x+3*y+z/3==100 && z%3==0)
            {
                printf("公鸡 %2d 只,母鸡 %2d 只,小鸡 %2d 只
", x, y, z);
            }
        }
    return 0;
}

这次只用了两次循环,时间复杂度为:o(n^2),直接少了个能级。而且该题中直接从上次的21*34*100变成了21*34,次数直接减少100倍。

分析4

初高中做题时,一般三元一次方程组,我们会选择消元来解题,那么这题我们能不能直接消去z呢。

x + y + z = 100   ①
5x + 3y + z/3 = 100   ②

3 * ①,得: 
15x + 9y + z = 300 ③
联立②③,得:
(15x + 9y + z = 300) - (x + y + z = 100)推出 14x + 8y = 200 解得:y = 25 - (7/4)x ④

然后我们将④带入原式

y = 25 - (7/4)x
为了消除分母,我们设:x = 4k,则:
x = 4k;
y = 25 - 7k;
z= 100 - x - y = 75 + 3k;
根据分析②:x <= 20 ,y <= 33,z <=99,且三者皆>=0
得:k=1,2,3

 程序4:

#include <stdio.h>

int main()
{
    int x, y, z,k;
    
    for( k=0; k <= 3; k++ )
    {
        x = 4 * k;
        y = 25 - 7 * k;
        z = 75 + 3 * k;
        printf("公鸡 %2d 只,母鸡 %2d 只,小鸡 %2d 只
", x, y, z);
    }
    return 0;
}

这样,我们一个循环就解决了这个问题。时间复杂度为:o(n),该问题只需四次运算,同之前的101^2,21*34*100以及21*34,简单了无数倍。

所以我们在不得已使用枚举法时,也要尽量的减少程序的运行次数以提高效率。

例二

 完美立方问题,问题描述:a^3 = b^3 + c^3 + d^3为完美立方等式。例如12^3 = 6^3 + 8^3 + 10^3。

编写一个程序,对任意的正整数N(N≤100),寻找所有的四元组(a,b,c,d),使得a^3 = b^3 + c^3 + d^3,其中a > 1,b,c,d≤N。

#include <stdio.h>

int main()
{
    int i,n,a,b,c,d;
    scanf("%d",&n);//输入正整数n 
    for(a=2;a<=n;a++)
       for(b=2;b<=a-1;b++)
         for(c=b;c<=a-1;c++)
           for(d=c;d<=a-1;d++)
                if(a*a*a==b*b*b+c*c*c+d*d*d)
                printf("a=%d,b=%d,c=%d,d=%d
",a,b,c,d);
    return 0;
}

对于这道题我们也是尽量减小b,c,d的取值范围来增快程序的运算速率。

运行结果:

24
a=6,b=3,c=4,d=5
a=12,b=6,c=8,d=10
a=18,b=2,c=12,d=16
a=18,b=9,c=12,d=15
a=19,b=3,c=10,d=18
a=20,b=7,c=14,d=17
a=24,b=12,c=16,d=20
原文地址:https://www.cnblogs.com/zj666666/p/12327583.html