• 算法系列二:插值查找、斐波那契查找


    书接上回:算法系列一:顺序查找、二分查找、分块查找

    四. 插值查找

    4.1 简介

    上一篇的二分查找中,每次查找都是从待查序列的中间开始,那么就很容易想到一个问题或者场景:假设我有一个数组0~999,我要查找的数为900,如果按照二分查找,那我每次取中间值要查找好一会,我们从上帝视角看明显可以从靠后的位置去查。于是针对这种情况,出现了插值查找

    • 基本思路
        插值查找是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的 查找方法,其核心就在于插值的计算公式 (key-array[low])/(array[high]-array[low])*(high-low)。简而言之,基于二分查找算法,将查找点的选择改进为自适应选择。
      复杂度分析
    • 时间复杂性:如果元素均匀分布,则O(log(logn)),在最坏的情况下可能需要O(n)。
    • 空间复杂度:O(1)。
    • 优缺点分析
        对于长度比较长、关键字分布又比较均匀的查找表来说,插值查找算法的平均性能比折半查找要好的多。反之,数组中如果分布非常不均匀,那么插值查找未必是很合适的选择。

    区别对比
    ① 插值查找类似于二分法,但插值查找每次从自适应 mid 处开始查找;
    ② 二分查找种mid为(left+right) / 2, 插值查找则是通过公式计算得来;
    ③ 插值查找公式:int mid = low + (high - low) * (key - arr[low]) / (arr[high] - arr[low]) ;low表示左边索引left,high表示右边索引right. key 就是需要查找的值。

    插值查找特点
    ① 查找序列有序;
    数据量大、关键字分布均匀的有序序列,插值查找快;
    ③ 对于分布不均匀的有序序列来说,不一定比二分查找好。

    4.2 代码实现

    ① 迭代法查找:

    private static int insertSearch1(int arr[],int target){
    	/*初始化左右搜索边界*/
    	   int left=0,right=arr.length-1;
    	   int mid;
    	   while(left<=right){
    	       mid=left+(target-arr[left])/(arr[right]-arr[left])*(right-left);
    	       /*arr[mid]大于target,即要寻找的元素在左半边,所以需要设定右边界为mid-1,搜索左半边*/
    	       if(target<arr[mid]){
    	           right=mid-1;
    	       /*arr[mid]小于target,即要寻找的元素在右半边,所以需要设定左边界为mid+1,搜索右半边*/
    	          }else if(target>arr[mid]){
    	           left=mid+1;
    	       /*搜索到对应元素*/
    	       }else if(target==arr[mid]){
    	           return mid;
    	       }
    	   }
    	   /*搜索不到返回-1*/
    	   return -1;
    	}
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20

    ② 递归法实现:

    private static int insertSearch2(int array[],int left,int right,int target){
    	if(left<=right){
    		int mid=left+(target-array[left])/(array[right]-array[left])*(right-left);
    		/*搜索到对应元素*/
    		if(array[mid]==target){
    			return mid;
    		}else if(array[mid]<target){
    			/*array[mid]小于target,即要寻找的元素在右半边,所以需要设定左边界为mid+1,搜索右半边*/
    			return insertSearch2(array,mid+1,right,target);
    		}else{
    			/*array[mid]大于target,即要寻找的元素在左半边,所以需要设定右边界为mid-1,搜索左半边*/
    			return insertSearch2(array,left,mid-1,target);
    		}
    	}else{
    		return -1;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    五. 斐波那契查找

    5.1 简介

    斐波那契查找又名黄金分割查找算法。

    1. 什么是黄金分割?
      黄金分割点是指把一条线段分割为两部分,使其中一部分与全长之比等于另一部分与这部分之比。取其前三位数字的近似值是0.618.由于按此比例设计的造型十分美丽,因此称为黄金分割,也称中外比。
    2. 什么是斐波那契数列?
      斐波那契数列{1, 1, 2, 3, 5, 8, 13, 21, 34, 55…}发现斐波那契数列的两个相邻数的比例,无限接近黄金分割值0.618

    算法原理
    斐波那契查找原理与二分查找算法和插值查找算法非常相似,仅仅改变了中间节点(mid)的位置,mid不再是中间或插值得到,而是位于黄金分割点附近,即mid=low+F(k-1)-1(F代表斐波那契数列)。

    ???怎么理解这里的mid

    • 由斐波那契数列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.
    • 类似的,每一字段也可以用相同的方式分割
    • 但顺序表长度n不一定刚好等于F[k]-1,所以需要将原来的顺序表长度n增加至F[k]-1.这里的k值只要能使F[k]-1恰好大于或等于n即可,由以下代码得到,顺序表长度增加后,新增的位置(从n+1 到 F[k]-1位置),都赋为n位置的值即可
    while(n>fib(k)-1){
      k++;
    }
    
    • 1
    • 2
    • 3
    • 复杂度分析
      最坏情况下,时间复杂度为O(logn),且其期望复杂度也为O(logn)。

    5.2 算法实现

    public class FibonacciSearch {
    	
    	public static int FLENGTH = 20;
    	public static void main(String[] args) {
    		int [] arr = {1,8,10,89,100,134};
    		int target = 89;
    		System.out.println("目标元素在数组中位置是:" + fibSearch(arr, target));		
    	}
    
    	public static int[] fib() {
    		int[] f = new int[FLENGTH];
    		f[0] = 1;
    		f[1] = 1;
    		for (int i = 2; i < FLENGTH; i++) {
    			f[i] = f[i-1] + f[i-2];
    		}
    		return f;
    	}
    	
    	public static int fibSearch(int[] arr, int target) {
    		int low = 0;
    		int high = arr.length - 1;
    		int k = 0; 
    		int mid = 0; 
    		int f[] = fib();
    		/*获取最相邻的斐波那契数组中元素的值,该值略大于数组的长度*/
    		while(high > f[k] - 1) {
    			k++;
    		}
    		/*因为 f[k]值可能大于arr的长度。如果大于时,需要构造一个新的数组temp[],将arr数组中的元素拷贝过去,不足的部分会使用0填充*/
    		int[] temp=Arrays.copyOf(arr, f[k]);
    		/*然后将temp后面填充的0,替换为最后一位数字
    		 *如将temp数组由{1,8,10,89,100,134,0,0}变换为{1,8,10,89,100,134,134,134}*/
    		for(int i = high + 1; i < temp.length; i++) {
    			temp[i] = arr[high];
    		}
    		
    		while (low <= high) { 
    			mid = low + f[k - 1] - 1;
    			if(target < temp[mid]) { 
    				high = mid - 1;
    				/*因为f[k]=f[k-1]+f[k-2],所以k--就相当于取temp数组的左边部分*/
    				k--;
    			} else if ( target > temp[mid]) { 
    				low = mid + 1;
    				/*同理,f[k]=f[k-1]+f[k-2],k -= 2就相当于取temp数组的右边部分*/
    				k -= 2;
    			} else {
    				/*原arr数组中的值*/
    				if(mid <= high){
    					return mid;
    				/*在temp中,扩展出来的高位的值*/
    				}else{
    					return high;
    				}
    			}
    		}
    		return -1;
    	}
    }
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57
    • 58
    • 59
    • 60
    • 61

    这里的算法实现可以理解可以看这位博主


    活动地址:CSDN21天学习挑战赛

  • 相关阅读:
    【Mac】系统环境配置
    《TCP/IP网络编程》阅读笔记--域名及网络地址
    LeetCode2369. Check if There is a Valid Partition For The Array——动态规划
    centos8 安装 docker
    Windows + Syslog-ng 发送eventlog 到Splunk indexer
    Databend 开源周报第 149 期
    设计模式~迭代器模式(Iterator)-20
    Docker---Docker-compose部署安装Prometheus+Alertmanager+Grafana
    数据采集之:巧用布隆过滤器提取数据摘要
    C++ Reference: Standard C++ Library reference: C Library: cmath: copysign
  • 原文地址:https://blog.csdn.net/qq_36256590/article/details/126176644