·1.1.1_总是可行的枚举。
假设有一组数:{1,8,9,7,5,6,1,10,6},需要得到其中最大的数的值,对于人来说,显然,最大的数的值为10。但对于计算机来说,它并不能“一眼看”出这组数的最大值。
有一个比较好的且易于理解的方法便是先记录前m个数的最大值max_number,然后再插入第m+1个数,对比max_number与第m+1个数的值之间的大小,如果max_number是较小数,则将第m+1个数的值赋于max_number,max_number遍成为前m+1个数的最大值。
这个方法是可行且容易证明其可行性的。(下面的证明过程可以不看。)
证明,设 N[i]为一组数中的第i个数。
因对于一个数N[1]来说,最大值与最小值就是这个数本身,则max_number的值等于N。
此时,前1个数的最大值max_number与N[2]对比,无论对比的结果如何,max_number都为前2个数的最大值。
由此类推,当 i 等于 N 时,max_number则为前N个数的最大值。由此,此算法成立并且由于进行了N-1次判断,时间复杂度为O(N-1)
而上述方法,便是称为枚举法。其基本思想为,一个不漏地查看所有可能的情况。
是不是和一种修辞手法举例子很像呢?枚举法可以看成 举出所有例子的一种手法嘛。
·1.1.2_枚举的局限性。
枚举在大多数情况下都可行,但毕竟没有万能的方法,更何况是一个如此直观又易于理解的方法呢。此节的名称是暴力的枚举,有一句话云,暴力出奇迹,这句话显示了暴力是一个可以创造奇迹的方法,但是暴力在大多数情况下都是不可取的阿!
枚举的局限性:枚举是一种很笨的方法,他仅适用于一些规模很小的问题。
·1.1.3_枚举的优点。
在实际做题中,特别是比赛当中,我们没有更多事件去思考是不是有更优的解决方法,所以,我们很多时候是会选择一个看似可行的方法。这时候,作为最容易让人想起的方法——枚举法,便大有可为了。
看到这里,如果读者您或者想到,枚举法的优点就只有简单吗?那您真的是too young too simple了。(此处拒绝续命。
其实枚举法还是一个可控性比较强的方法。对于可控性,我是这样想的,这个方法是实际应用中相对难以出错的算法,无论是逻辑上的错误还是个人低级错误(例如写错变量之类的)。他只需要相对短小精悍的代码来实现,而且优化起来灵活。
·1.1.4_更好地枚举。
上一点提到了枚举的优化起来比较灵活,此处简单介绍几种枚举的常见优化方法和应用例子。
①减少不可能情况。有一些情况是显而易见不可能的,所以可以除去。
②选择更好的数据结构进行储存。用适当的数据结构解决适当的问题便是数据结构的意义所在。
③适当地用空间换取时间。在算法比赛中(OJ刷题中),给出的空间往往是很充裕的,某些情况下,用空间换取时间是很好的选择。
④N元方程的优化。在某些N元方程中,我们往往只需要计算N-1元。
一个例子。给定长度为n的数组S,判断数组中的元素知否存在a,b,c,使得a+b+c=0。找出所有满足条件的元素并输出。
对于此问题,我们枚举所有所有可能方案,由所有可能方案则为S中任意三个元素的组合,则为 n*(n-1)*(n-2)/(2*3) 个可能方案。但这是可以优化的,用方法④。
假设,A[x]为a取S中第x个数,B[y]为b取S中第y个数,C[z]为c取S中第z个数。(x != y != z)
我们就可以得到,此问题转化为求 A[x]+B[y]+C[z] = 0时,x,y,z的值。
再转化一下,则变为 A[x] + B[y] = -C[z],则我们只需要枚举 A[x] + B[y]的值是否有存在一个其的相反数c在数组S中且不为第x或第y个数即可。
等等,假如你是机智可爱的小读者,你就会聪明的发现,这有优化么,喵喵喵喵喵喵喵?还需要判断一次c是否存在哇!这复杂度没变过阿。
(=u=),机智可爱的读者们说得没错,但毕竟,我们还有其他方法不是么。
由于现在我们需要解决的问题是判断数组S中剩余的数是否存在第c,所以,这就可以用方法②和方法③来解决了!这里我选择开一个散列表来解决。只要我们往散列表中,将S的所有数作为key来put进HashMap中,那么我们就可以仅付出O(1)的代价来查询 数c是否存在于数组S中了不是么?
所以,时间复杂度便从 O(n*(n-1)*(n-2)/6) 优化成了 O(n*(n-1)/2),这可是 O(n的3次方) 级别到 O(n的2次方) 级别的飞跃阿!
此栗子举例了方法②③④在枚举中的应用,关于①方法,读者可以自己去尝试一下,亦可在后续文章中听本蒻进行讲解。
这里是叶攻攻。