几个时间复杂度O(logN)的算法
1 二分查找算法
int BinarySearch(const ElementType A[], ElementType X, int N) { int mid, right, left; right = 0; left = N - 1; while(right <= left){ // 不断更新左右边界的索引(实质是每次循环将待查元素减半,实现了O(logN)时间复杂度),直到左索引大于右索引 mid = (right + left)/2; if(A[mid] > X) left = mid - 1; else if(A[mid] < X) right = mid + 1; else return mid; } return -1; }
二分查找算法适合:只需查找,不需要插入(O(N)复杂度?)和删除的情况。如查询元素周期表这种较稳定的数据。
2 欧几里德算法(求最大公因数)
1 unsigned int Gcd(unsigned int M, unsigned int N) 2 { 3 unsigned int Rem; 4 5 while(N > 0){ 6 Rem = M % N; 7 M = N; 8 N = Rem; 9 } 10 11 return M; 12 }
若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)。欧几里德算法的平均性能需要大量篇幅的精确计算,迭代次数的平均值约为(12ln2 lnN) / π^2+1.47。
3 求幂
最简单的递推公式:Xn = Xn-1 * X;
X0 = 1;
需要Θ(n)步和Θ(n)空间。
还可以: Xn = X n/2 * X n/2 = (X * X)n/2 X is even
Xn = X(n-1)/2 * X(n-1)/2 * X = (X * X)(n-1)/2 * X X is odd
long int Pow(long int X,unsigned int N) { if(N == 0) return 1;if(N % 2 == 1) return Pow(X * X, N/2); else return Pow(X * X, (N-1)/2) * X; }
需要Θ(logN)步和Θ(logN)空间。
还可以:Xn = X n/2 * X n/2 X is even
Xn = Xn-1 * X X is odd
1 long int Pow(long int X, unsigned int N) 2 { 3 if(N == 0) 4 return 1; 5 if(N % 2 == 0) 6 return Pow(X*X, N/2); 7 else 8 return Pow(X, N-1) * X; 9 }
需要Θ(logN)步和Θ(logN)空间。
在不影响正确性的前提下对代码进行微调是很有趣的。
如对第六行的修改:
1) return Pow(Pow(X,2), N/2);
看上去正确,但当N=2时,return Pow(Pow(X,2),1)调用Pow(X,2),Pow(X,2)继续调用Pow(X,2),每次递归没有向基准情况推进,无限循环直到崩溃。
2) return Pow(Pow(X, N/2), 2);
看上去正确,但当N = 2时, 调用Pow(X,2),再调用Pow(X,2)...,同(1).
3) return Pow(X, N/2) * Pow(X, N/2);
正确,但影响效率。
每次调用都有两次递归。则总步数为20 + 21 + 22 + ... 2logN = 2logN+1 -1 = 2N-1
则需O(N)步和O(N)空间。(待验证)
以及和上述算法相同的迭代算法。
long int Pow(long int X, unsigned N) { long int a = 1; while(N > 0){ if(N % 2 != 0) a *= X; N = N/2; X *= X; } return a; }
例如 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的值即我们要求的值。
通常,定义一个不变量,使它在状态间保持不变,这种技术是构建迭代算法的强有力的方法。