[ 数据结构 ] 查找算法--------递归实现

news2024/12/30 3:50:25

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/146724.html

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

相关文章

ora-39083/01917报错

报错信息&#xff1a; Import: Release 11.2.0.4.0 - Production on Wed Dec 7 17:59:59 2022 Copyright (c) 1982, 2011, Oracle and/or its affiliates. All rights reserved. Connected to: Oracle Database 11g Enterprise Edition Release 11.2.0.4.0 - 64bit Production …

1、Javaweb_HTMLtable布局

web概念概述 * JavaWeb&#xff1a; * 使用Java语言开发基于互联网的项目 * 软件架构&#xff1a; 1. C/S: Client/Server 客户端/服务器端 * 在用户本地有一个客户端程序&#xff0c;在远程有一个服务器端程序 * 如&#xff1a;QQ&#xff0c;迅雷.…

Linux环境下安装 java / javac

目录 1、检查虚拟机或者服务器的位数 2、下载 jdk 3、解压jdk 4、添加全局环境变量 1、检查虚拟机或者服务器的位数 安装 java / javac 其实就是下载合适版本的 jdk&#xff0c;我们需要先确认虚拟机或者服务器的主机信息&#xff0c;来下载合适版本的 jdk。 输入 uname …

maven环境变量配置(超详细!)

下载地址&#xff1a; 官网地址 建议不要下载在C盘&#xff01;&#xff01; 配置过程 1.解压下载好的压缩包 2.此电脑–右键–属性–高级系统设置–环境变量 3.新建一个系统变量&#xff08;点击系统变量的新建&#xff09; 变量名&#xff1a;MAVEN_HOME 变量值&#x…

Python深度学习基础(八)——线性回归

线性回归引言损失函数解析解公式代码实例梯度下降理论随机梯度下降的手动实现代码torch中的随机梯度下降引言 我们生活中可能会遇到形如 yw1x1w2x2w3x3byw_1x_1w_2x_2w_3x_3byw1​x1​w2​x2​w3​x3​b 的问题&#xff0c;其中有y为输出&#xff0c;x为输入&#xff0c;w为权…

Java设计模式中工厂模式是啥?静态工厂、简单工厂与抽象工厂,工厂方法模式又是啥,怎么用,

继续整理记录这段时间来的收获&#xff0c;详细代码可在我的Gitee仓库SpringBoot克隆下载学习使用&#xff01; 4.3 工厂模式 4.3.1 背景 若创建对象时直接new对象&#xff0c;则会使对象耦合严重&#xff0c;更换对象则很复杂 4.3.2 简单工厂 4.3.3 特点 不是一种设计模…

c语言 文件处理2 程序环境和预处理

对比函数 sprintf&#xff08;把一个格式化数据转化为字符串&#xff09; sscanf &#xff08;从一个字符串中读一个格式化数据&#xff09; struct S {char arr[10];int age;float f; };int main() {struct S s { "hello", 20, 5.5f };//把这个转化为一个字符串s…

idea调试unity里面的lua代码

前言 本人一名java后端开发&#xff0c;看到前端同事调试lua代码无脑print&#xff0c;甚为鄙视&#xff0c;百度加实操写一份调试unity的lua脚本文档 操作 1.安装lua lua官网下载页面 最终下载页面 2.idea安装插件 emmylua 3.idea打开unity的lua脚本 idea->file->op…

【面试题】面试如何正确的介绍项目经验

大厂面试题分享 面试题库前端面试题库 &#xff08;面试必备&#xff09; 推荐&#xff1a;★★★★★地址&#xff1a;前端面试题库1、在面试前准备项目描述&#xff0c;别害怕&#xff0c;因为面试官什么都不知道面试官是人&#xff0c;不是神&#xff0c;拿到你的简历的时候…

ospf双向重发布,LSA优化综合

目录实验分析ip地址划分写公网缺省路由区域0公网MGRE搭建各个区域ospf的宣告改变ospf接口工作方式和更改接口优先级ospf多进程及双向重发布减少LSA的更新量1&#xff0c;减少特殊区域的LSA更新量2&#xff0c;骨干区域的优化域间汇总域外汇总防环nat的设置实验分析 如图实际的…

VS Code 为 Clang for MSVC 配置 cmake cmake tools

介绍 在windows平台上&#xff0c;由于平台API差异过大&#xff0c;一般为linux设计的项目&#xff08;POSIX兼容&#xff09;无法通过MSVC的编译&#xff0c;而是会报非常多的头文件错误。如果要修改&#xff0c;工程量将巨大。Windows平台上&#xff0c;主要有两个类POSIX兼容…

【JavaScript】事件--总结

千锋 1.Event 对象 代表事件的状态&#xff0c;比如事件在其中发生的元素、键盘按键的状态、鼠标的位置、鼠标按钮的状态。 div{width: 200px;height: 200px;background-color: yellow;} </style> <body><input type"text" id"username"&…

JavaScript 事件

文章目录JavaScript 事件HTML 事件常见的HTML事件JavaScript 可以做什么?JavaScript 事件 HTML 事件是发生在 HTML 元素上的事情。 当在 HTML 页面中使用 JavaScript 时&#xff0c; JavaScript 可以触发这些事件。 HTML 事件 HTML 事件可以是浏览器行为&#xff0c;也可以是…

babel做兼容处理 到底怎么使用?

1.背景 日常项目开发中总是避免不了对低版本浏览器做一些兼容处理&#xff0c;最常见的手段就是结合编译工具使用babel来处理一些语法的兼容&#xff0c;但是每次使用的时候其实Babel的配置和使用到的相关库总是云里雾里&#xff0c;网上的各种推荐方案眼花缭乱不知道到底应该…

自定义DotNetCore 项目模板

在进行代码开发时候&#xff0c;各自的团队或者公司都会有服务自己要求的项目代码模板&#xff0c;再创建新的项目时&#xff0c;都需要按照模板规范进行定义&#xff0c;NET支持自定义项目模板&#xff0c;这样在进行项目创建时就会高效很多。 官方参考文档&#xff1a;dotne…

软测复习01:软件测试概述

文章目录软件测试的目的软件测试的定义软件测试与软件开发软件测试发展软件测试的目的 基于不同的立场&#xff0c;存在着两种完全不同的测试目的 从用户的角度出发&#xff0c;希望通过软件测试暴露软件中隐藏的错误和缺陷&#xff0c;以考虑是否可接受该产品。从软件开发者的…

Java当中的定时器

目录 一、什么是定时器 二、Java当中的定时器 ①schedule()方法&#xff1a; ②TimerTask ​编辑 ③delay 三、实现一个定时器 前提条件: 代码实现: ①确定一个“任务”&#xff08;MyTask)的描述&#xff1a; ②schedule方法&#xff1a; ③需要一个计时器 属性…

MAT-内存泄漏工具使用

目录 一、MAT简介 1.1 MAT介绍 1.2 MAT工具的下载安装 二、使用MAT基本流程 2.1 获取HPROF文件 2.2 MAT主界面介绍 2.3 MAT中的概念介绍 2.3.1 Shallow heap 2.3.2 Retained Heap 2.3.3 GC Root 2.4 MAT中的一些常用的视图 2.4.1 Thread OvewView 2.4.2 Group 2.…

复杂工况下少样本轴承故障诊断的元学习

摘要&#xff1a;近年来&#xff0c;基于深度学习的轴承故障诊断得到了较为系统的研究。但是&#xff0c;这些方法中的大多数的成功在很大程度上依赖于大量的标记数据&#xff0c;而这些标记数据在实际生产环境中并不总是可用的。如何在有限的数据条件下训练出鲁棒的轴承故障诊…

线程状态到底是5种还是六种?傻傻分不清楚

目录 从操作系统层面上描述线程状态 从javaAPI层面上理解线程的6种状态 线程的状态转换. NEW --> RUNNABLE 1.RUNNABLE <--> WAITING 2.RUNNABLE <--> WAITING 3.RUNNABLE <--> WAITING 1.RUNNABLE <--> TIMED_WAITING 2.RUNNABLE <--&…