枚举
首先,最为简单的思想,枚举算法。枚举也叫穷举,顾名思义,就是穷尽列举。枚举思想的应用场景十分广泛,也非常容易理解。简单来说,枚举就是将问题的可能解依次列举出来,然后一一带入问题检验,从而从一系列可能解中获得能够解决问题的精确解。
枚举虽然看起来简单,但是其实还是有一些容易被人忽视的考虑点。比方说待解决问题的「可能解/候选解」的筛选条件,「可能解」之间相互的影响,穷举「可能解」的代价,「可能解」的穷举方式等等。
很多时候实际上不必去追求高大上的复杂算法结构,反而大道至简,采用枚举法就能够很好的规避系统复杂性带来的冗余,同时或许在一定程度上还能够对空间进行缩减。
枚举思想的流程可以用下图来表示。通过实现事先确定好「可能解」,然后逐一在系统中进行验证,根据验证结果来对「可能解」进行分析和论证。这是一种很明显的结果导向型的思想,简单粗暴地试图从最终结果反向分析「可能解」的可行性。
不过,枚举思想的劣势当然也很明显。在实际的运行程序中,能够直接通过枚举方法进行求解的问题少之又少。而当「可能解」的筛选条件不清晰,导致「可能解」的数量和范围无法准确判断时,枚举就失去了意义。
然而当「可能解」的规模比较小,同时依次验证的过程容易实施时,枚举思想不失为一种方便快捷的方式。只不过在具体使用时,还可以针对应用场景对「可能解」的验证进行优化。
这种优化可以从两个方向入手,一是问题的简化,尽可能对需要处理的问题进行模型结构上的精简。这种精简具体可体现在问题中的变量数目,减少变量的数据,从而能够从根本上降低「可能解」的组合。
二是对筛选「可能解」的范围和条件进行严格判断,尽可能的剔除大部分无效的「可能解」。
虽说如此,但是一般而言大部分枚举不可用的场景都是由于「可能解」的数量过多,无法在有限空间或有限时间内完成所有可能性的验证。不过实际上枚举思想是最接近人的思维方式,在更多的时候是用来帮助我们去「理解问题」,而不是「解决问题」。
应用
百钱买百鸡问题。 该问题叙述如下:鸡翁一,值钱五;鸡母一,值钱三;鸡雏三,值钱一;百钱买百鸡,则翁、母、雏各几何?
翻译过来,意思是公鸡一个五块钱,母鸡一个三块钱,小鸡三个一块钱,现在要用一百块钱买一百只鸡,问公鸡、母鸡、小鸡各多少只?
解:假设公鸡有x只,母鸡有y只,则小鸡有100-x-y只。可以推出一个等式:5x + 3y + (100-x-y)*1/3 = 100。由于鸡的数量肯定不能为小数,所以我们在等式两边同乘3,变为 15x + 9y + 100-x-y = 300。再化简:14x + 8y = 200;有了该等式,我们的代码就显得很简单了。
//公鸡一个五块钱,母鸡一个三块钱,小鸡三个一块钱,
//现在要用一百块钱买一百只鸡,问公鸡、母鸡、小鸡各多少只?
int BuyChicken(int price, int num)// 钱, 只
{
int total = 0;
for (int x = 0; x < num / 5; ++x)
{
for (int y = 0; y < num / 3; ++y)
{
int z = num - x - y;
//15*x + 9*y + z = 3*price
if (14 * x + 8 * y == 2 * price)
{
++total;
printf("公鸡:%d, 母鸡:%d, 小鸡:%d, \n", x, y, z);
}
}
}
return total;
}
int main()
{
int price = 100;
int num = 100;
int res = BuyChicken(price, num);
printf("花%d块钱买%d只鸡共有%d中方案\n", price, num, res);
return 0;
}
运行结果: