什么是枚举,百科上的解释:在数学和计算机科学理论中,一个集的枚举是列出某些有穷序列集的所有成员的程序,或者是一种特定类型对象的计数。这两种类型经常(但不总是)重叠。是一个被命名的整型常数的集合,枚举在日常生活中很常见,例如表示星期的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
如果用解方程的方式解这道题需要进行多次猜解,计算机的一个优势就是计算速度特别暴力。因此我们用穷举法的方式来解题,需要 1013 次猜解,但对于计算机来说,完全没问题。
程序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 只\n", x, y, z);
}
}
return 0;
}
运行结果:
公鸡 0 只,母鸡 25 只,小鸡 75 只
公鸡 4 只,母鸡 18 只,小鸡 78 只
公鸡 8 只,母鸡 11 只,小鸡 81 只
公鸡 12 只,母鸡 4 只,小鸡 84 只
回看上面这个算法,我们使用了3层循环,时间复杂度为:o(n3),在实际生活中,这种复杂度的算法我们根本不会用。那么我们有没有可能继续优化这个算法呢?
分析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 只\n", x, y, z);
}
}
return 0;
}
这样虽然还是没有改变时间复杂度为:o(n3)的情况,但是我们的运算次数从1013缩小到了203399次,大概减少了十五六倍的运算。
分析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 只\n", x, y, z);
}
}
return 0;
}
这次只用了两次循环,时间复杂度为:o(n2),直接少了个能级。而且该题中直接从上次的2134100变成了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 只\n", x, y, z);
}
return 0;
}
这样,我们一个循环就解决了这个问题。时间复杂度为:o(n),该问题只需四次运算,同之前的1012,2134100以及21*34,简单了无数倍。
所以我们在不得已使用枚举法时,也要尽量的减少程序的运行次数以提高效率。
例二
完美立方问题,问题描述:a3 = b3 + c3 + d3 为完美立方等式。例如123 = 6^3 + 83 + 103。
编写一个程序,对任意的正整数N(N≤100),寻找所有的四元组(a,b,c,d),使得a3 = b3 + c3 + d3,其中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\n",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