问题提出
Binary search with addition and subtraction (probleme@ldc.ro, 2004)[1]
You are given a sorted array A[1..N], and you want to search for a given element. But you may only use additions and subtractions, and O(1) memory. Can you obtain O(lgN) running time?
在给定的无重复升序序列中,查找某个具体元素,只能使用加法和减法以及常数级别的内存,能否提供O(lgN)的解法?
思考过程
先排除只能使用加法和减法的条件——观察二分查找
输入数据是一个升序序列,可以使用直观简单的二分查找,本质上就是不断将数组5、5对半切开。
int BinarySearch(int key,int[] a)
{
int lo = 0;
int hi = a.length -1;
int mid = 0;
while(lo<hi){
mid = lo+(hi-lo)/2; // 除法运算
if(key<a[mid]) hi = mid -1;
else if(key>a[mid]) lo = mid +1;
else return mid; //查找到返回元素坐标
}
return -1;//没有查到返回-1
}
再加入只能使用加法和减法的限制——斐波那契查找
斐波那契数列:0 1 1 2 3 5 8 13 21 34 55 89 144...
最新一项等于前两项之和,F(X)=F(X-1)+F(X-2)
随着项数不断增大,前一项与后一项的比值越来越逼近黄金分割0.618.[2]
可以想象成, 如果用斐波那契数列来分割数组,每一刀就是4、6开。
数值演示
先用一组精心准备的特殊数组来演示一下利用斐波那契数列查找的具体过程
int[] a = new int[22];
for(int i=0;i<22;i++) a[i] = i;
int max_value_of_a = a[a.length-1] = 21;
//int[] a = {0,1,2,3,4,5,6,7, 8, 9, 10,11,12,13,14,15,16,17,18,19,20,21};
//int[] F = {0,1,1,2,3,5,8,13,21,34,55,89,144,...};
// [ , ] Search Area
// Try to find number : 1
Start : 1 ?
Search:
max_value_of_a = F(8) = 21
[0,F(8)] = [0,F(6)+F(7)] = [0,8+13] 1 < F(6) =8
[0,F(6)] = [0,F(4)+F(5)] = [0,3+ 5] 1 < F(4) =3
[0,F(4)] = [0,F(2)+F(3)] = [0,1+ 1] 1 = F(2) =1 break;
End: 1 Yes.
// Try to find number : 20
Start :20 ?
Search:
max_value_of_a = F(8) = 21
[ 0,F(8)] = [ 0,F(6)+F(7)] = [ 0, 8+ 13] = [ 0,21]
20 > F(6)= 8 [ 0+F(6), 0+F(6)+F(7)] = [ 0 + 8, 0+ 8+13] = [ 8,21] = [8, 8+F(7)]=[ 8, 8+F(5)+F(6)]
20 > 8+F(5)= 8+5=13 [ 8+F(5), 8+F(5)+F(6)] = [ 8 + 5, 8+ 5+ 8] = [13,21] = [13,13+F(6)]=[13,13+F(4)+F(5)]
20 > 13+F(4)=13+3=16 [13+F(4),13+F(4)+F(5)] = [13 + 3,13+ 3+ 5] = [16,21] = [16,16+F(5)]=[16,16+F(3)+F(4)]
20 > 16+F(3)=16+2=18 [16+F(3),16+F(3)+F(4)] = [16 + 2,16+ 2+ 3] = [18,21] = [18,18+F(4)]=[18,18+F(2)+F(3)]
20 > 18+F(2)=18+1=19 [19+F(2),19+F(2)+F(3)] = [19 + 1,19+ 1+ 2] = [19,21] = [19,19+F(3)]=[19,19+F(1)+F(2)]
20 = 19+F(1)=19+1=20 break;
End: 20 Yes.
//Try to find number: 4
Start: 4?
Search:
max_value_of_a = F(8) = 21
[0,F(8)] = [0,F(6)+F(7)] = [0, 8+13] 4 < F(6) =8
[0,F(6)] = [0,F(4)+F(5)] = [0, 3+ 5] 4 > F(4) =3
[0+3,0+3+5] = [3,3+5] = [3,3+F(3)+F(4)] 4 < 3+F(3) =5
[3,3+F(4)] = [3,3+1+2] = [3,3+F(2)+F(3)] 4 = 3+F(2) =4 break;
End: 4 Yes.
代码排版可能错位辅助以下图片
查找数值1 一路朝着数组左边(更小的一侧)切过去
查找数值2 一路朝着数组右边(更大的一侧)切过去
查找数值3 先左再右
演示说明
算法本质
- 斐波那契数列
F(x)
的值在运算时承担的是数组下标的职责; - 每一个斐波那契数列中的数其实本质上最终都可以拆成若干个
F(0)、F(1)
的组合,这意味着利用这些数是可以遍历整个数组的下标(算法本质)
可行性
二分既可以理解成不断对分,也可以理解成不对半,只要切下去就可以了,最终只会剩下一个(步骤始终是有穷的);
每一个斐波那契数列元素都是前两个项的和,保证了只要按照这个规则的出来的数就一定可以分成更小的斐波那契数(循环可持续);
- 最后剩下的那个如果满足查找值就可以得到结果,否则就查找该数(循环终止条件)
算法原理
F(k) = F(k-1)+F(k-2)
//斐波那契数列的规则
AT FIRST Arrays [index , index+F(k) ] = [index,index+F(k-2)+F(k-1)]
//总是可以拆成前两个
WHILE F(k-2) NOT EQUALS ZERO // 切至最后一块
IF KEY < Arrays [F(k-2)] THEN
SEARCH Arrays [index , index+F(k-2)]
END IF
ELSE IF KEY > Arrays [F(k-2)] THEN
SEARCH Arrays [index +F(k-2) , index+F(k-2)+F(k-1)]
INDEX index = index +F(k-2)
ELSE KEY == Arrays [F(k-2)] THEN
RETURN "KEY FOUND MESSAGE";
END IF
END
完整源码
public class FibonacciSearch
{
public static void main(String args[]){
int[] t = {0,1,2,3,4,5,6,7,8,9,10};
int indexi = CreateFindex(t);
int[] a = CreateArray(indexi,t);
for(int x:a) System.out.print(" "+x+" ");
// Search number
for(int i=0;i<a.length;i++){
int key = i-1;
System.out.println("\n---- Try to find : "+ key+"----\n");
if(hasNum(key,indexi,a)>-1)
System.out.print(" < "+key+" > yes. \n");
else
System.out.print(" < "+key+" > no. \n");
}
}
public static int F(int x){
int[] f = new int[30];
for(int i=2;i<f.length;i++){
//f[0] = 1;
f[0] = 0;
f[1] = 1;
f[i] = f[i-1] + f[i-2];
}
return f[x];
}
// Create new array
public static int[] CreateArray(int indexi,int[] a){
int[] newa = new int[F(indexi)+1];
int N = a.length;
for(int x=0;x<newa.length;x++){
if(x<N)
newa[x] = a[x];
else
newa[x] = a[N-1];
}
return newa;
}
public static int CreateFindex(int[] a){
int N = a.length;
int indexi = 0;
for(int i=0;true;i++)
{
if(F(i) >=(N-1) ){
indexi = i;
break;
}
}
return indexi;
}
// Binary search with only addition and substration
public static int hasNum(int key,int k,int[] nums){
int c = F(k);
int b = F(k-1);
int a = c - b;
int index = 0;
while(a>0){
//System.out.println("Time : "+k);
if(key<(nums[index+a])) {
System.out.println(" <<< Dn ["+index+","+(index+a)+"]");
b = b - a;
c = a;
a = c - b;
if(b==0){
a = 0;
if(nums[index+a]==key) return key;
}
}
else if(key>(nums[index+a])){
System.out.println(" >>> Up ["+(index+a)+","+(index+c)+"]");
index = index + a;
c = b;
b = a;
a = c - b;
}
else {
System.out.println("Bingo ["+(index+a)+"]"+" Found : "+key);
return key;
}
}
return -1;
}
}
/*
BUG REPORT
zero found fixed.
*/
运行结果
Compiling FibonacciSearch.java.......
-----------OUTPUT-----------
0 1 2 3 4 5 6 7 8 9 10 10 10 10
---- Try to find : -1----
<<< Dn [0,5]
<<< Dn [0,2]
<<< Dn [0,1]
< -1 > no.
---- Try to find : 0----
<<< Dn [0,5]
<<< Dn [0,2]
<<< Dn [0,1]
< 0 > yes.
---- Try to find : 1----
<<< Dn [0,5]
<<< Dn [0,2]
Bingo [1] Found : 1
< 1 > yes.
---- Try to find : 2----
<<< Dn [0,5]
Bingo [2] Found : 2
< 2 > yes.
---- Try to find : 3----
<<< Dn [0,5]
>>> Up [2,5]
Bingo [3] Found : 3
< 3 > yes.
---- Try to find : 4----
<<< Dn [0,5]
>>> Up [2,5]
>>> Up [3,5]
Bingo [4] Found : 4
< 4 > yes.
---- Try to find : 5----
Bingo [5] Found : 5
< 5 > yes.
---- Try to find : 6----
>>> Up [5,13]
<<< Dn [5,8]
Bingo [6] Found : 6
< 6 > yes.
---- Try to find : 7----
>>> Up [5,13]
<<< Dn [5,8]
>>> Up [6,8]
Bingo [7] Found : 7
< 7 > yes.
---- Try to find : 8----
>>> Up [5,13]
Bingo [8] Found : 8
< 8 > yes.
---- Try to find : 9----
>>> Up [5,13]
>>> Up [8,13]
<<< Dn [8,10]
Bingo [9] Found : 9
< 9 > yes.
---- Try to find : 10----
>>> Up [5,13]
>>> Up [8,13]
Bingo [10] Found : 10
< 10 > yes.
---- Try to find : 11----
>>> Up [5,13]
>>> Up [8,13]
>>> Up [10,13]
>>> Up [11,13]
>>> Up [12,13]
< 11 > no.
---- Try to find : 12----
>>> Up [5,13]
>>> Up [8,13]
>>> Up [10,13]
>>> Up [11,13]
>>> Up [12,13]
< 12 > no.
[Finished in 1.0s]
代码说明
- 诸如前面所述,正因为F(x)承担的是数组下标的角色,那么我假定我的程序里面F(x)是一个有效小标;
- 可是,斐波那契数列中的数值并不一定就恰恰等于我输入数组的最大下标(N-1);
- 然而无非两种情况,要么最大下标恰恰等于某个斐波那契数,这就很好办了,直接开始算;
- 更多的情况应该是最大下标恰恰小于某个斐波那契数f,也很好办,只要把重声明一个数组并且把长度补到那个斐波那契数f就可以了;
- 至于补充什么数,我按照我输入数据是升序的规律,为了方便一律填充了数组的最大值;
- 因为
0 1 1
这头三个序列数字比较特殊,再下降到差不多F(0)
的时候,用一个if语句
防止找不到Array[0]元素的BUG:
if(b==0){
a = 0;
if(nums[index+a]==key) return key;
}
代码心得
从题干里F是什么都看不懂到总算把这个谜题硬解出了,虽然代码还是很糙但是好开心啊!
但是,我感觉吧,包括各种准备功夫,都不是O(lgN)
可以解决的问题了(´・ω・`)。。。。
目前为止,我编程是 1成实现、1成命名注释、2成算法设计、2成用例设计、2成调试、1成可复用性Git管理,剩下1成自我陶醉,还有的九万九千九八九十九成以后再说吧w
引用出处
[1] Puzzles Binary search with addition and subtraction (probleme@ldc.ro, 2004)(Mihai Patrascu)
http://people.csail.mit.edu/mip/probs.html
[2] 百度百科斐波那契数列