斐波那契查找

二分查找
插值查找
斐波那契查找
树表查找
哈希查找
分块查找

斐波那契数列 [1, 1, 2, 3, 5, 8, 13, 21, 34, 55 ] ,前后的比值会越来越接近 0.618 ,也就是黄金分割点。0.618也被公认为最具有审美意义的比例数字。斐波那契查找原理其实和二分查找原理差不多,只不过计算中间值 mid 的方式不同,还有一点就是斐波那契查找的数组长度必须是 f(k) – 1 ,这样就可以划分斐波那契数列:

f(k) - 1 = f(k - 1) + f(k - 2) - 1= (f(k - 1) - 1) + 1 + (f(k - 2) - 1);

如下图所示:

然后前面部分和后面部分都还可以继续划分,但实际上要查找的数组长度不可能都是 f(k) – 1,所以要补齐最后的部分,让数组的长度等于 f(k) – 1,让数组的最后一位数字把后面铺满。比如查找的数组长度是 21 ,而 f(8) – 1 = 21 – 1 = 20;小于 21 ,所以 f(8) – 1 是不行的,需要把数组长度变为 f(9) – 1 = 34 – 1 = 33,后面多余的用原数组最后的那个值填充,来看下代码。

public int search(int[] nums, int target) {
    int length = nums.length;
    int k = 0;// 根据k计算待查找的数组长度。
    while (length > f(k) - 1 || f(k) - 1 < 5)
        k++;
    // 斐波那契数列
    int[] fb = makeFbArray(f(k) - 1);
    // 生成新的数组。
    int[] tmp = Arrays.copyOf(nums, fb[k] - 1);
    for (int i = length; i < tmp.length; i++)
        tmp[i] = nums[length - 1];//用原数组最后的值填充。

    int left = 0;
    int right = length - 1;
    while (left <= right) {
        int mid = left + fb[k - 1] - 1; // 划分
        if (tmp[mid] == target) {
            // tmp 的后面是拼凑的,如果原数组长度很小,mid可能会超过原数组的长度,需要判断下。
            if (mid <= right) {
                return mid;
            } else {
                return right;
            }
        } else if (tmp[mid] > target) {//要查找的值在前面部分。
            right = mid - 1;
            k = k - 1;// 根据k计算下次查找的数组长度。
        } else {//要查找的值在后面部分。
            left = mid + 1;
            k = k - 2;// 根据k计算下次查找的数组长度。
        }
    }
    return -1;
}

private int f(int n) {
    if (n == 0 || n == 1)
        return n;
    return f(n - 1) + f(n - 2);
}

public int[] makeFbArray(int length) {
    int[] nums = new int[length];
    nums[0] = 1;
    nums[1] = 1;
    for (int i = 2; i < length; i++)
        nums[i] = nums[i - 1] + nums[i - 2];
    return nums;
}

实际上数组后面也可以不用填充,如果我们获取的元素下标超出数组的范围,直接返回数组的最后一个元素即可。

public int search(int[] nums, int target) {
    int length = nums.length;
    int k = 0;// 根据k计算待查找的数组长度。
    while (length > f(k) - 1 || f(k) - 1 < 5)
        k++;
    // 斐波那契数列
    int[] fb = makeFbArray(f(k) - 1);
    int left = 0;
    int right = length - 1;
    while (left <= right) {
        int mid = left + fb[k - 1] - 1; // 划分
        if (numVal(nums, mid) == target) {
            // tmp 的后面是拼凑的,如果原数组长度很小,mid可能会超过原数组的长度,需要判断下。
            if (mid <= right) {
                return mid;
            } else {
                return right;
            }
        } else if (numVal(nums, mid) > target) {//要查找的值在前面部分。
            right = mid - 1;
            k = k - 1;// 根据k计算下次查找的数组长度。
        } else {//要查找的值在后面部分。
            left = mid + 1;
            k = k - 2;// 根据k计算下次查找的数组长度。
        }
    }
    return -1;
}

private int numVal(int[] nums, int index) {
    if (index < nums.length)
        return nums[index];
    return nums[nums.length - 1];
}

private int f(int n) {
    if (n == 0 || n == 1)
        return n;
    return f(n - 1) + f(n - 2);
}

public int[] makeFbArray(int length) {
    int[] nums = new int[length];
    nums[0] = 1;
    nums[1] = 1;
    for (int i = 2; i < length; i++)
        nums[i] = nums[i - 1] + nums[i - 2];
    return nums;
}

信奥赛编程(刷题请进)>>>

经过两年的打磨,我的新作《算法秘籍》已经出版,有需要的可以点击购买。也可以点击 内容介绍 查看详情。