几个时间复杂度O(logN)的算法
2014-02-26 09:58
302 查看
1二分查找算法
二分查找算法适合:只需查找,不需要插入(O(N)复杂度?)和删除的情况。如查询元素周期表这种较稳定的数据。
2欧几里德算法(求最大公因数)
若M>N,则第一次循环交换M和N。
若想分析其时间复杂度,则要求循环次数,即生成余数的次数。
可以证明:当M>N,则M%N<M/2
证明:当N<=M/2时,M%N<M/2
当N>M/2时,M-N<M/2,那么也有M%N<M/2.
结论成立。
由此可得:有M、N(M>N)两个正整数,第一次循环后其余数小于M/2,第二次循环后其余数小于N/2,所以可以说,每两次循环后,余数最多为原值的一半。
所以最大的求余次数为2logN=O(logN).
2logN并不精确,即使在最坏的情况(如M、N是相邻的fibonacci数)下,仍然可被优化为1.44logN(每次M和余数的商为1.618,则求余的次数为N关于1.618的对数,即1.44logN)。欧几里德算法的平均性能需要大量篇幅的精确计算,迭代次数的平均值约为(12ln2lnN)/π^2+1.47。
3求幂
最简单的递推公式:Xn=Xn-1*X;
X0=1;
需要Θ(n)步和Θ(n)空间。
还可以:Xn=Xn/2*Xn/2=(X*X)n/2Xiseven
Xn=X(n-1)/2*X(n-1)/2*X=(X*X)(n-1)/2*XXisodd
需要Θ(logN)步和Θ(logN)空间。
还可以:Xn=Xn/2*Xn/2Xiseven
Xn=Xn-1*XXisodd
需要Θ(logN)步和Θ(logN)空间。
在不影响正确性的前提下对代码进行微调是很有趣的。
如对第六行的修改:
1)returnPow(Pow(X,2),N/2);
看上去正确,但当N=2时,returnPow(Pow(X,2),1)调用Pow(X,2),Pow(X,2)继续调用Pow(X,2),每次递归没有向基准情况推进,无限循环直到崩溃。
2)returnPow(Pow(X,N/2),2);
看上去正确,但当N=2时,调用Pow(X,2),再调用Pow(X,2)...,同(1).
3)returnPow(X,N/2)*Pow(X,N/2);
正确,但影响效率。
每次调用都有两次递归。则总步数为20+21+22+...2logN=2logN+1-1=2N-1
则需O(N)步和O(N)空间。(待验证)
以及和上述算法相同的迭代算法。
例如X23=X*(X*X)11=X*X2*(X2*X2)5=X*X2*X4*(X4*X4)2=X*X2*X4*(X8*X8)1=X*X2*X4*X16=X23
该程序中定义了一个变量a,整个程序中保证a*XN不变,则当N=0时,a*XN=a*X0=a,说明这是a的值即我们要求的值。
通常,定义一个不变量,使它在状态间保持不变,这种技术是构建迭代算法的强有力的方法。
intBinarySearch(constElementTypeA[],ElementTypeX,intN) { intmid,right,left; right=0; left=N-1; while(right<=left){//不断更新左右边界的索引(实质是每次循环将待查元素减半,实现了O(logN)时间复杂度),直到左索引大于右索引 mid=(right+left)/2; if(A[mid]>X) left=mid-1; elseif(A[mid]<X) right=mid+1; else returnmid; } return-1; }
二分查找算法适合:只需查找,不需要插入(O(N)复杂度?)和删除的情况。如查询元素周期表这种较稳定的数据。
2欧几里德算法(求最大公因数)
unsignedintGcd(unsignedintM,unsignedintN) { unsignedintRem; while(N>0){ Rem=M%N; M=N; N=Rem; } returnM; }
若M>N,则第一次循环交换M和N。
若想分析其时间复杂度,则要求循环次数,即生成余数的次数。
可以证明:当M>N,则M%N<M/2
证明:当N<=M/2时,M%N<M/2
当N>M/2时,M-N<M/2,那么也有M%N<M/2.
结论成立。
由此可得:有M、N(M>N)两个正整数,第一次循环后其余数小于M/2,第二次循环后其余数小于N/2,所以可以说,每两次循环后,余数最多为原值的一半。
所以最大的求余次数为2logN=O(logN).
2logN并不精确,即使在最坏的情况(如M、N是相邻的fibonacci数)下,仍然可被优化为1.44logN(每次M和余数的商为1.618,则求余的次数为N关于1.618的对数,即1.44logN)。欧几里德算法的平均性能需要大量篇幅的精确计算,迭代次数的平均值约为(12ln2lnN)/π^2+1.47。
3求幂
最简单的递推公式:Xn=Xn-1*X;
X0=1;
需要Θ(n)步和Θ(n)空间。
还可以:Xn=Xn/2*Xn/2=(X*X)n/2Xiseven
Xn=X(n-1)/2*X(n-1)/2*X=(X*X)(n-1)/2*XXisodd
longintPow(longintX,unsignedintN) { if(N==0) return1;if(N%2==1) returnPow(X*X,N/2); else returnPow(X*X,(N-1)/2)*X; }
需要Θ(logN)步和Θ(logN)空间。
还可以:Xn=Xn/2*Xn/2Xiseven
Xn=Xn-1*XXisodd
longintPow(longintX,unsignedintN)
{
if(N==0)
return1;
if(N%2==0)
returnPow(X*X,N/2);
else
returnPow(X,N-1)*X;
}
需要Θ(logN)步和Θ(logN)空间。
在不影响正确性的前提下对代码进行微调是很有趣的。
如对第六行的修改:
1)returnPow(Pow(X,2),N/2);
看上去正确,但当N=2时,returnPow(Pow(X,2),1)调用Pow(X,2),Pow(X,2)继续调用Pow(X,2),每次递归没有向基准情况推进,无限循环直到崩溃。
2)returnPow(Pow(X,N/2),2);
看上去正确,但当N=2时,调用Pow(X,2),再调用Pow(X,2)...,同(1).
3)returnPow(X,N/2)*Pow(X,N/2);
正确,但影响效率。
每次调用都有两次递归。则总步数为20+21+22+...2logN=2logN+1-1=2N-1
则需O(N)步和O(N)空间。(待验证)
以及和上述算法相同的迭代算法。
longintPow(longintX,unsignedN)
{
longinta=1;
while(N>0){
if(N%2!=0)
a*=X;
N=N/2;
X*=X;
}
returna;
}
例如X23=X*(X*X)11=X*X2*(X2*X2)5=X*X2*X4*(X4*X4)2=X*X2*X4*(X8*X8)1=X*X2*X4*X16=X23
该程序中定义了一个变量a,整个程序中保证a*XN不变,则当N=0时,a*XN=a*X0=a,说明这是a的值即我们要求的值。
通常,定义一个不变量,使它在状态间保持不变,这种技术是构建迭代算法的强有力的方法。
相关文章推荐
- 上班第一年的感悟与学习计划
- 电脑主机箱前置耳机没声音(window7)
- 利用组策略来关闭win7驱动的自动更新
- java获取服务器一些信息的方法
- 启动框架 org.apache.catalina.startup(转)
- 问题:NavigationBar问题
- OC -- @interface和@property两种声明变量方式的区别
- 11个强大的Visual Studio调试小技巧
- 把多个JavaScript函数绑定到onload事件处理函数上
- hibernate批量删除
- ios--安全攻防02--后台daemon非法窃取用户iTunesstore信息
- 设置正确的post数据格式
- Epplus 操作Excel 2007/2010
- java.io输入流怎么转成输出流
- CentOS 6.5编译安装MySQL 5.6.16
- SQLServer建立架构(模式/目录)_建立表_建立视图
- ios--安全攻防01--Hack必备的命令与工具
- 如何设置win7 锁屏不断网
- Android JNI知识简介
- 解决android-support-v4.jar引用外部项目冲突问题