【查找】四种查找算法

顺序查找

顺序查找(Sequential Search)又叫线性查找,是最简单直接暴力的查找方法。就是从头至尾挨个寻找,找到位置。当遍历完没找到就说明要找的数据不存在。它不要求数据有序。该算法的时间复杂度为O(n)。

public static int seqSearch(int[] arr, int value) {
    for(int index = 0; index < arr.length; index++) {
        if(arr[index] == value) {
	    return index;
	}
    }

    return -1;
}

二分查找(折半查找)

自我感觉是相对用的比较广泛的,效率也挺高的。不过它要求数据是有序的。在有序的情况下,一半一半地找。先与中间值对比,判断要找的值是在前半部分还是后半部分。然后就根据判断结果锁定下一次查找区间,这相当于一次缩小一半的查找量。

 

public static int binarySearch(int[] arr, int value) {
    int low = 0;
    int high = arr.length - 1;
		
    if(value < arr[low] || value > arr[high]) {
	return -1;
    }
		
    int v;
    int mid = 0;
    while(low <= high) {
        mid = low + (high - low) / 2;
	if((v = arr[mid]) < value) {
	    //继续从右边查
	    low = mid + 1;
	}else if(v == value) {
	    return mid;
	}else {
	    high = mid - 1;
        }
    }

    return -1;
}

以查找数组中20为例。流程跟踪:

 

插值查找

基于二分查找算法,将查找点的选择改进为自适应选择,可以提高查找效率。差值查找也需要数据有序。二分查找是每次都选择一个区间的中间来缩小查找范围,不论数据如何都是从中间劈开。而差值查找是一种自适应地选择分割区间值。两种思路对比:

对于数据量较大,关键字分布比较均匀的查找表来说,采用插值查找,速度较快。关键字分布不均匀的情况下,该方法不一定比折半查找要好。 没有更好更坏,只有合适。

public static int insertValueSearch(int[] arr, int value) {
    int low = 0;
    int high = arr.length - 1;
		
    /*
     * 必须判断。针对公式mid = low + (high - low) * (value - arr[low])/(arr[high] - arr[low]);
     * value如果为无穷大,那么mid很可能为无穷大。会下标越界。
     */
    if(value < arr[low] || value > arr[high]) {
        return -1;
    }
		
    int v;
    while(low <= high) {
        int mid = low + (high - low) * (value - arr[low])/(arr[high] - arr[low]);
	if((v = arr[mid]) < value) {
	    //继续从右边查
	    low = mid + 1;
	}else if(v == value) {
	    return mid;
	}else {
	    high = mid - 1;
	}
    }
    
    return -1;
}

 下面查找分布均匀和不均匀为例测试,查找次数:

public static void main(String[] args) {
    int[] arr2 = {1,2,3,4,5,6,7,8,9,11,20,88};
    int[] arr3 = new int[100];
    for(int index = 0; index < 100; index ++) {
	arr3[index] = index * 2;
    }
    System.out.println();
    int flag = insertValueSearch(arr2, 20);
    System.out.println(flag == -1 ? "没有这个数" : "找到了 下标为:" + flag);
		
    flag = insertValueSearch(arr3, 20);
    System.out.println(flag == -1 ? "没有这个数" : "找到了 下标为:" + flag);
}

斐波那契查找

黄金分割:

黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之.比。取其前三位数字的近似值是0.618。由于按此比例设计的造型十分美丽,因此称为黄金分割。这是一个神奇的数字,会带来意向不到的效果。
 

斐波那契数列{1,1,2,3,5,,8,13, 21, 34, 55}可以发现斐波那契数列的两个相邻数的比例,无限接近黄金分割值0.618

斐波那契查找原理与前两种相似,仅仅改变了中间结点(mid) 的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1(F代表斐波那契数列,  k代表的是斐波那契数列的第k个元素),如下图所示 

对F(k-1)-1的理解:

  1. 由斐波那契数列 F[k]=F[k- 1]+F[k-2]的性质,可以得到(F[k]-1) = (F[k-1]-1) + (F[k-2]-1) +1。该式说明:只要顺序表的长度为F[k]-1, 则可以将该表分成长度为F[K-1]-1和F[k-2]-1的两段,即如上图所示。从而中间位置为mid=low+F[k-1]-1
  2. 类似的, 每一子段也可以用相同的方式分割
  3. 但顺序表长度n不一定刚好等于F[k]-1, 所以需要将原来的顺序表长度n增加至FlK1-1.这里的k值只要能使得F[k)-1恰好大于或等于n即可
public class fibSearch {
    private static int maxSize = 20;
	
    public static int[] fib() {
        int[] fib = new int[maxSize];
	fib[0] = 1;
	fib[1] = 1;
		
	for(int index = 2; index < maxSize; index++) {
	    fib[index] = fib[index - 1] + fib[index - 2];
	}
		
        return fib;
    }
	
    //斐波那契数列管理索引。  数组必须是有序的
    public static int fSearch(int[] arr, int value) {
	int low = 0; 
	int high = arr.length - 1;
	int k = 0;				//表示斐波那契分割数值的下标
	int mid = 0;
	int[] fib = fib();
		
	while(high > fib[k] - 1) {
	    k++;
	}
	//因为fib[k]的值可能大于arr的长度,所以需要构造新的数组
	int[] tmp = Arrays.copyOf(arr, fib[k]);
		
        for(int index = high + 1; index < tmp.length; index++) {
	    tmp[index] = arr[high];
	}
		
	while(low <= high) {					//只要这个条件满足就可以找
	    mid = low + fib[k - 1] - 1;
	    if(value < tmp[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(value > tmp[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;
}

流程分析:

 

【我们见到的太阳是8分钟之前的太阳,见到的月亮是1.3秒之前的月亮,见到一英里以外的建筑是5微妙之前的存在,即使你在我一米之外,我见到的也是3纳米秒之前的你,在宇宙的尺度上,我们所见的都是过去。】 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值