|
|
|
|
移动端

2.4.3 最大子序列和问题的求解

《数据结构与算法分析:Java语言描述(原书第3版)》第2章算法分析,本章对如何分析程序的复杂性给出一些提示。遗憾的是, 它并不是完善的分析指南。简单的程序通常给出简单的分析, 但是情况也并不总是如此。本节为大家介绍最大子序列和问题的求解。

作者:冯舜玺/陈越 译来源:机械工业出版社|2016-04-13 11:36

年前最后一场技术盛宴 | 1月27日与京东、日志易技术大咖畅聊智能化运维发展趋势!


2.4.3 最大子序列和问题的求解

现在我们将要叙述四个算法来求解早先提出的最大子序列和问题。第一个算法如图2-5所示, 它只是穷举式地尝试所有的可能。for循环中的循环变量反映了Java中数组从0开始而不是从1开始这样一个事实。还有, 本算法并不计算实际的子序列; 实际的计算还要添加一些额外的代码。

该算法肯定会正确运行(这用不着花太多的时间去证明)。运行时间为O(N3), 这完全取决于第13行和第14行, 它们由一个含于三重嵌套for循环中的O(1)语句组成。第8行上的循环大小为N。

第2个循环大小为N-i,  它可能要小, 但也可能是N。我们必须假设最坏的情况, 而这可能会使得最终的界有些大。第3个循环的大小为j-i+1我们也要假设它的大小为N。因此总数为O(1·N·N·N)=O(N3)。第6行总共的开销只是O(1), 而语句16和17也只不过总共开销O(N2), 因为它们只是两层循环内部的简单表达式。

事实上, 考虑到这些循环的实际大小, 更精确的分析指出答案是Θ(N3), 而我们上面的估计高6倍(不过这并无大碍, 因为常数不影响数量级)。一般说来, 在这类问题中上述结论是正确的。精确的分析由和∑N-1i=0∑N-1j=i∑jk=i1得到, 39该“和”指出程序的第14行被执行多少次。使用1.2.3节中的公式可以对该和从内到外求值。特别地, 我们将用到前N个整数求和以及前N个平方数求和的公式。首先有

我们可以通过撤除一个for循环来避免三次的运行时间。不过这不总是可能的, 在这种情况下算法中出现大量不必要的计算。纠正这种低效率的改进算法可以通过观察∑jk=iAk=Aj+∑j-1k=iAk而看出, 因此算法1中第13行和第14行上的计算过分地耗费了。图2-6给出了一种改进的算法。算法2显然是O(N2); 对它的分析甚至比前面的分析还简单。

对这个问题有一个递归和相对复杂的O(N logN)解法, 我们现在就来描述它。要是真的没出现O(N)(线性的)解法, 这个算法就会是体现递归威力的极好的范例了。该方法采用一种“分治(divide-and-conquer)”策略。其想法是把问题分成两个大致相等的子问题, 然后递归地对它们求解, 这是“分”的部分。“治”阶段将两个子问题的解修补到一起并可能再做些少量的附加工作, 最后得到整个问题的解。

在我们的例子中, 最大子序列和可能在三处出现。或者整个出现在输入数据的左半部, 或者整个出现在右半部, 或者跨越输入数据的中部从而位于左右两半部分之中。前两种情况可以递归求解。40第三种情况的最大和可以通过求出前半部分(包含前半部分最后一个元素)的最大和以及后半部分(包含后半部分第一个元素)的最大和而得到。此时将这两个和相加。作为一个例子, 考虑下列输入:

其中前半部分的最大子序列和为6(从元素A1到A3)而后半部分的最大子序列和为8(从元素A6到A7)。

前半部分包含其最后一个元素的最大和是4(从元素A1到A4), 而后半部分包含其第一个元素的最大和是7(从元素A5到A7)。因此, 横跨这两部分且通过中间的最大和为4+7=11(从元素A1到A7)。

我们看到, 在形成本例中的最大和子序列的三种方式中, 最好的方式是包含两部分的元素。于是, 答案为11。图2-7提出了这种策略的一种实现手段。

有必要对算法3的程序进行一些说明。递归过程调用的一般形式是传递输入的数组以及左边界和右边界, 它们界定了数组要被处理的部分。单行驱动程序通过传递数组以及边界0和N-1而将该过程启动。

第8行至第12行处理基准情况。如果left==right, 那么只有一个元素, 并且当该元素非负时它就是最大子序列。left>right的情况是不可能出现的, 除非N是负数(不过, 程序中小的扰动有可能致使这种混乱产生)。第15行和第16行执行两个递归调用。我们可以看到, 递归调用总是对小于原问题的问题进行, 不过程序中的小扰动有可能破坏这个特性。第18行至第24行以及第26行至第32行计算达到中间分界处的两个最大和的和数。这两个值的和为扩展到左右两部分的最大和。例程max3(未给出)返回这三个可能的最大和中的最大者。

显然, 算法3需要比前面两种算法更多的编程努力。然而, 程序短并不总意味着程序好。正如我们在前面显示算法运行时间的表中已经看到的, 除最小的输入量外, 该算法比前两个算法明显要快。

对运行时间的分析方法与在分析计算斐波那契数程序时的方法类似。令T(N)是求解大小为N的最大子序列和问题所花费的时间。如果N=1, 则算法3执行程序第8行到第12行花费某个常数时间量, 我们称之为一个时间单位。于是, T(1)=1。否则, 程序必须运行两个递归调用, 即在第19行和第32行之间的两个for循环, 以及某个小的簿记量, 如第14行和第18行。这两个for循环总共接触到从A0到AN-1的每一个元素, 而在循环内部的工作量是常量, 因此, 在第19到32行花费的时间为 O(N)。在第8行到第14行, 第18、 26和34行上的程序的工作量都是常量, 从而与O(N)相比可以忽略。其余就是第15、 16行上运行的工作。这两行求解大小为N/2的子序列问题(假设N是偶数)。因此, 这两行每行花费T(N/2)个时间单元, 共花费2T(N/2)个时间单元。算法3花费的总的时间为2T(N/2)+O(N)。我们得到方程组

T(1)=1

T(N)=2T(N/2)+O(N)

为了简化计算, 我们可以用N代替上面方程中的O(N)项; 由于T(N)最终还是要用大O来表示, 因此这么做并不影响答案。在第7章, 我们将会看到如何严格地求解这个方程。至于现在, 如果T(N)=2T(N/2)+N, 且T(1)=1, 那么T(2)=4=2*2, T(4)=12=4*3, T(8)=32=8*4, 以及T(16)=80=16*5。其形式是显然的并且可以得到, 即若N=2k, 则T(N)=N*(k+1)=N log N+N=O(N log N)。

这个分析假设N是偶数, 否则N/2就不确定了。通过该分析的递归性质可知, 实际上只有当N是2的幂时结果才是合理的, 否则我们最终要得到大小不是偶数的子问题, 方程就是无效的了。当N不是2的幂时, 我们多少需要更加复杂一些的分析, 但是大O的结果是不变的。

在后面的章节中, 我们将看到递归的几个漂亮的应用。这里, 我们还是介绍求解最大子序列和的第4种方法, 该算法实现起来要比递归算法简单而且更为有效。它在图2-8中给出。

不难理解为什么时间的界是正确的, 但是要明白为什么算法是正确可行的却需要多加思考。为了分析原因, 注意, 像算法1和算法2一样, j代表当前序列的终点, 而i代表当前序列的起点。碰巧的是, 如果我们不需要知道具体最佳的子序列在哪里, 那么i的使用可以从程序上被优化, 因此在设计算法的时候假设i是需要的, 而且我们想要改进算法2。一个结论是, 如果a[i]是负的, 那么它不可能代表最优序列的起点, 因为任何包含a[i]的作为起点的子序列都可以通过用a[i+1]作起点而得到改进。类似地, 任何负的子序列不可能是最优子序列的前缀(原理相同)。如果在内循环中检测到从a[i]到a[j]的子序列是负的, 那么可以推进i。关键的结论是, 我们不仅能够把i推进到i+1, 而且实际上还可以把它一直推进到j+1。为了看清楚这一点, 令p为i+1和j之间的任一下标。开始于下标p的任意子序列都不大于在下标i开始并包含从a[i]到a[p-1]的子序列的对应的子序列, 因为后面这个子序列不是负的(j是使得从下标i开始其值成为负值的序列的第一个下标)。因此, 把i推进到j+1是没有风险的: 我们一个最优解也不会错过。

这个算法是许多聪明算法的典型: 运行时间是明显的, 但正确性则不那么容易看出来。对于这些算法, 正式的正确性证明(比上面的分析更正式)几乎总是需要的; 然而, 即使到那时, 许多人仍然还是不信服。此外, 许多这类算法需要更有技巧的编程, 这导致更长的开发过程。不过当这些算法正常工作时, 44它们运行得很快, 而我们将它们和一个低效(但容易实现)的蛮力算法通过小规模的输入进行比较可以测试到大部分的程序原理。

该算法的一个附带的优点是, 它只对数据进行一次扫描, 一旦a[i]被读入并被处理, 它就不再需要被记忆。因此, 如果数组在磁盘上或通过互联网传送, 那么它就可以被按顺序读入, 在主存中不必存储数组的任何部分。不仅如此, 在任意时刻, 算法都能对它已经读入的数据给出子序列问题的正确答案(其他算法不具有这个特性)。具有这种特性的算法叫作联机算法(on-line algorithm)。仅需要常量空间并以线性时间运行的联机算法几乎是完美的算法。

喜欢的朋友可以添加我们的微信账号:

51CTO读书频道二维码


51CTO读书频道活动讨论群:342347198

【责任编辑:book TEL:(010)68476606】

回书目   上一节   下一节
点赞 0
分享:
大家都在看
猜你喜欢

读 书 +更多

网管员必读——网络安全(第2版)

本书是在《网管员必读—网络安全》第1版的基础上修改而成的。新版在保留第1版实用内容的基础上增加了大量新的实用内容,同时删除了一些过时...

订阅51CTO邮刊

点击这里查看样刊

订阅51CTO邮刊