欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

斐波拉契查找法的原理及实现

程序员文章站 2022-06-03 18:29:02
...

斐波拉契查找法与二分法和插值查找法大致类似,后两者在寻找mid值的过程中都用到了除法,特别是在插值查找法中寻找mid值同时用到了乘除法,与加减法相比,乘除法无疑更消耗时间,因此,斐波拉契查找法利用斐波拉契数列去寻找mid值,整个过程巧妙的避开了使用乘除法,是一种空间换时间的方法。

0 1 2 3 4 5 6 7 8 9 10
1 1 2 3 5 8 13 21 34 55 89

如上所示,斐波拉契数列的一个特点就是从第2(0为第一个数)个数开始,每一个数都是前面两个数的和(这刚好满足了二分查找中,我们想把数据分为左右两部分的需求)。
一个斐波拉契数组,数组名fibonacciArr,则满足:
fibonacciArr[n] = fibonacciArr[n-1] + fibonacciArr[n-2] (n>=2) ;
因此我们可以将待查找的数组arr的长度arr.length扩展到斐波拉契数组中的一个合适的值fibonacciArr[k] (k 满足:fibonacciArr[k] >=arr.length && fibonacciArr[k-1] <arr.length) 。这样在二分数组时,就可以根据斐波拉契数列里的查到的值去二分(这里就是空间换时间)。比如现在arr的长度被扩展到了fibonacciArr[k]的大小,扩展后的数组记为arr1,我们斐波拉契数组里查询到 fibonacciArr[k-1] 及 fibonacciArr[k-2] 的值,就可以将arr1分为左段( 长度为:fibonacciArr[k-1] )和右段( 长度为:fibonacciArr[k-2] )。
以上就是斐波拉契查找法的思想。

由fibonacciArr[n] = fibonacciArr[n-1] + fibonacciArr[n-2] 可以得到:
fibonacciArr[n] - 1 = fibonacciArr[n-1] - 1 + fibonacciArr[n-2] - 1 + 1
即把斐波拉契数组里面的每个数都减1,那么就能空出一个位置留给mid

斐波拉契查找法的原理及实现
如上图所示,为了便于代码实现,将扩展后的数组arr1根据斐波拉契数组分成了三个部分,然后采用递归的方法(for循环一样)就能实现斐波拉契查找法。

java代码实现:

import java.util.Arrays;
import java.util.List;
import java.util.ArrayList;

/**
 * 斐波那契查找
 * 
 * 由于在计算mid时使用的是斐波拉契数列,所以没有用到除法。整个查找过程只用到了加减法
 * 相比于二分法,没有乘法和除法,所以统计上说会更快
 * @author MSI
 *
 */
public class FibonacciSearch {

	public static void main(String[] args) {
		int[] arr = {0,1,2,2,2,5,6,7,7,7,7};
		System.out.println(fibonacciSort(arr,7));
	}

	
	/**
	 * 产生斐波那契数列
	 * 
	 * @param maxSize 需要的最大长度
	 * @return
	 */
	public static int[] fibonacciArray(int maxSize) {
		int[] arr = new int[maxSize];
		arr[0] = arr[1] = 1;
		for (int i = 2; i < arr.length; i++) {
			arr[i] = arr[i - 1] + arr[i - 2];
		}
		return arr;
	}
	/**
	 * 在这里面调用递归函数
	 * @param arr
	 * @param num
	 * @return
	 */
	public static List<Integer> fibonacciSort(int[] arr, int num) {
		//现在斐波那契数列中找到一个最接近于且大于arr长度的斐波那契数
		int[] fibArr = fibonacciArray(20);   //这里的20应该是根据数组的长度动态变化的
		int k = 0;
		//F(n)-1 = F(n-1)-1 + F(n-2)-1 + 1
		//左段:left ~ left + F(n-1) - 2
		//中间值: left + F(n-1) - 1
		//右段:left + F(n-1) ~ right
		for (int i = 2; i < fibArr.length; i++) {     //斐波那契数列前两个都是1,直接从非1的数开始寻找
			if(fibArr[i] - 1 >= arr.length && fibArr[i - 1] - 1 < arr.length) {
				k = i;
				break;
			}
		}
		//将原数组填充到相应斐波那契数的大小
		int[] arr1 = Arrays.copyOf(arr, fibArr[k] - 1);
		for(int j = arr.length; j < arr1.length; j++) {
			arr1[j] = arr[arr.length - 1];
		}
		//调用递归函数
		return recursionFuc(arr1, fibArr, arr.length, k, 0, arr1.length - 1, num);
	}
	/**
	 * 递归函数
	 * @param arr1             补充后的数组
	 * @param fiboarr    斐波拉契数组
	 * @param arrLen     原数组长度
	 * @param k          索引到斐波那契数组第几个值了
	 * @param left       该段左边界限
	 * @param right      该段右边界限
	 * @param num        要找的值
	 * @return
	 */
	public static List<Integer> recursionFuc(int[] arr1, int[] fiboarr, int arrLen, int k, int left, int right, int num) {
		//F(n)-1 = F(n-1)-1 + F(n-2)-1 + 1
		//left:
		if(left > right) {
			return new ArrayList<Integer>();
		}
		
		int mid = left + fiboarr[k-1] - 1;  //中间位置
		if(num == arr1[mid] && mid < arrLen) {
			List<Integer> list = new ArrayList<Integer>();
			list.add(mid);
			
			int mid_l = mid-1;
			int mid_r = mid+1;
			while(mid_l >= 0 && arr1[mid_l] == num) {    //向左查找,是否还存在目标数据
				list.add(mid_l);
				mid_l--;
			}
			while(mid_r < arrLen && arr1[mid_r] == num) {  //向右查找,是否还存在目标数据
				list.add(mid_r);
				mid_r++;
			}
			return list;
		}else if (num > arr1[mid]) {  //右
			return recursionFuc(arr1,fiboarr, arrLen, k-2, mid + 1,right, num);
		}else{
			return recursionFuc(arr1,fiboarr, arrLen, k-1, left, mid-1, num);
		}
	}

}

执行结果:
斐波拉契查找法的原理及实现

相关标签: 数据结构 java