关于凸函数求最大值的下标的小讨论(斐波那契优选法/二分法/三分法)

问题:假设F是定义在整数集合的函数,并且F在区间[L,R]中先严格单调递增再严格单调递减,求最大值下标。


方法一:三分法

对于区间[ L , R ] ,在区间取左右两个三等分点M1,M2;

V1=F(M1)  V2=F(M2) 比较V1,V2的值.

若 V1<V2 则最大值一定不在M1左侧,继续判断新区间[M1,R]

若 V1>V2 则最大值一定不在M2右侧,继续判断新区间[L,M2]

V1=V2时,为了写代码方便,可归入上面任意一类。

于是每次去掉区间的33.3%,并且计算两次F

F的计算次数约为 2*log1.5 ( n),约为斐波那契优选法的2倍多一点


方法二:斐波那契优选法(三分法的优化)

斐波那契优选法的重点就在于代码的重用,

令区间长度R-L为某个斐波那契数(R不足时扩充R,使F对大于R的值都返回F[R]来避免大于R的值干扰结果)

用D[n]表示斐波那契数列的第n个数。

假设区间长度为D[n],那么取  M1=D[n-2],   M2=D[n-1].

V1=F(M1)  V2=F(M2) 比较V1,V2的值.

若V1<V2  最大值不在M1左侧,继续判断新区间[M1,R] 。

但是,此时,新区间的newM1 = 旧的M2 于是newV1的值我们已经知道了,等于V2,所以少了一次对F的计算(总共就两次,所以是少了一半)。

见下图:两行分别是旧的和新的L,M1,M2,R.其中L是绝对地址,M1,M2,R都是相对于L的地址。


另一边也是这样,就不赘述了。

V1=V2时,需归入V1>V2的区间,这是为了优先删除右区间,因为右区间是有赘余的。

由于是用的斐波那契数,由斐波那契数之间的比例关系,得到,每次删除的区间其实是38.2%,并且只用计算一次F

所以各方面都优于三分法。F的计算次数约为 log1.618 (n)

给出一个例子,在区间[0,1000000]中寻找最大值的过程:


方法三:二分法

由于区间只有上升和下降两种趋势,由F(n+1)-F(n)可判断n在极值点左侧还是右侧

所以只要二分求满足F(n+1)>F(n)的最大n,然后再+1就是答案。

每次删除一半,但每次要计算两个F,所以F的计算次数约为 2*log2(n),约为斐波那契优选法的1.4倍。

对于0到100000000的区间,三种方法的效率为:



若由F(n)推出F(n+1) 的时间相对于直接计算F(n)的时间可以忽略不计的话,二分法的F的执行次数就可以减半,这时二分法是最优的解。

如果不符合这个条件的话,还是斐波那契优选法最优,只是写起来比二分法麻烦一点。


最后附上代码:

int Count;
int F(int x){
	++Count;
	return 1000000000-abs(x-375832);
}
int Find(int Low,int Hi){
	int L=Low,M1,M2,R;//闭区间[L,L+R] ,L是绝对地址,M1,M2,R 是相对地址 
	int VL,V1,V2,VR;
	#define Func(x)  (x<Hi?F(x):F(Hi))
	M1=0;M2=1;R=1; 
	while(R <Hi-Low){int t=M2+R;M1=M2;M2=R;R=t;}
	Count=0;
	VL=Func(L);V1=Func(L+M1);V2=Func(L+M2);VR=Func(L+R);
	while(R>3){
		//printf("实际区间:%6d  %6d  %6d  %6d   相对区间:0 %6d %6d %6d\n",L,L+M1,L+M2,L+R,M1,M2,R);
		if(V1<V2){
			//L   F[n-2]   F[n-1]                         F[n] 
			//      L      F[n-3]   (新区间:F[n-2])      F[n-1]  
			L+=M1;R=M2;M1=M2-M1;M2=R-M1;
			VL=V1;V1=V2;V2=Func(L+M2);
		}
		else{
			//L              F[n-2]    F[n-1]    R
			//L   (F[n-3])   F[n-2]      R
			R=M2;M2=M1;M1=R-M2;
			VR=V2;V2=V1;V1=Func(L+M1);
		}
	}
	#undef Func
	//printf("\n斐波那契优选法:执行F函数%d次   ",Count);
	if(VL>=VR&&VL>=V1&&VL>=V2) return L;
	if(V1>=V2&&V1>=VR) return L+1;
	if(V2>=VR) return L+2;
	return L+3;
}
int BinaryFind(int Low,int Hi){//[Low,Hi]
	int L=Low,R=Hi,M;//左闭右开:[L,R) 求最大的x使得F[x+1]>F[x];
	Count=0;
	while(L+1<R){
		M=(L+R)>>1;
		if(F(M)<F(M+1)) L=M;
		else R=M;
	}
	int ANS=F(L+1)>F(L)?R:L;
	//printf("\n二分法:执行F函数%d次   ",Count);	
	return ANS;
}
int TriFind(int Low,int Hi){
	int L=Low,M1,M2,R=Hi;
	int V1,V2;
	Count=0;
	while(L +3 <R){
		M1=L+(R-L)/3;M2=M1+(R-L)/3;
		V1=F(M1);V2=F(M2);
		if(V1==V2) {L=M1;R=M2;}
		else if(V1<V2) L=M1;
		else R=M2;
	}
	int H[4];
	for(int i=0;i<4;++i) H[i]=F(L+i);
	//printf("\n三分法:执行F函数%d次   ",Count);	
	if(H[0]>=H[1]&&H[0]>=H[2]&&H[0]>=H[3]) return L;
	if(H[1]>=H[2]&&H[1]>=H[3]) return L+1;
	if(H[2]>H[3]) return L+2;
	return L+3;
}




评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值