7、查找

  • 分类:
  1. 顺序(线性)查找
  2. 二分查找/折半查找
  3. 插值查找
  4. 斐波那契查找

1 线性查找

  • 线性查找的数组可以是无序的
//线性查找
public class SequenceSearchTest {

    public static void main(String[] args) {
        int[] array={1,2,6,4,5,9};
        int value=6;
        int key=sequenceSearch(array,value);
        if(key!=-1){
            System.out.println(value+"位于数组的第"+(key+1)+"位置");
        }else {
            System.out.println("未找到该元素");
        }
    }

    private static int sequenceSearch(int[] array, int value) {
        for (int i = 0; i < array.length; i++) {
            if(array[i]==value){
                return i;
            }
        }
        return -1;
    }


}

2 二分查找

  • 二分查找必须确保数组本身是顺序的。

递归实现:

//二分查找
public class BinarySearch {
    public static void main(String[] args) {
        int[] array = {1, 2, 4, 5, 6, 9};
        int value = 6;
        int key = binarySearch(array, 0, array.length - 1, value);
        if (key != -1) {
            System.out.println(value + "位于数组的第" + (key + 1) + "位置");
        } else {
            System.out.println("未找到该元素");
        }
    }

    private static int binarySearch(int[] array, int left, int right, int value) {
        if (left > right) {
            return -1;
        }
        int mid = (left + right) / 2;
        if (value > array[mid]) {
            return binarySearch(array, mid + 1, right, value);
        } else if (value < array[mid]) {
            return binarySearch(array, left, mid - 1, value);
        } else {
            return mid;
        }
    }

}

3 插值查找

  • 插值查找相比二分查找可以说是更人性化,就好比你直接通过翻字典查单词,当你查abandon时,你用二分就是每次都在字典中间找,但显然,我们知道a开头的单词在字典很前面的部分,插值查找就是利用这种特性,所以插值查找就更像一个真人去查单词;
  • 对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找, 速度较快;
  • 关键字分布不均匀的情况下,该方法不一定比折半查找要好。
//插值查找
public class InterpolationSearchTest {
    public static void main(String[] args) {
        int[] array = new int[100];
        for (int i = 0; i < array.length; i++) {
            array[i] = i + 1;
        }
        int value = 1;
        int key = interpolationSearch(array, 0, array.length - 1, value);
        if(key!=-1){
            System.out.println(value+"位于数组的第"+(key+1)+"位置");
        }else {
            System.out.println("未找到该元素");
        }
    }

    private static int interpolationSearch(int[] array, int left, int right, int value) {
        System.out.println("查找中...");
        //这儿的判断是不可省的,否则可能会数组越界,因为 (value - array[left]) / (array[right] - array[left])不能大于或小于零
        if (value < array[0] || value > array[array.length - 1] || left > right) {
            return -1;
        }
        int mid = left + (right - left) * (value - array[left]) / (array[right] - array[left]);
        if (value > array[mid]) {
            return interpolationSearch(array, mid + 1, right, value);
        } else if (value < array[mid]) {
            return interpolationSearch(array, left, mid - 1, value);
        } else {
            return mid;
        }
    }


}

4 斐波那契查找

(也称黄金分割法)

  • 斐波那契搜索就是在二分查找的基础上根据斐波那契数列进行分割的。在斐波那契数列找一个等于略大于查找表中元素个数的数F[n],将原查找表扩展为长度为F[n] (如果要补充元素,则补充重复最后一个元素,直到满足F[n]个元素),完成后进行斐波那契分割,即F[n]个元素分割为前半部分F[n-1]个元素,后半部分F[n-2]个元素,找出要查找的元素在那一部分并递归,直到找到。
  • 在最坏情况下,斐波那契查找的时间复杂度还是O(log2n),且其期望复杂度也为O(log2n),但是与折半查找相比,斐波那契查找的优点是它只涉及加法和减法运算,而不用除法,而除法比加减法要占用更多的时间,因此,斐波那契查找的运行时间理论上比折半查找小,但是还是得视具体情况而定。
import java.util.Arrays;

public class FibonacciSearch {
    public static int maxSize = 20;

    public static void main(String[] args) {
        int[] arr = {1, 8, 10, 89, 1000, 1234};
        System.out.println("index=" + fibSearch(arr, 1));// 0
    }

    //因为后面我们mid=low+F(k-1)-1,需要使用到斐波那契数列,因此我们需要先获取到一个斐波那契数列
    //非递归方法得到一个斐波那契数列
    public static int[] fib() {
        int[] f = new int[maxSize];
        f[0] = 1;
        f[1] = 1;
        for (int i = 2; i < maxSize; i++) {
            f[i] = f[i - 1] + f[i - 2];
        }
        return f;
    }

    //编写斐波那契查找算法
    //使用非递归的方式编写算法

    /**
     * @param a   数组
     * @param key 我们需要查找的关键码(值)
     * @return 返回对应的下标,如果没有-1
     */
    public static int fibSearch(int[] a, int key) {
        int low = 0;
        int high = a.length - 1;
        int k = 0; //表示斐波那契分割数值的下标
        int mid = 0; //存放mid 值
        int f[] = fib(); //获取到斐波那契数列
        //获取到斐波那契分割数值的下标
        while (high > f[k] - 1) {
            k++;
        }
        //因为f[k] 值可能大于a 的长度,因此我们需要使用Arrays 类,构造一个新的数组,并指向temp[]
        //不足的部分会使用0 填充
        int[] temp = Arrays.copyOf(a, f[k]);
        //实际上需求使用a 数组最后的数填充temp
        //举例:
        //temp = {1,8, 10, 89, 1000, 1234, 0, 0} => {1,8, 10, 89, 1000, 1234, 1234, 1234,}
        for (int i = high + 1; i < temp.length; i++) {
            temp[i] = a[high];
        }
        // 使用while 来循环处理,找到我们的数key
        while (low <= high) { // 只要这个条件满足,就可以找
            mid = low + f[k - 1] - 1;
            if (key < temp[mid]) { //我们应该继续向数组的前面查找(左边)
                high = mid - 1;
                //为甚是k--
                //说明
                //1. 全部元素= 前面的元素+ 后边元素
                //2. f[k] = f[k-1] + f[k-2]
                //因为前面有f[k-1]个元素,所以可以继续拆分f[k-1] = f[k-2] + f[k-3]
                //即在f[k-1] 的前面继续查找k--
                //即下次循环mid = f[k-1-1]-1
                k--;
            } else if (key > temp[mid]) { // 我们应该继续向数组的后面查找(右边)
                low = mid + 1;
                //为什么是k -=2
                //说明
                //1. 全部元素= 前面的元素+ 后边元素
                //2. f[k] = f[k-1] + f[k-2]
                //3. 因为后面我们有f[k-2] 所以可以继续拆分f[k-1] = f[k-3] + f[k-4]
                //4. 即在f[k-2] 的前面进行查找k -=2
                //5. 即下次循环mid = f[k - 1 - 2] - 1
                k -= 2;
            } else { //找到
                //需要确定,返回的是哪个下标
                if (mid <= high) {
                    return mid;
                } else {
                    return high;
                }
            }
        }
        return -1;
    }
}