大话数据结构-查找-有序表查找

news2025/1/24 10:36:22

注:本文同步发布于稀土掘金。

3 有序表查找

3.1 折半查找

  折半查找(Binary Search)技术,又称为二分查找,它的前提是线性表中的记录必须是关键码有序(通常从小到大有序),线性表必须采用顺序存储。

  折半查找的基本思想是:在有序表中,取中间记录作为比较对象,若给定值与中间记录的关键字相等,则查找成功;若给定值小于中间记录的关键字,则在中间记录的左半区继续查找;若给定值大于中间记录的关键字,则在中间记录的右半区继续查找。不断重复上述过程,直到查找成功,或所有查找区域无记录,查找失败为止。

  代码有多种实现方式,以下是示例:

/**
 * Binary Search
 *
 * @author Korbin
 * @date 2023-04-19 17:57:03
 **/
public class BinarySearch<T extends Comparable<T>> {

    /**
     * binary search
     * <p>
     * return index in data if searched, else return -1
     *
     * @param data array to search
     * @param key  key to search
     * @return index of key in data
     * @author Korbin
     * @date 2023-04-19 18:30:33
     **/
    public int binarySearch(T[] data, T key) {
        int length = data.length;

        int from = 0;
        int to = length - 1;

        // if key little than data[0] or key greater than data[length - 1], return -1, means search failed
        if (data[from].compareTo(key) > 0 || data[to].compareTo(key) < 0) {
            return -1;
        }

        int mid = ((to - from) + 1) / 2;

        while (from < to) {

            // if data[mid] equals key, then return mid
            if (data[mid].equals(key)) {
                return mid;
            }
            if (data[mid].compareTo(key) < 0) {
                // if key greater than data[mid], then search from [mid + 1, to]
                from = Math.min(mid + 1, length - 1);
            } else if (data[mid].compareTo(key) > 0) {
                // if key little than data[mid], then search from [from, mid - 1]
                to = Math.max(mid - 1, 0);
            }

            if (from == to) {
                // if from equals to, then check if data[from] equals key
                return (data[from].equals(key)) ? from : -1;
            }
            
            mid = from + ((to - from) + 1) / 2;

        }
        return -1;
    }

}

3.2 插值查找

  插值查找(Interpolation Search)是根据要查找的关键字key与查找表中最大最小记录的关键字比较后的查找方法,其核心在于插值公式 k e y − a [ f r o m ] a [ t o ] − a [ f l o w ] \frac {key-a[from]}{a[to]-a[flow]} a[to]a[flow]keya[from]

  从时间复杂度来看,它也是O(logn),但对于表长较大,而关键字又分布比较均匀的查找表来说,插值查找的平均性能要比折半查找算法的性能要好很多。反之,如果数组分布不均匀,用插值查找未必有优势。

  插值查找是在折半查找的基础上进行优化的,在折半查找中,计算mid的算法为:

   m i d = f r o m + 1 2 ( ( t o − f r o m ) + 1 ) mid = from + \frac {1}{2}((to - from) + 1) mid=from+21((tofrom)+1)

  在插值查找算法中,则是:

   m i d = f r o m + k e y − a [ f r o m ] a [ t o ] − a [ f l o w ] ( ( t o − f r o m ) + 1 ) mid = from + \frac {key-a[from]}{a[to]-a[flow]}((to - from) + 1) mid=from+a[to]a[flow]keya[from]((tofrom)+1)

  因此代码只作少量改动:

/**
 * interpolation search
 * <p>
 * return index in data if searched, else return -1
 *
 * @param data array to search
 * @param key  key to search
 * @return index of key in data
 * @author Korbin
 * @date 2023-04-19 18:30:33
 **/
public int interpolationSearch(int[] data, int key) {

    int length = data.length;

    int from = 0;
    int to = length - 1;

    // if key little than data[0] or key greater than data[length - 1], return -1, means search failed
    if (data[from] > key || data[to] < key) {
        return -1;
    }

    int mid = ((key - data[from]) / (data[to] - data[from])) / 2 * ((to - from) + 1);

    while (from < to) {

        // if data[mid] equals key, then return mid
        if (data[mid] == key) {
            return mid;
        }
        if (data[mid] < key) {
            // if key greater than data[mid], then search from [mid + 1, to]
            from = Math.min(mid + 1, length - 1);
        } else if (data[mid] > key) {
            // if key little than data[mid], then search from [from, mid - 1]
            to = Math.max(mid - 1, 0);
        }

        if (from == to) {
            // if from equals to, then check if data[from] equals key
            return (data[from] == key) ? from : -1;
        }

        mid = from + ((key - data[from]) / (data[to] - data[from])) / 2 * ((to - from) + 1);

    }
    return -1;
}

  调整一下mid的计算方式即可。

3.3 斐波那契查找

  以下是一个斐波那契数组:

  斐波那契数组的特性是,后一个元素的值等于前两个元素值的和,即F[K]=F[K-1]+F[K-2]。此外,F[K]/F[K+1]无限接近于0.618。斐波那契查找法依据这一特性,将数据分割成两部分,并把F[K-1]-1作为mid值进行对比处理。

  例如,假设数组长度是8,8在斐波那契数组中的下标是6,那么把数组分为两段,长度分别是F[K-1]=F[5]=5,F[K-2]=F[4]=3,令mid=F[K-1]-1=5-1=4,比较要查找的数值与被查找的数组A中,下标为4的元素的大小。

  在持续查找的过程中,被查找的数组A因为是有序数组,所以如果mid所对应的元素值大于要查找的数值时,进行下一轮查找时,则应到被查找数组的下半段去查找,下半段数组长度是多少呢?上文提到,裴波那契数组的特性F[K]=F[K-1]+F[K-2],而斐波那契查找就是将数组分成两段,前半段长度是F[K-2],后半段长度是F[K-1],因此当我们在后半段查找时,后半段的数组长度是F[K-1],即新的K=K-1,接下来的mid计算方式仍然不变。

  而这种情况下,下标为mid以及其后的元素,在下一轮查找时显然不可以再用于查找,因此它们肯定会大于要查找的这个值,因此我们设置一个变量high,令其初始值为数组的长度,在A[mid]大于要查找的数值时,令high=mid-1,表示最多可以被查找的元素下标是high,对应的元素值是A[high]。

  而如果mid所对应的元素值小于要查找的数值时,需要进行下一轮查找时,因为前半段长度为F[K-2],因此新的K=K-2,而mid的计算方式不再是mid=F[K-1]-1,而是“上一轮的mid”+1+F[K-1]-1,我们设置一个变更low,令其等于“上一轮的mid”+1,那么,mid的计算方式就变成了mid=low+F[K-1]-1,由于第一轮查找时没有“上一轮的mid”,所以如果按照这个公式,第一轮的low则为1,这样可以保证mid的计算公式一直是mid=low+F[K-1]-1。

  根据以上分析,可知:

  (1) 变量mid,表示使用数组中下标为mid的元素与要查找的数值进行比较;

  (2) 变量k,表示被查找的数组长度在斐波那契数组中的位置;

  (3) 变量low,表示从数组的下标为low的元素开始查找,初始值为1,当A[mid]<被查找的元素时,low=mid+1,同时置k=k-2;

  (4) 变量high,表示最多查到数组的下标为high的元素,初始值为数组的最大下标,当A[mid]>被查找的元素时,high=mid-1,同时置k=k-1;

  现在我们来开始尝试,假设有以下数组:

  我们需要从中找到数值59所在的位置。

  首先,初始化,low=1,high=数组的最大下标=10,同时定义一个斐波那契数组:

  然后第一次查找,我们来找k,已知数组长度为11,在斐波那契数组f中并未找到10这个元素,有两个选择:

  如果选择8,即k=6,f[k]=f[6]=8,假设我们要查找的是99,会出现什么情况呢:

  (1) 第一轮,mid=low+f[k-1]-1=1+f[5]-1=1+5-1=5,由于a[mid]<要查找的数值,因此新的k=k-2=3,新的low=mid+1=5+1=6;

  (2) 第二轮,mid=low+f[k-1]-1=6+f[2]-1=6+1-1=6,由于a[mid]<要查找的数值,因此,新的k=k-2=0,新的low=mid+1=6+1=7;

  (3) 第三轮,mid=low+f[k-1]-1=7+f[0-1]-1,无法再继续,而此时仍有a[7]~a[10];

  如果选择13,即k=7,f[k]=f[7]=13,假设我们要查找的是99,会出现什么情况呢:

  (1) 第一轮,mid=low+f[k-1]-1=1+f[6]-1=1+8-1=8,a[8]<99,因此新的k=k-2=4,新的low=mid+1=8+1=9;

  (2) 第二轮,mid=low+f[k-1]-1=9+f[3]-1=9+3-1=11,这时会发现,11已经超过了a的最大下标10,查找直接失败;

  (3) 此时我们进行一些调整,将数组a的长度扩大到f[k]即13位,并补齐后两位的值为f[10],即f[11]=f[12]=f[10]=99,这时再来查询,就可以得到a[11]=99,找到99在数组a的下标为11的位置,而由于原始的a最大下标为10,因此直接返回10即可。

  由此找到规则:当数组长度在斐波那契数组中找不到对应元素时,取与数组长度相邻,但大于数组长度的那个元素的下标作为k,同时将被查找的数组长度扩大到k,并补齐后续元素值使其等于被查找的数组的最后一个元素值。

  因此我们取k=7,此时数组a和f的结构如下所示:

  开始第一轮查找,此时mid=low+f[k-1]-1=1+f[6]-1=1+8-1=8,a[8]=73>59,因此high=mid-1=8-1=7,k=k-1=7-6=6:

  第二轮查找,mid=low+f[k-1]-1=1+f[5]-1=5,a[5]=47<59,因此low=mid+1=5+1=6,k=k-2=6-2=4:

  第三轮查找,mid=low+f[k-1]-1=6+f[2]-1=6+1-1=6,a[6]=59,得到查找结果,返回查找值59所在的下标是6,查找结束。

  依据以上分析,代码实现比较简单:

import java.util.Arrays;

/**
 * 斐波那契查找
 *
 * @author Korbin
 * @date 2023-11-09 09:16:33
 **/
public class FibonacciSearch {

    /**
     * 定义一个斐波那契数组
     *
     * @param length 数组长度
     * @return 斐波那契数组
     * @author Korbin
     * @date 2023-11-09 09:26:32
     **/
    private static int[] fibonacciArray(int length) {
        int[] array = new int[length];
        array[0] = 0;
        if (length == 1) {
            return array;
        } else if (length == 2) {
            array[1] = 1;
            return array;
        } else {
            array[1] = 1;

            for (int i = 2; i < length; i++) {
                array[i] = array[i - 1] + array[i - 2];
            }
            return array;
        }
    }

    /**
     * 查找key在数组array中的下标,找不到时返回-1
     *
     * @param array 被查找的数组
     * @param key   要查找的key
     * @return key在array中的下标
     * @author Korbin
     * @date 2023-11-09 09:28:51
     **/
    private static int fibonacciSearch(int[] array, int key) {

        int length = array.length;
        // 如果被查找的数组只有一位,则直接比较返回
        if (length == 1) {
            if (array[0] == key) {
                return 0;
            } else {
                return -1;
            }
        }

        // 因为是从下标为1的数组开始查找的,因此先比较下标为0的元素
        if (array[0] == key) {
            return 0;
        }

        int[] fibonacciArray = fibonacciArray(length);

        // low初始为1
        int low = 1;
        // high初始为length - 1
        int high = length - 1;

        // 从斐波那契数组中找到k
        int k = 0;
        for (int i = 0; i < length; i++) {
            if (length > fibonacciArray[i]) {
                k++;
            }
        }

        // 如果被查找的数组长度小于k,则扩充数组
        int[] newArray = Arrays.copyOf(array, fibonacciArray[k]);
        if (fibonacciArray[k] > length) {
            for (int i = length; i < fibonacciArray[k]; i++) {
                newArray[i] = array[length - 1];
            }
        }

        // 开始查找
        while (low <= high) {
            // 计算mid
            int mid = low + fibonacciArray[k - 1] - 1;
            if (key < newArray[mid]) {
                high = mid - 1;
                k = k - 1;
            } else if (key > newArray[mid]) {
                low = mid + 1;
                k = k - 2;
            } else {
                if (mid < length) {
                    return mid;
                } else {
                    return length - 1;
                }
            }
        }

        return -1;
    }

    public static void main(String[] args) {
        int[] array = new int[]{0, 1, 16, 24, 35, 47, 59, 62, 73, 87, 99};
        for (int j : array) {
            int index = fibonacciSearch(array, j);
            System.out.println("元素" + j + "的下标是" + index);
        }
    }

}

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

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

相关文章

【Vue3从入门到项目实现】RuoYi-Vue3若依框架前端学习——登录页面

若依官方的前后端分离版中&#xff0c;前端用的Vue2&#xff0c;这个有人改了Vue3的前端出来。刚好用来学习&#xff1a; https://gitee.com/weifengze/RuoYi-Vue3 运行前后端项目 首先运行项目 启动前端&#xff0c;npm install、npm run dev 启动后端&#xff0c;按教程配置…

信息系统安全运维服务资质认证申报流程详解

随着我国信息化和信息安全保障工作的不断深入&#xff0c;以应急处理、风险评估、灾难恢复、系统测评、安全运维、安全审计、安全培训和安全咨询等为主要内容的信息安全服务在信息安全保障中的作用日益突出。加强和规范信息安全服务资质管理已成为信息安全管理的重要基础性工作…

nodeJS爬虫-爬取虎嗅新闻

1.安装依赖库到本地&#xff0c;需要的库有&#xff1a;安装方法见Node.js笔记说明 const superagent require(superagent); const cheerio require(cheerio); const async require(async); const fs require(fs); const url require(url); const request require(reques…

Apache Doris 详细教程(一)

1、Doris简介 1.1、doris概述 Apache Doris 由百度大数据部研发&#xff08;之前叫百度 Palo&#xff0c;2018 年贡献到 Apache 社区后&#xff0c; 更名为 Doris &#xff09;&#xff0c;在百度内部&#xff0c;有超过 200 个产品线在使用&#xff0c;部署机器超过 1000 台…

用OpenCV与MFC写一个图像格式转换及简单处理程序

打开不同格式的图形文件&#xff0c;彩色装灰度图像、锐化、高斯滤波、边界检测及将其存储为需求格式是图像处理的最基本的操作。如果单纯用MFC编程&#xff0c;是一个令人头痛的事情&#xff0c;有不少的代码量。可用OpenCV与MFC编程就变得相对简单。下面来详细演示这一编程操…

股票所有均线都跌破应该怎么操作?

股票跌破所有均线说明股票趋势是走坏的&#xff0c;并且均线对股票起到压制作用&#xff0c;投资者有两种操作方式&#xff0c;第一种是割肉换股&#xff0c;投资者可以在股票小幅上涨时&#xff0c;将股票全部卖出&#xff0c;再买入最近比较强势的个股&#xff0c;赚取收益、…

ubuntu安装MySQL8

1.下载mysql8 MySQL :: Download MySQL Installer (Archived Versions) 选择对应的mysql版本和对应的ubuntu版本图即可 2.下载后上传到sftp文件夹中&#xff0c;然后通过以下命令解压 tar -xvf mysql-server_8.0.29-1ubuntu20.04_amd64.deb-bundle.tar 3.依次安装即可 &#…

数据结构与算法之美学习笔记:32 | 字符串匹配基础(上):如何借助哈希算法实现高效字符串匹配?

标题 前言BF 算法RK 算法解答开篇 & 内容小结 前言 本节课程思维导图&#xff1a; 从今天开始&#xff0c;我们来学习字符串匹配算法。我们用的最多的比如 Java 中的 indexOf()&#xff0c;Python 中的 find() 函数等&#xff0c;它们底层就是依赖接下来要讲的字符串匹配算…

解决:spring boot+mybatis进行增删改查的时候,接收到前端数据,并且执行成功了,但是数据库里面添加的内容都是空值

在写spring boot整合mybatis的时候&#xff0c;我在Apifox里面测试&#xff0c;数据也传递过去了&#xff0c;后端服务器也接收到了参数&#xff0c;就是数据库里面添加的都是空值&#xff1f;&#xff1f;&#xff1f; 前端接收到了数据&#xff0c;并且没有报错 Apifox里面也…

【微服务】spring循环依赖深度解析

目录 一、循环依赖概述 1.2 spring中的循环依赖 二、循环依赖问题模拟 2.1 循环依赖代码演示 2.2 问题分析与解决 2.2.1 使用反射中间容器 三、spring循环依赖问题解析 3.1 spring中的依赖注入 3.1.1 field属性注入 3.1.2 setter方法注入 3.1.3 构造器注入 3.2 spri…

Python中字符串列表的相互转换详解

更多资料获取 &#x1f4da; 个人网站&#xff1a;ipengtao.com 在Python编程中&#xff0c;经常会遇到需要将字符串列表相互转换的情况。这涉及到将逗号分隔的字符串转换为列表&#xff0c;或者将列表中的元素连接成一个字符串。本文将深入讨论这些情景&#xff0c;并提供丰富…

JAVA网络编程——BIO、NIO、AIO深度解析

I/O 一直是很多Java同学难以理解的一个知识点&#xff0c;这篇帖子将会从底层原理上带你理解I/O&#xff0c;让你看清I/O相关问题的本质。 1、I/O的概念 I/O 的全称是Input/Output。虽常谈及I/O&#xff0c;但想必你也一时不能给出一个完整的定义。搜索了谷哥欠&#xff0c;发…

吉利护航,宣称比友商“更懂车”,魅族造车的底气与底色

继小米、华为后&#xff0c;又一家手机厂商宣布跨界造车。 在近日举办的2023魅族秋季无界生态发布会上&#xff0c;星纪魅族集团&#xff08;下称“魅族”&#xff09;董事长兼CEO沈子瑜宣布&#xff0c;魅族正式进入汽车市场&#xff0c;将在2024年第一季度启动“DreamCar共创…

CoreDNS实战(十一)-分流与重定向

本文主要介绍了目前CoreDNS服务在外部域名递归结果过程中出现的一些问题以及使用dnsredir插件进行分流和alternate插件进行重试优化的操作。 1 自建DNS服务现状 一般来说&#xff0c;无论是bind9、coredns、dnsmasq、pdns哪类dns服务器&#xff0c;我们自建的监听在UDP53端口…

微信怎么自动跟圈?怎么一键转发好友的朋友圈?

做私域、微商的小伙伴们每天需要发许多朋友圈来推广产品&#xff0c;一个人手上就有好几个微信的话&#xff0c;每个微信都要发朋友圈的话&#xff0c;非常麻烦。有没有一键转发同步好友朋友圈的功能呢&#xff1f; 朋友圈互动 1&#xff09;查看朋友圈&#xff1a;可通过昵称…

智能优化算法应用:基于梯度算法无线传感器网络(WSN)覆盖优化 - 附代码

智能优化算法应用&#xff1a;基于梯度算法无线传感器网络(WSN)覆盖优化 - 附代码 文章目录 智能优化算法应用&#xff1a;基于梯度算法无线传感器网络(WSN)覆盖优化 - 附代码1.无线传感网络节点模型2.覆盖数学模型及分析3.梯度算法4.实验参数设定5.算法结果6.参考文献7.MATLAB…

识别和修复网站上损坏链接的最佳实践

如果您有一个网站&#xff0c;我们知道您花了很多时间在它上面&#xff0c;以使其成为最好的资源。如果你的链接不起作用&#xff0c;你的努力可能是徒劳的。您网站上的断开链接可能会以两种方式损害您的业务&#xff1a; 它们对企业来说是可怕的&#xff0c;因为当消费者点击…

接上文 IBM ServeRAID M1015阵列卡 支持RAID5

模块到了&#xff0c;由于着急测试没直接拍照&#xff0c;就是一个跟指甲盖大小的模块&#xff0c;直接安装到阵列卡U1接口上&#xff0c;&#xff0c;不知道U1是哪个位置的参考前文&#xff0c;安装到机器上之后的图片如下 启动服务器&#xff0c;进入WebBIOS&#xff0c;选择…

Databend 如何利用 GPT-4 进行质量保证

背景 在数据库行业&#xff0c;质量是核心要素。 Databend 的应用场景广泛&#xff0c;特别是在金融相关领域&#xff0c;其查询结果的准确性对用户至关重要。因此&#xff0c;在快速迭代的过程中&#xff0c;如何确保产品质量&#xff0c;成为我们面临的重大挑战。 随着 Da…

微信小程序基础bug

1.苹果11手机小程序请求数据不显示 设置-》隐私-》分析与改进-》开启 ”与开发者共享“ 2.<navigator>组件回退delta不成功 tabBar 页面是不能实现后退的效果的. 因为, 当我们跳转到 tabBar 页面&#xff0c;会关闭其他所有非tabBar 页面,所以当处于 tabBar 页面时, 无…