[ 数据结构 ] 查找算法--------线性、二分、插值、斐波那契查找

news2024/11/16 16:29:42

0 前言

查找算法有4种:

  • 线性查找

  • 二分查找/折半查找

  • 插值查找

  • 斐波那契查找

1 线性查找

思路:线性遍历数组元素,与目标值比较,相同则返回下标

/**
     *
     * @param arr  给定数组
     * @param value 目标元素值
     * @return 返回目标元素的下标,没找到返回-1
     */
    public static int search(int[] arr, int value) {
        int index = -1;

        for (int i = 0; i < arr.length; i++) {
            if (arr[i] == value) {
                index = i;
            }
        }

        return index;
    }

2 二分查找

思路:

  1. 在第一版search01中编写递归方法:

    递归三要素:①传参为目标数组(有序),查找区间的上下界,目标值,返回值为找到的下标 + ②终止条件为区间终止就是目标值(下界>上界表示找不到,则目标值为-1) + ③递归调用为改变区间上界或下界,即向查找区间的左半部分或右半部分查找

  2. 在第二版search02中:

    对search01方法优化:解决了存在多个相同查找目标的问题,当找到目标后并未立即返回,而是向两侧搜索相同值并存放在结果集合中,最后返回结果集

  3. 在第三版search03中:

    采用暴力搜索,将所有目标值的下标收集到集合中

  4. 对比总结:

    第二版和第三版一样是递归但是原理不同,第二版要求数组本身有序所以可以采用if-else结构来递归调用(可以理解为剪枝,即优化),而第三版就是简单的二叉树模型,遍历所有节点去查找目标值

//返回下标,没找到返回-1,前提是数组本身有序
    public static int search01(int[] arr,int lo,int hi, int value) {
        int index = -1;

        //这里为什么不能是大于或等于,因为最后区间大小就是1,即lo==mid==hi
        //而任一树枝上(本题只有一只树枝)一定先满足lo==hi(如果能找到此层就该返回正确值),而后才是lo>hi,
        if (lo > hi) {
            return index;
        }

        int mid = (lo + hi) / 2;
        int midVal = arr[mid];

        if (value < midVal) {
            index=search01(arr, lo, mid - 1, value);
        } else if (value > midVal) {
            index=search01(arr, mid + 1, hi, value);
        } else {
            index=mid;
        }

        return index;
    }

    //返回多个相同值的索引集合,前提是数组本身有序
    //原理:找到一个正确值后没有立即返回,而是向两侧搜索相同值
    public static List<Integer> search02(int[] arr,int lo,int hi, int value) {
        ArrayList<Integer> result = new ArrayList<>();

        //这里为什么不能是大于或等于,因为最后区间大小就是1,即lo==mid==hi
        //而任一树枝上(本题只有一只树枝)一定先满足lo==hi(如果能找到此层就该返回正确值),而后才是lo>hi,
        if (lo > hi) {
            return result;
        }

        int mid = (lo + hi) / 2;
        int midVal = arr[mid];

        if (value < midVal) {
            return search02(arr, lo, mid - 1, value);
        } else if (value > midVal) {
            return search02(arr, mid + 1, hi, value);
        } else {
            result.add(mid);
            int l = mid;
            int r = mid;
            //注意这里的或不能用短路或,否则后面的r自增不会执行,导致mid重复收集
            while (arr[--l] == midVal | arr[++r] == midVal) {
                if (arr[l] == midVal) {
                    result.add(l);
                }
                if (arr[r] == midVal) {
                    result.add(r);
                }
            }
        }

        return result;
    }


    public static List<Integer> list = new ArrayList<>();

    //基于search01优化:返回若干个相同值
    //不同于search01,方法1的原理是树只有一个树枝,遇到正确结果立即层层返回
    //search03原理,没有返回值,暴力搜索,遍历多个树枝的所有节点,收集所有满足的结果放入全局变量list集合
    //当然本方法不要求数组本身有序
    public static void search03(int[] arr,int lo,int hi, int value) {

        //这里为什么不能是大于或等于,因为最后区间大小就是1,即lo==mid==hi
        //而任一树枝上(本题只有一只树枝)一定先满足lo==hi(如果能找到此层就该返回正确值),而后才是lo>hi,
        if (lo > hi) {
            return;
        }

        int mid = (lo + hi) / 2;
        int midVal = arr[mid];

        if (value == midVal) {
            list.add(mid);
        }

        search03(arr, lo, mid - 1, value);
        search03(arr, mid + 1, hi, value);
    }

3 插值查找

引出:二分查找简单说就是看中间,不停地对半砍同时看正中间是不是我要的,那么问题就来了,如果我要的就是最左边,那得切多少下才能切到最左边,简单!不要对半切就行了,改下权重就解决

思路:将权重由1/2改为自适应,在数据量较大且分布均匀时明显效率更优,不再对半切了,你要的值和两边中哪边差不多就往那边切,找起来快多了

//基于二分查找的改良,将权重由1/2改为自适应,在数据量较大且分布均匀时明显效率更优
    public static int search01(int[] arr,int lo,int hi, int value) {
        int index = -1;

        //优化:查找值超过极值则直接返回-1
        if (lo > hi||value<arr[0]||value>arr[arr.length-1]) {
            return index;
        }

        //(value-arr[lo])/(arr[hi]-arr[lo])就是自适应的权重,二分的权重是1/2
        int mid = lo+(hi-lo)*( (value-arr[lo])/(arr[hi]-arr[lo]) );
        int midVal = arr[mid];

        if (value < midVal) {
            index=search01(arr, lo, mid - 1, value);
        } else if (value > midVal) {
            index=search01(arr, mid + 1, hi, value);
        } else {
            index=mid;
        }

        return index;
    }

4 斐波那契查找

引出:如果说二分找中间快,插值找两边快(中间也快),那么斐波那契查找就是鸡肋中的鸡肋,华而不实的lj东西

思路:首先你得拿到著名的斐波那契数列(1,1,2,3,5,8…),假定给定的待排序数组arr大小为6,那么给它扩到斐波那契数列的下一级,6和5一级,下一级就是8,扩充的元素就是当前的最后一个元素,完事就开始砍,不是对半砍,你得找到可以砍的地方,满足砍完左右两部分的数组大小刚好在斐波那契数列中,比如8个元素,对着arr[4]砍一刀,左边包含arr[0]到arr[4]即大小为5的数组,右边就剩下标为567了,砍的同时看下刀点arr[4]是不是你要的,整体就是这样一直砍一直看下刀点

总结:且不说这种砍法没有实际意义,说白了就是二分查找的权重改为无限接近黄金分割比0.618(而且数组越大越接近0.618,数组越小越接近1),那你干嘛不直接用0.618呢,况且你砍完之后,下刀点那个元素还是归到了左半部分,这就太sb了,完全就是为了凑数组大小为斐波那契数列中的元素值了,造成重复判断

image-20230107192331090.png

//获取斐波那契数组方法
    public static int[] fib() {
        int[] f = new int[20];
        f[0] = 1;
        f[1] = 1;
        for (int i = 2; i < 20; i++) {
            f[i] = f[i - 1] + f[i - 2];
        }
        return f;
    }

    //查找方法,不使用递归实现
    //斐波那契查找说到底只是让查找的区间大小一直在斐波那契数列中
    //,说白了就是切割比接近0.618,既然如此为什么不将二分查找的逻辑改为权重0.618
    //,纯粹就是装杯吧,说是感受数学之美还比不上插值查找的自适应权重有用
    public static int search(int[] arr, int value) {
        int lo = 0;
        int hi = arr.length - 1;
        int key = 0;//指针指向斐波那契数列元素,用以控制区间大小
        int mid = 0;//切割点
        int[] f = fib();//斐波那契数列

        //临时数组扩容到原数组大小的下一级,比如6扩容到5的下一级8
        while (hi > f[++key]-1) {
        }
        int[] temp = Arrays.copyOf(arr, f[key]);
        for (int i = hi+1; i < temp.length; i++) {
            temp[i] = temp[hi];
        }

        //此时key==5,f[key]==8,f[key-1]==5,f[key-2]==3,lo==0,hi==5,temp.length==8,mid==4
        //主要就是保证lo和hi的差值就是斐波那契数列的某个值-1,即查找的区间大小就是斐波那契数列值
        //缩小查找区间只需使key自减就行,但是
        //上下界的变迁需要注意,hi变迁只需赋为mid,lo却赋为mid+1,这是因为mid和hi间距是f[key-2]需要-1
        while (lo < hi) {
            mid = lo+ f[key - 1] - 1;
            if (value < temp[mid]) {
                hi = mid;
                key--;
            } else if (value > temp[mid]) {
                lo = mid + 1;
                key -= 2;
            } else {
                return mid;
            }
        }

        //hi==lo+f[k]-1,f[k]>=1
        if (lo == hi) {
            return lo;
        }


        return -1;
    }

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/150141.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

63.Python 调用类的属性和方法

63.调用类的属性和方法 文章目录63.调用类的属性和方法1. 调用属性的语法2.调用类的方法3.总结1. 调用属性的语法 我们根据类创建了一张奥迪A6的小汽车。根据汽车流水线呢&#xff0c;汽车生产完之后&#xff0c;需要移交给检查部门检查车辆的外观、颜色(属性)等是否与图纸一致…

JavaEE高阶---SpringBoot 统一功能处理

一&#xff1a;什么是SpringBoot 统⼀功能处理 SpringBoot统一功能处理是AOP的实战环节。我们主要学习三方面内容&#xff1a; 统一用户登录权限验证&#xff1b;统一数据格式返回&#xff1b;统一异常处理。 二&#xff1a;统一用户登录权限验证 Spring 中提供了具体的实现…

Scala 集合常用函数

文章目录集合常用函数一、基本属性和常用操作1、常用方法2、案例示例二、衍生集合1、衍生集合常用方法操作2、案例示例三、集合简单计算函数1、常用计算操作2、案例示例四、集合计算高级函数1、语义说明2、案例示例集合常用函数 一、基本属性和常用操作 1、常用方法 (1) 获取…

Release库与Debug库混用导致释放堆内存时产生异常的详细分析

目录 1、问题描述 2、使用Windbg启动Debug版本的exe程序进行分析 3、进一步分析 4、问题复盘 5、为什么Debug库与Release库混用可能会出异常&#xff1f; 6、最后 VC常用功能开发汇总&#xff08;专栏文章列表&#xff0c;欢迎订阅&#xff0c;持续更新...&#xff09;ht…

DM8:dexpdimp-逻辑导出--逻辑导入

DM8:dexp&dimp-逻辑导出--逻辑导入1 dexp逻辑导出dmp文件1.1 全库导出命令附加的参数信息1.2 导出用户所拥有权限的数据库对象-命令附加的参数信息1.3 导出用户所拥有权限的数据库对象-命令附加的参数信息2 dimp--逻辑导入dmp文件2.1 全库导入dmp数据文件-命令附加的参数信…

人体姿态估计-论文精读--DeepPose: Human Pose Estimation via Deep Neural Networks

图 1. 除了关节的极端变异性外&#xff0c;许多关节几乎不可见。我们能猜测左图中右臂的位置&#xff0c;因为我们看到了姿势的其余部分&#xff0c;并预测了这个人的运动或活动。同样&#xff0c;右边的人的左半身也根本看不见。这些都是需要进行整体推理的例子。我们相信DNNs…

php处理支付宝应用网关给接口发送的post参数

php如何接收支付宝应用网关发送的POST请求方式,参数又是GET请求的数据格式配置支付宝应用网关如何接收支付宝异步通知(应用网关接收请求)将&连接的参数分割成数组实例&#xff1a;难点配置支付宝应用网关 首先要在服务器上写一个接口,然后将接口的访问地址设置在支付宝应用…

手把手教Arthas,不再怕排查线上问题了

Arthas是alibaba开源的java诊断工具&#xff0c;支持jdk6&#xff0c;采用命令行交互模式&#xff0c;可以防败的定位和诊断线上的程序运行问题。官方文档&#xff1a;https://arthas.aliyun.com/doc/一、Arthas使用场景是否有一个全局视角来查看系统的运行状况&#xff1f;为什…

六种方式,教你在SpringBoot初始化时搞点事情!

前言 在实际工作中总是需要在项目启动时做一些初始化的操作&#xff0c;比如初始化线程池、提前加载好加密证书....... 那么经典问题来了&#xff0c;这也是面试官经常会问到的一个问题&#xff1a;有哪些手段在Spring Boot 项目启动的时候做一些事情&#xff1f; 方法有很多…

卷积层里的多输入多输出通道、池化层

多输入多通道每个通道都有一个卷积核&#xff0c;结果是所有通道卷积结果的和。无论有多少输入通道&#xff0c;到目前为止我们只用到单输出通道。可以有多个三维卷积核&#xff0c;每个核生成一个输出通道。输出通道数是卷积层的超参数。每个输入通道有独立的二维卷积核&#…

为什么JavaScript这么难学啊?

前言 觉得Js难其实是很正常的&#xff0c;首先这证明你在某些知识点上没有理解透彻&#xff0c;JS挺多的知识点点其实是比较抽象的&#xff0c;比如闭包、原型和原型链等&#xff0c;其次便是不会变通运用&#xff0c;这主要是敲代码熟练度的问题&#xff0c;所以我针对你这种…

架构运维篇(六):MySQL 8.0启用BinLog 支持

上一篇&#xff1a;架构运维篇&#xff08;五&#xff09;&#xff1a;Centos7/Linux中安装RocketMQ 最新线上的项目终于到多个数据执行的问题&#xff0c;找了团队DBA发现云上的MySQL 默认是没有启用BinLog支持。 小编研究了一下很简单&#xff0c;不过中间也遇到一些坑可以给…

结构重参数化(Structural Re-Parameters)PipLine

文章目录BASICSstrcutural Inception算法思想算法核心算法架构Re-Parameter四部曲&#xff1a;ACNetACNet原理ACNet分析涨点原因推理阶段融合机制Re-Parameter四部曲&#xff1a;RepVGGRepVGG原理RepVGG分析RepVGG BlockStructural Re-Parameters融合conv2d和BN融合1x1conv转换…

【一文讲通】样本不均衡问题解决--下

1欠采样、过采样欠采样&#xff1a;减少多数类的数量&#xff08;如随机欠采样、NearMiss、ENN&#xff09;。过采样&#xff1a;尽量多地增加少数类的的样本数量&#xff08;如随机过采样、以及2.1.2数据增强方法&#xff09;&#xff0c;以达到类别间数目均衡。还可结合两者做…

地址解析协议ARP

目录地址解析协议ARP1、流程2、动态与静态的区别3、ARP协议适用范围地址解析协议ARP 如何从IP地址找出其对应的MAC地址&#xff1f; 1、流程 ARP高速缓存表 当主机B要给主机C发送数据包时&#xff0c;会首先在自己的ARP高速缓存表中查找主机C的IP地址所对应的MAC地址&#xf…

Linux常用命令——lsblk命令

在线Linux命令查询工具 lsblk 列出块设备信息 补充说明 lsblk命令用于列出所有可用块设备的信息&#xff0c;而且还能显示他们之间的依赖关系&#xff0c;但是它不会列出RAM盘的信息。块设备有硬盘&#xff0c;闪存盘&#xff0c;cd-ROM等等。lsblk命令包含在util-linux-ng…

ES报文辅助生成工具-JavaFX

此程序为基于 Java8 开发的 JavaFX Maven 工程&#xff0c;是 Java 组装ElasticSearch请求报文工具的辅助 Java 代码生成工具&#xff0c;方便开发者快速编写代码。现学现用&#xff0c;写得不好。 工具界面 代码 pom.xml <project xmlns"http://maven.apache.org/P…

Android:URLEncoder空格被转码为“+”号

Android前段和后端接口交互时&#xff0c;经常会遇到特殊字符&#xff0c;比如表情、特殊标点等&#xff0c;这样在Url中是无法识别的&#xff0c;需要进行转码&#xff0c;后端进行解码交互。 但当使用URLEncoder时&#xff0c;会发现字符串中的空格被转换成“”号&#xff0…

客服系统即时通讯IM开发(四)网站实现实时在线访客列表【唯一客服】网站在线客服系统...

在使用我的客服系统时&#xff0c;如果引入了我的js &#xff0c;就可以实时看到网站上的所有访客了 使用 WebSocket 技术来实现实时通信。 在访客登录或退出时&#xff0c;向指定客服的 WebSocket 客户端发送消息。例如&#xff0c;你可以在访客登录时&#xff0c;向指定客服…

测试用例的设计? 万能公式

万能公式(必背)&#xff1a;功能测试性能测试界面测试兼容性测试易用性测试安全测试功能测试 &#xff1a;可能来自于需求文档&#xff0c;也可能来自生活经验性能测试 &#xff1a;功能没有问题不代表性能是ok的&#xff0c;性能往往体现在一些极端情况界面测试 &#xff1a;颜…