减常因子算法
折半查找
对于有序数组的查找来说,折半查找是一种性能卓越的算法。它通过比较查找键K和数组中间元素A[m来完成查找工作。如果它们相等,算法结束。否则,如果K<A[m],就对数组的前半分执行该操作,如果K>A[m],则对数组的后半部分执行该操作。虽然折半查找很明显是基于递归的思想,它也可以很容易地以非递归算法的形式实现。
伪代码
BinarySearch(A[0..n-1],K)
//实现非递归的折半查找
//输入:一个升序数组A[0..n-1]和一个查找键K
//输出:一个数组元素的下标,该元素等于K;如果没有这样一个元素,则返回-1
l<-0;r<-n-1
while l <= r do
m < floor((1+r)/2)
if K = A[m] return m
else if K < A[m] r = m-1
else l <- m+1
Java代码实现
package com.算法.减治法;
/**
* @Author Lanh
**/
public class 折半查找 {
public static void main(String[] args) {
int[] arr = {1,2,3,4,5,6,7};
System.out.println("7:"+binarySearch(arr,7));
System.out.println("1:"+binarySearch(arr,1));
System.out.println("2:"+binarySearch(arr,2));
}
public static int binarySearch(int[] arr,int K){
int l = 0;
int r = arr.length;
while (l<=r){
int m = (l+r)>>1;
if (K==arr[m]) return m;
else if (K<arr[m]) r = m-1;
else l = m+1;
}
return -1;
}
}
测试
测试用例:{1,2,3,4,5,6,7};
测试结果如下:
假币问题
在识别假币问题的多种版本中,我们考虑最能够体现出减常因子策略的那个版本。在枚外观相同的硬币中,有一枚是假币。在一架天平上,我们可以比较任意两组硬币。也就是说,通过观察天平是向右倾、向左倾还是停在当中,我们可以判断出两组硬币重量是否相同,或者哪一组比另一组更重,但我们不知道重多少。我们的问题是,要求设计一个高效的算法来检测出这枚假币。该问题的一个较简单的版本(就是我们这里所讨论的)假设假币相对真币较轻还是较重是已知的,即假币较轻。
解决这个简化版假币问题的最直接的就是一枚一枚地称,再高效点就是把n枚使币分成两堆,每堆有floor(n/2)枚馊币。如果为奇数,就留下一枚额外的硬币,然后把两堆硬币放在天平上。如果两堆硬币重量相同,那么放在旁边的硬币就是假币:否则我们可以用同样的方式对较轻的一堆硬币进行处理,这堆硬币中一定包含那枚假币。时间复杂度为
log
2
n
\log_2n
log2n
目前为止,这些内容看上去都很初级,有人甚至会觉得无聊。但请注意:这里有意思的地方在于,实际上,该算法并不是最高效的解法。如果不是把硬币分成两堆,而是分成三堆,每堆n/3个硬币,将会更好。因为,第一第二堆比较重量,相等则假币在第三堆,否则假币在第一第二堆。时间复杂度将被优化到
log
3
n
\log_3n
log3n
Java代码实现
int[] arr1 = {3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6};
System.out.println(findFalseCoin(arr1));
int[] arr2 = {6,6,6,6,6,6,3,6,6,6,6,6,6,6,6,6,6,6,6,6};
System.out.println(findFalseCoin(arr2));
int[] arr3 = {6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,3};
System.out.println(findFalseCoin(arr3));
测试结果
测试用例:
{3,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6}
{6,6,6,6,6,6,3,6,6,6,6,6,6,6,6,6,6,6,6,6}
{6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,6,3}
测试结果如下:
俄式乘法
现在我们来考虑对两个正整数相乘的非主流算法,它被称为俄式乘法,或者俄国农夫法。假设n和m是两个正整数,我们要计算它们的乘积,同时,我们用n的值作为实例规模的度量标准。这样,如果n是偶数,一个规模为原来一半的实例必须要对2进行处理,对于该问题较大实例的解和较小实例的解的关系有一个显而易见的公式:
n
∗
m
=
n
2
∗
2
m
n*m=\frac {n}{2}*2m
n∗m=2n∗2m
如果n是奇数,我们只需要对该公式做轻微的调整
n
∗
m
=
n
−
1
2
∗
2
m
+
m
n*m=\frac {n-1}{2}*2m+m
n∗m=2n−1∗2m+m
通过应用这个公式,并以1×m=m作为算法停止的条件,我们既可以用递归也可以用迭代来计算n×m的乘积。
大家还应该注意,该算法只包括折半、加倍和相加这几个简单的操作,这对那些不想背诵九九乘法表的人来说可能是一个有吸引力的特性。很可能就是该算法的这个特性,使得它对俄国农夫非常有吸引力。据西方的游客说,在19世纪,俄国农夫很广泛地在使用这个算法,这个算法的名字由此而来。实际上,埃及数学家早在公元前1650年就使用了这个算法的思想。这个算法也使得硬件实现的速度非常快,因为使用移位就可以完成二进制数的折半和加倍,在机器层次上,这些都属于最基本的操作。
Java代码实现
package com.算法.减治法;
/**
* @Author Lanh
**/
public class 俄式乘法 {
public static void main(String[] args) {
System.out.println(prod(50,65));
}
public static int prod(int n,int m){
int extra = 0;
while (n>1){
if (n%2==1) {
extra += m;
}
n = n>>1;
m = m<<1;
}
return m + extra;
}
}
测试用例
测试用例:50*65
测试结果如下: