数组中元素的插入和查找算法探究

数组的查找

线性查找

概念

线性查找也叫顺序查找,这是最基本的一种查找方法,从给定的值中进行搜索,从一端开始逐一检查每个元素,直到找到所需元素的过程。

元素序列的排列可以有序,也可以无序。

代码实现

public class Test01 {public static void main(String[] args) {//线性查找int[] arr = {45, 62, 15,62, 78, 30};int index = sequentialSearch01(arr, 62);System.out.println("指定元素首次出现的下标位置:" + index);List<Integer> indexList = sequentialSearch02(arr, 62);System.out.println("指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray()));}/*** 顺序查找* 返回指定元素首次出现的下标位置*/public static int sequentialSearch01(int[] arr,int value){for (int i = 0; i < arr.length; i++) {if(arr[i] == value){return i;}}return -1;}/*** 顺序查找* 返回指定元素出现的下标位置的集合*/public static List<Integer> sequentialSearch02(int[] arr,int value){List<Integer> list = new ArrayList<>();for (int i = 0; i < arr.length; i++) {if(arr[i] == value){list.adds(i);}}return list;}
}

二分法查找

概念

二分查找(Binary Search)算法,也叫折半查找算法。

当要从一个序列中查找一个元素的时候,二分查找是一种非常快速的查找算法。

二分查找是针对有序数据集合的查找算法,如果是无序数据集合就遍历查找。

二分查找之所以快速,是因为它在匹配不成功的时候,每次都能排除剩余元素中一半的元素。因此可能包含目标元素的有效范围就收缩得很快,而不像顺序查找那样,每次仅能排除一个元素。

原理

比如有一个有序表数组[1,3,5,7,9,11,13,15,17,19,21],它是按照从小到大的顺序来进行排列的,现在需要在该有序表中查找元素19,步骤如下:

  1. 首先设置两个指针low和high,分别指向数据集合的第一个数据元素1(位序为0)和最后一个数据元素21(位序为10)。

然后把整个数据集合长度分成两半,并用一个指针指向它们的临界点,所以定义指针mid指向了中间元素11(位序5),也就是说mid=(high+low)/2,其中high和low都代表所指向的元素的位序,如下图:

在这里插入图片描述

  1. 接着,将mid所指向的元素(11)与待查找元素(19)进行比较。

因为19大于11,说明待查找的元素(19)一定位于mid和high之间。所以继续折半,则low = mid+1,而mid = (low+high)/2,结果如下图:

在这里插入图片描述

  1. 接着,又将mid所指向的元素(17)与待查找元素(19)进行比较,由于19大于17,所以继续折半,则low = mid+1,而mid = (low+high)/2,结果如下图:
    在这里插入图片描述
  1. 最后,又将mid所指向的元素(19)与待查找元素(19)进行比较,结果相等,则查找成功,返回mid指针指向的元素的位序。

如果查找的元素值不是19,而是20,那么在最后一步之前还得继续折半查找,最后出现的情况如下图:

在这里插入图片描述

代码实现

public class Test01 {public static void main(String[] args) {//二分法查找int[] arr = {1,2,3,4,5,6,7,8,9,11,11,11,11,11,11};int index = binarySearch01(arr, 11);System.out.println("指定元素出现的下标位置:" + index);List<Integer> indexList = binarySearch02(arr, 11);System.out.println("指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray()));index = recursionbinarySearch01(arr, 0, arr.length-1, 11);System.out.println("递归方式 - 指定元素出现的下标位置:" + index);indexList = recursionbinarySearch02(arr, 0, arr.length-1, 11);System.out.println("递归方式 - 指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray()));}/*** 有序的数组中查找某个元素出现的下标位置* 不使用递归的二分查找* 返回出现的下标位置*/public static int binarySearch01(int[] arr,int val){int low = 0;int high = arr.length-1;while(low <= high){int mid = (low + high)/2;if(val > arr[mid]){//目标在右侧low = mid+1;}else if(val < arr[mid]){//目标在左侧high = mid-1;}else{return mid;}}return -1;}/*** 有序的数组中查找某个元素首次出现的下标位置* 不使用递归的二分查找* 返回下标集合*/public static List<Integer> binarySearch02(int[] arr,int val){int low = 0;int high = arr.length-1;while(low <= high){int mid = (low + high)/2;if(val > arr[mid]){//目标在右侧low = mid+1;}else if(val < arr[mid]){//目标在左侧high = mid-1;}else{// 定义放置索引下标的集合List<Integer> list = new ArrayList<>();// 将首次查找的位置放入集合list.add(mid);// 判断是否还有重复值int index = mid + 1;while(index < arr.length){if(arr[index] == val){list.add(index);}else{break;}index++;}index = mid-1;while(index >= 0){if(arr[index] == val){list.add(index);}else{break;}index--;}return list;}}return null;}/*** 有序的数组中查找某个元素出现的下标位置* 使用递归的二分查找* 返回出现的下标位置*/public static int recursionbinarySearch01(int[] arr,int low,int high,int val){if(val < arr[low] || val > arr[high] || low > high){return -1;}int mid = (low + high)/2;if(val > arr[mid]){//目标在右侧return recursionbinarySearch01(arr, mid+1, high, val);}else if(val < arr[mid]){//目标在左侧return recursionbinarySearch01(arr, low, mid-1, val);}else{return mid;}}/*** 有序的数组中查找某个元素首次出现的下标位置* 使用递归的二分查找* 返回下标集合*/public static List<Integer> recursionbinarySearch02(int[] arr,int low,int high,int val){if(val < arr[low] || val > arr[high] || low > high){return null;}int mid = (low + high)/2;if(val > arr[mid]){//目标在右侧return recursionbinarySearch02(arr, mid+1, high, val);}else if(val < arr[mid]){//目标在左侧return recursionbinarySearch02(arr, low, mid-1, val);}else{// 定义放置索引下标的集合List<Integer> list = new ArrayList<>();// 将首次查找的位置放入集合list.add(mid);// 判断是否还有重复值int index = mid + 1;while(index < arr.length){if(arr[index] == val){list.add(index);}else{break;}index++;}index = mid-1;while(index >= 0){if(arr[index] == val){list.add(index);}else{break;}index--;}return list;}}
}

优缺点

优点:速度快,不占空间,不开辟新空间

缺点:必须是有序的数组,数据量太小没有意义

插值查找

概念

从折半查找中可以看出,折半查找的查找效率还是不错的。可是为什么要折半呢?为什么不是四分之一、八分之一呢?

打个比方,在牛津词典里要查找“apple”这个单词,会首先翻开字典的中间部分,然后继续折半吗?肯定不会,对于查找单词“apple”,我们肯定是下意识的往字典的最前部分翻去,而查找单词“zero”则相反,我们会下意识的往字典的最后部分翻去。

所以在折半查找法的基础上进行改造就出现了插值查找法,也叫做按比例查找。所以插值查找与折半查找唯一不同的是在于mid的计算方式上,它的计算方式为:

int mid = low + (high - low) * (val- arr[low]) / (arr[high] - arr[low])

这样就能快速定位目标数值所在的索引,比二分查找可以更快速实现查找。

自适应获取mid,也就是自适应查找点。

代码实现

public class Test01 {public static void main(String[] args) {//插值查找int[] arr = {1,2,3,4,5,6,7,8,9,11,11,11,11,11,11};int index = insertSearch01(arr, 11);System.out.println("指定元素出现的下标位置:" + index);List<Integer> indexList = insertSearch02(arr, 11);System.out.println("指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray()));index = recursionInsertSearch01(arr, 0, arr.length-1, 11);System.out.println("递归方式 - 指定元素出现的下标位置:" + index);indexList = recursionInsertSearch02(arr, 0, arr.length-1, 11);System.out.println("递归方式 - 指定元素出现的下标位置的集合:" + Arrays.toString(indexList.toArray()));}/*** 有序的数组中查找某个元素出现的下标位置* 不使用递归的二分查找* 返回出现的下标位置*/public static int insertSearch01(int[] arr,int val){int low = 0;int high = arr.length-1;while(low <= high){int mid = low + (high - low) * (val - arr[low])/(arr[high] - arr[low]);if(val > arr[mid]){//目标在右侧low = mid+1;}else if(val < arr[mid]){//目标在左侧high = mid-1;}else{return mid;}}return -1;}/*** 有序的数组中查找某个元素首次出现的下标位置* 不使用递归的二分查找* 返回下标集合*/public static List<Integer> insertSearch02(int[] arr,int val){int low = 0;int high = arr.length-1;while(low <= high){int mid = low + (high - low) * (val - arr[low])/(arr[high] - arr[low]);if(val > arr[mid]){//目标在右侧low = mid+1;}else if(val < arr[mid]){//目标在左侧high = mid-1;}else{// 定义放置索引下标的集合List<Integer> list = new ArrayList<>();// 将首次查找的位置放入集合list.add(mid);// 判断是否还有重复值int index = mid + 1;while(index < arr.length){if(arr[index] == val){list.add(index);}else{break;}index++;}index = mid-1;while(index >= 0){if(arr[index] == val){list.add(index);}else{break;}index--;}return list;}}return null;}/*** 有序的数组中查找某个元素出现的下标位置* 使用递归的二分查找* 返回出现的下标位置*/public static int recursionInsertSearch01(int[] arr,int low,int high,int val){if(val < arr[low] || val > arr[high] || low > high){return -1;}int mid = low + (high - low) * (val - arr[low])/(arr[high] - arr[low]);if(val > arr[mid]){//目标在右侧return recursionInsertSearch01(arr, mid+1, high, val);}else if(val < arr[mid]){//目标在左侧return recursionInsertSearch01(arr, low, mid-1, val);}else{return mid;}}/*** 有序的数组中查找某个元素首次出现的下标位置* 使用递归的二分查找* 返回下标集合*/public static List<Integer> recursionInsertSearch02(int[] arr,int low,int high,int val){if(val < arr[low] || val > arr[high] || low > high){return null;}int mid = low + (high - low) * (val - arr[low])/(arr[high] - arr[low]);if(val > arr[mid]){//目标在右侧return recursionInsertSearch02(arr, mid+1, high, val);}else if(val < arr[mid]){//目标在左侧return recursionInsertSearch02(arr, low, mid-1, val);}else{// 定义放置索引下标的集合List<Integer> list = new ArrayList<>();// 将首次查找的位置放入集合list.add(mid);// 判断是否还有重复值int index = mid + 1;while(index < arr.length){if(arr[index] == val){list.add(index);}else{break;}index++;}index = mid-1;while(index >= 0){if(arr[index] == val){list.add(index);}else{break;}index--;}return list;}}
}

斐波那契查找

概念

斐波那契查找也叫做黄金分割法查找。

斐波那契查找也是二分查找的一种提升算法,通过运用黄金比例的概念在数列中选择查找点进行查找,提高查找效率。同样地,斐波那契查找也属于一种有序查找算法。

原理

对于斐波那契数列:1、1、2、3、5、8、13、21、34、55、89……(也可以从0开始),前后两个数字的比值随着数列的增加,越来越接近黄金比值0.618。

比如元素个数为89的有序列表。89在斐波那契数列中是34和55相加所得。

把元素个数为89的有序列表分成:前55个数据元素组成的前半段和34个数据元素组成的后半段。那么前半段元素个数和整个有序表长度的比值接近黄金比值0.618,而前后两段长度的比值也接近黄金比值0.618。

假如要查找的元素在前半段,那么继续按照斐波那契数列来看,55 = 34 + 21,所以继续把前半段分成前34个数据元素的前半段和后21个元素的后半段,继续查找,如此反复,直到查找成功或失败。这样斐波那契数列就被应用到查找算法中了。

总长度=f[k],

前半段长度=f[k-1],后半段长度=f[k-2]

在这里插入图片描述

有序列表的元素个数不是斐波那契数列中的数字时该如何处理呢?

当有序表的元素个数不是斐波那契数列中的某个数字时,需要把有序列表的长度补齐,让它成为斐波那契数列中的一个数值。

如果不是补齐,而是将多余的截掉是否可行?把原有序列表截断肯定是不可行的,因为可能把要查找的目标值截掉。

每次取斐波那契数列中的某个值时(f[k]),都会进行-1操作,这是因为数组下标从0开始。

代码实现


public class Test01 {public static void main(String[] args) {int[] arr = {1,13,25,37,49,51,62,68,70,80,80};List<Integer> fiboSearchList = fiboSearchList(arr, 80);System.out.println(Arrays.toString(fiboSearchList.toArray()));}public static List<Integer> fiboSearchList(int[] arr, int val) {int low = 0;int high = arr.length-1;// 斐波那契的索引下标。数组长度的数值在斐波那契数列中对应的索引下标int[] fiboArray = getFiboArray(10);//[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]// 斐波那契的索引下标。数组长度的数值在斐波那契数列中对应的索引下标int k = 0;// 斐波那契的索引下标。数组长度的数值在斐波那契数列中对应的索引下标while(arr.length > fiboArray[k]){k++;}System.out.println("k = " + k);//6System.out.println("fiboArray = " + Arrays.toString(fiboArray));//[1, 1, 2, 3, 5, 8, 13, 21, 34, 55]// 利用Java工具类Arrays 构造新数组并指向 数组 arr[]int[] temp = Arrays.copyOf(arr, fiboArray[k]);System.out.println("temp=" + Arrays.toString(temp));//[1, 13, 25, 37, 49, 51, 62, 68, 70, 80, 80, 0, 0]//对新构造的数组进行元素补充,补充为最高位的数值for (int i = high+1; i < temp.length; i++) {temp[i] = arr[high];}System.out.println("补充数值的temp=" + Arrays.toString(temp));//[1, 13, 25, 37, 49, 51, 62, 68, 70, 80, 80, 80, 80]while(low <= high){//数列左侧有f[k-1]个元素int mid = low + fiboArray[k-1] - 1;if(val < temp[mid]){// 目标值小于mid所在元素,在左侧查找high = mid-1;/*全部元素=前部元素+后部元素* f[k]=f[k-1]+f[k-2]* 因为左侧有f[k-1]个元素,所以可以继续拆分f[k-1]=f[k-2]+f[k-3]* 即在f[k-1]的前部继续查找 所以k-=1* 即下次循环 mid=f[k-1-1]-1*/k-=1;}else if(val > temp[mid]){// 目标值大于mid所在元素,在右侧查找low = mid+1;/*全部元素=前部元素+后部元素* f[k]=f[k-1]+f[k-2]* 因为右侧有f[k-2]个元素,所以可以继续拆分f[k-2]=f[k-3]+f[k-4]* 即在f[k-2]的前部继续查找 所以k-=2* 即下次循环 mid=f[k-1-2]-1*/k -= 2;}else{// 定义放置索引下标的集合ArrayList<Integer> list = new ArrayList<>();list.add(mid);int index = mid+1;while(index < arr.length){if(arr[index] == val){list.add(index);index++;}else{break;}}index = mid-1;while(index > 0){if(arr[index] == val){list.add(index);index--;}else{break;}}return list;}}return null;}public static int[] getFiboArray(int maxSize){int[] fiboArray = new int[maxSize];fiboArray[0] = 1;fiboArray[1] = 1;for (int i = 2; i < fiboArray.length; i++) {fiboArray[i] = fiboArray[i-1] + fiboArray[i-2];}return fiboArray;}
}

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

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

相关文章

JVM,Java堆区、新生代、老年代,创建对象的内存分配,分代垃圾收集思想、堆区产生的错误

JVM堆区 堆&#xff08;Heap&#xff09;堆区的组成&#xff1a;新生代老年代堆空间的大小设置创建对象的内存分配堆区的分代垃圾收集思想堆区产生的错误 堆&#xff08;Heap&#xff09; ​ Heap堆区&#xff0c;用于存放对象实例和数组的内存区域 ​ Heap堆区&#xff0c;是…

Vue2:通过props给组件传数据

一、业务场景 我们在使用Vue组件时&#xff0c;常常会复用Vue组件&#xff0c;那么&#xff0c;问题来了&#xff0c;复用的时候&#xff0c;业务数据不相同&#xff0c;怎么办了&#xff1f; 这里我们就需要学习新的属性&#xff1a;props来实现这个功能。 这样&#xff0c;组…

探寻未来卫生新境界:互联网公厕是什么意思

近年来&#xff0c;科技的飞速发展深刻改变了我们生活的方方面面&#xff0c;而公共卫生领域也在这场变革中迎来了一场前所未有的革命。在这个新时代&#xff0c;一个备受瞩目的概念逐渐崭露头角——那就是“互联网公厕”。这究竟意味着什么&#xff1f;是一场卫生革新的崛起&a…

swaggerUI不好用,试试这个openapiUI?

title: swaggerUI不好用&#xff0c;试试这个openapiUI? date: 2024-01-08 categories: [tool] tags: [openapi,工具] description: 基于swaggger2, openapi3规范的UI文档 1.背景 由于长期使用 swaggerUI 工具&#xff0c;它的轻量风格个人觉得还是不错的&#xff0c;但是它…

【漏洞复现】Hikvision SPON IP网络对讲广播系统存在命令执行漏洞CVE-2023-6895

漏洞描述 Hikvision Intercom Broadcasting System是中国海康威视(Hikvision)公司的一个对讲广播系统。 Hikvision Intercom Broadcasting System是中国海康威视(Hikvision)公司的一个对讲广播系统。Hikvision Intercom Broadcasting System 3.0.3_20201113_RELEASE(HIK)版…

vulhub中的Apache SSI 远程命令执行漏洞

Apache SSI 远程命令执行漏洞 1.cd到ssi-rce cd /opt/vulhub/httpd/ssi-rce/ 2.执行docker-compose up -d docker-compose up -d 3.查看靶场是否开启成功 dooker ps 拉取成功了 4.访问url 这里已经执行成功了&#xff0c;注意这里需要加入/upload.php 5.写入一句话木马 &…

微信预约小程序制作指南:从小白到专家

在当今的数字时代&#xff0c;微信小程序已经成为了一种非常流行的应用方式。预约功能更是成为了许多小程序的核心功能之一。如果你也想为你的小程序添加预约功能&#xff0c;以下步骤将会对你有所帮助。 一、进入乔拓云网后台 乔拓云网是一个在线小程序开发平台&#xff0c;你…

如何使用Docker部署开源CMF Drupal并结合cpolar内网穿透远程访问

文章目录 前言1. Docker安装Drupal2. 本地局域网访问3 . Linux 安装cpolar4. 配置Drupal公网访问地址5. 公网远程访问Drupal6. 固定Drupal 公网地址 前言 Dupal是一个强大的CMS&#xff0c;适用于各种不同的网站项目&#xff0c;从小型个人博客到大型企业级门户网站。它的学习…

【软考中级-软件设计师】day4:数据结构-线性表、单链表、栈和队列、串

大纲 线性结构 顺序存储和链式存储区别 单链表的插入和删除 真题 栈和队列 真题 串

kubectlkubeletrancherhelmkubeadm这几个命令行工具是什么关系?

背景 在最近学习k8s的过程中&#xff0c;发现kubectl&kubelet&rancher&helm&kubeadm这几个命令怎么在交错使用&#xff0c;他们究竟是什么关系&#xff1f;他们分别应该在什么情况下使用呢&#xff1f;这里我进行了简单的总结&#xff0c;做个区分。 各工具说…

性能分析与调优: Linux 实现 缺页剖析与火焰图

目录 一、实验 1.环境 2.缺页(RSS增长)剖析与火焰图 一、实验 1.环境 &#xff08;1&#xff09;主机 表1-1 主机 主机架构组件IP备注prometheus 监测 系统 prometheus、node_exporter 192.168.204.18grafana监测GUIgrafana192.168.204.19agent 监测 主机 node_exporter…

实现锚点定位功能(React/Vue)

前言 最近接到一个需求&#xff0c;修改某某页面&#xff0c;增加XXX功能&#xff0c;并实现个锚点功能。做产品就是不断优化&#xff0c;增加功能的过程。实现锚点的方式很多&#xff0c; 很多UI库也提供了组件&#xff0c;可以根据自己的需求调整一下组件库也可以实现&#…

vulhub中的Apache HTTPD 换行解析漏洞(CVE-2017-15715)详解

Apache HTTPD 换行解析漏洞&#xff08;CVE-2017-15715&#xff09; 1.cd到CVE-2017-15715 cd vulhub/httpd/CVE-2017-15715 2.运行docker-compose build docker-compose build 3.运行docker-compose up -d 4.查看docker-compose ps 5.访问 出现这个表示安装成功 6.漏洞复现…

Linux系统操作命令

Linux管理 在线查询Linux命令&#xff1a; https://www.runoob.com/linux/linux-install.htmlhttps://www.linuxcool.com/https://man.linuxde.net/ 1.Linux系统目录结构 Linux系统的目录结构是一个树状结构&#xff0c;每一个文件或目录都从根目录开始&#xff0c;并且根目…

MySQL语法练习-DML语法练习

文章目录 0、相关文章1、添加数据2、修改数据3、删除数据4、总结 0、相关文章 《MySQL练习-DDL语法练习》 1、添加数据 # 给指定字段添加数据 insert into 表名 (字段名1,字段名2,...) values(值1,值2...);# 给全部字段添加数据 insert into 表名 values(值1,值2,...);#批量…

Docker查看镜像的Dockerfile

前言 在使用Docker构建应用程序时&#xff0c;我们可以通过Dockerfile定义应用程序的环境&#xff0c;并将其打包成一个镜像。有时&#xff0c;我们可能需要查看一个已经构建好的镜像的Dockerfile&#xff0c;以了解镜像是如何构建的&#xff0c;或者进行后续的修改和调整。本…

python股票分析挖掘预测技术指标知识之蜡烛图指标(6)

本人股市多年的老韭菜&#xff0c;各种股票分析书籍&#xff0c;技术指标书籍阅历无数&#xff0c;萌发想法&#xff0c;何不自己开发个股票预测分析软件&#xff0c;选择python因为够强大&#xff0c;它提供了很多高效便捷的数据分析工具包。 我们已经初步的接触与学习其中数…

利用格式工厂,做视频的剪辑

接到一个工作&#xff0c;一段视频中&#xff0c;需要抠除其中某一段 其实 剪映、苹果手机的视频编辑功能&#xff0c;都可以轻松搞定 只是清晰度会有损伤 而且对于太大的视频&#xff0c;苹果手机就没法处理了。 很多软件在导出高清视频时&#xff0c;需要会员收费&#xff0…

Java学习笔记-day02-在IDEA中使用git忽略提交.idea下的文件

1.在根目录.gitignore文件排除.idea目录 ### IntelliJ IDEA ### .idea2.使用重置Head还原已经add过的文件 创建项目时&#xff0c;可能会有.idea中的文件先add到git后再创建的.gitignore文件&#xff0c;导致文件commit时无法排除&#xff0c;如下所示。 使用重置Head将文件…

深度学习 常考简答题--[HBU]期末复习

目录 1.为什么要引用非线性激活函数&#xff1f; 2.什么是超参数&#xff1f;如何优化超参数&#xff1f; 3.线性回归通常使用平方损失函数&#xff0c;能否使用交叉熵损失函数&#xff1f; 4.平方损失函数为何不适用于解决分类问题&#xff1f;(和第3题一块复习) ​编辑 …