天天看点

算法导论第四章分治策略实例解析(一)

一、第三章简单回顾 

  中间略过了第三章, 第三章主要是介绍如何从数学层面上科学地定义算法复杂度,以致于能够以一套公有的标准来分析算法。其中,我认为只要记住三个符号就可以了,其他的就看个人情况,除非你需要对一个算法剖根问底,不然还真用不到,我们只需有个印象,知道这玩意是用来分析算法性能的。三个量分别是:确定一个函数渐近上界的Ο符号,渐近下届Ω符号,以及渐近紧确界Θ符号,这是在分析一个算法的界限时常用的分析方法,具体的就详看书本了,对于我们更多关注上层算法的表达来说,这些显得不是那么重要,我的理解是Ο可以简单看成最坏运行时间,Ω是最好运行时间,Θ是平均运行时间。一般我们在写一个算法的运行时间时,大多是以Θ符号来表示。参考下面这幅经典的图:

算法导论第四章分治策略实例解析(一)

二、第四章两大板块

  第四章讲递归,也是数学的东西太多了,我准备这样来组织这章的结构:先用一个例子(最大子数组和)来讲解用到递归的一个经典方法——分治法,然后在引入如何解递归式,即引入解递归式的三种方法。

1、由分治法引发的

 这一章提出了一个在现在各大IT公司在今天依然很喜欢考的一道笔试面试题:

要求时间复杂度是O(n),我们暂且不管这个,由浅入深地分析一下这道题,时间复杂度从O(n^2)->O(nlgn)->O(n)。

1)、第一,大部分人想到的肯定是暴力法,两个for循环,时间复杂度自然是O(n^2),如下:

2)、第二,看了《算法导论》,你可能会想到分治法,看完之后你肯定会为该分治思想而惊叹,尤其是“横跨中点”的计算思想。简单说下该分治思想,其实很简单,最大和子数组无非有三种情况:左边,右边,中间。

时间复杂度分析:

  根据分治的思想,时间复杂度的计算包括三部分:两边+中间。由于分治的依托就是递归,我们可以写出下面的递推式(和合并排序的递推式是一样的):

算法导论第四章分治策略实例解析(一)

其中的Θ(n)为处理最大和在数组中间时的情况,经过计算(怎么计算的,请看本节第二部分:解分治法的三种方法),可以得到分治法的时间复杂度为Θ(nlgn)。代码如下:

3)、第三,看了《算法导论》习题4.1-5,你又有了另外一种思路:数组A[1...j+1]的最大和子数组,有两种情况:a) A[1...j]的最大和子数组; b) 某个A[i...j+1]的最大和子数组,假设你现在不知道动态规划,这种方法也许会让你眼前一亮,确实是这么回事,恩,看代码吧。时间复杂度不用想,肯定是O(n)。和暴力法比起来,我们的改动仅仅是用一个指针指向某个使和小于零的子数组的左区间(当和小于零时,区间向左减小,当和在增加时,区间向右增大)。因此,我们给这种方法取个名字叫区间法。

第四,你可能在平常的学习过程中,听说过该问题最经典的解是用动态规划来解,等你学习之后,你发现确实是这样,然后你又一次为之惊叹。动态规划算法最主要的是寻找递推关系式,大概思想是这样的:数组A[1...j+1]的最大和:要么是A[1...j]+A[j+1]的最大和,要么是A[j+1],据此,可以很容易写出其递推式为:

sum[i+1] = Max(sum[i] + A[i+1], A[i+1])

化简之后,其实就是比较sum[i] ?> 0(sum[i] + A[i+1] ?> A[i+1]),由此,就很容易写出代码如下:

  可以看出,区间法和动态规划有几分相似,我觉得两种方法的出发点和终点都是一致的,只不过过程不同。动态规划严格遵循递推式,而区间法是寻找使区间变化的标识,即和是否小于零,而这个标识正是动态规划采用的。

继续阅读