二分,一个简单而神奇的算法,可以简化时间复杂度,但是用二分有一个条件,就是我们进行二分的序列必须有单调性。
我们直接上一个栗子:从一个单调上升序列 a a a中找一个数 x x x(保证 x x x一定存在)。
我们可以用朴素算法,直接从头到尾找一遍,时间复杂度 O ( n ) O(n) O(n)。 如果当序列 a a a的长度十分大时,这种方法就不好用了。
二分横空出世。 我们每次寻找左右边界的中间。 如果这个数大于 x x x,右边界就等于中间。 否则,左边界就等于中间。 知道找到答案。
在二分过程中,每次都把查询的区间减半,因此对于一个长度为 n n n的数组,至多会进行 O ( l o g n ) O(log\ n) O(log n)次查找,从而起到简化时间的作用。
三分可以在一个单峰函数中找出其凸点或凹点。
我们就是把 [ l , r ] [l,r] [l,r]区间分成三份,如果 f ( m i d l ) > f ( m i d r ) f(midl)>f(midr) f(midl)>f(midr),则 r = m i d r r=midr r=midr;否则 l = m i d l l=midl l=midl(这是求凸点)。 这看起来跟二分很想。 当 f ( m i d l ) > f ( m i d r ) f(midl)>f(midr) f(midl)>f(midr),仔细思考一下,当 f ( m i d l ) > f ( m i d r ) f(midl)>f(midr) f(midl)>f(midr)时, m i d r midr midr一定在凸点右边,所以我们把 r = m i d r r=midr r=midr。 否则,我们就把 l = m i d l l=midl l=midl,这也十分好理解。
第一种写法
double three_divide(double l,double r) { while(r-l>=eps) { double midl=(l+r)>>1,midr=(midl+r)>>1; if(f(midl)>f(midr)) r=midr; else l=midl; } return f(l); }第二种写法
double three_divide(double l,double r) { while(r-l>=eps) { double midl=l+(r-l)/3,midr=r-(r-l)/3; if(f(midl)>f(midr)) r=midr; else l=midl; } return f(l); }但其实没有啥区别。