408考研-数据结构算法-顺序表

数组

如何创建数组

我们以 Java 中创建数组为例,创建语法如下

dataType[] arrName = new dataType[size];
  • dataType: 也就是我们数组中元素的数据类型
  • arrName:即数组名
  • size:即数组所能容纳的元素数量
  • new: Java 语言中的关键词

假设我们要创建一个由 10 个元素的数组,其中元素的数据类型为 int,则创建的方法如下

int[] arr = new int[19];

顺序表采用顺序存储方式,即逻辑上相邻的数据在计算机内的存储位置也是相邻的。顺序存储方式,元素存储是连续的,中间不允许有空,可以快速定位第几个元素,但是插入和删除时需要移动大量元素。根据分配空间方法不同,顺序表可以分为静态分配和动态分配两种方法。

静态分配

顺序表最简单的方法是使用一个定长数组data[]存储数据,最大空间为Maxsize,用length记录实际的元素个数,即顺序表的长度。这种用定长数组存储的方法称为静态分配。

在这里插入图片描述

顺序表的静态分配结构体定义,如图2-3所示。采用静态分配方法,定长数组需要预先分配一段固定大小的连续空间,但是在运算的过程中,如合并、插入等操作,容易超过预分配的空间长度,出现溢出。解决静态分配的溢出问题,可以采用动态分配的方法。

动态分配

在程序运行过程中,根据需要动态分配一段连续的空间(大小为Maxsize),用elem记录该空间的基地址(首地址),用length记录实际的元素个数,即顺序表的长度。动态顺序表。采用动态存储方法,在运算过程中,如果发生溢出,可以另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存储空间的目的。

在这里插入图片描述
注意哦,这里有两个概念“数组的长度”和“线性表的长度”需要区分一下。

数组的长度是存放线性表的存储空间的长度,存储分配后这个量是一般是不变的。

线性表的长度是线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是变化的。

在任意时刻,线性表的长度应该小于等于数组的长度。

数组基本操作

下面分别介绍数组的初始化、创建、取值、查找、插入、删除等基本操作。

1.初始化

初始化是指为顺序表分配一段预定义大小的连续空间,用elem记录这段空间的基地址,当前空间内没有任何数据元素,因此元素的实际个数为0。假设我们已经预定义了一个最大空间数Maxsize,那么就用new分配大小为Maxsize的空间,分配成功会返回空间的首地址,分配失败会返回空指针。

在这里插入图片描述

package com.maweiqi;public class ArrayDemo {public static void main(String[] args) {int[] arr = new int[10];int length = 0;for (int i = 0;i< 5;i++) {arr[length++] = i+1;}System.out.println("数组的容量:"+arr.length);System.out.println("数组的长度:"+length);}
}

创建数组

顺序表输入数据,输入数据的类型必须与类型定义中的类型一致。

算法步骤

1)初始化下标变量i=0,判断顺序表是否已满,如果是则结束;否则执行第2步。
2)输入一个数据元素x。
3)将数据x存入顺序表的第i个位置,即L.elem[i]=x,然后i++。
4)顺序表长度加1,即L.length++。
5)直到数据输入完毕。

1)输入元素:5。将数据元素5存入顺序表的第0个位置,即L.elem[0]=5,然后i++

在这里插入图片描述
2)输入元素:3。将数据元素3存入顺序表的第1个位置,即L.elem[1]=3,然后i++

在这里插入图片描述
3)输入元素:9。将数据元素9存入顺序表的第2个位置,即L.elem[2]=9,然后i++
在这里插入图片描述

插入元素到数组

在顺序表中第i个位置之前插入一个元素e,需要从最后一个元素开始,后移一位……直到把第i个元素也后移一位,然后把e放入第i个位置

在这里插入图片描述
算法步骤

1)判断插入位置i是否合法(1≤i≤L.length+1),可以在第一个元素之前插入,也可以在第L.length+1个元素之前插入。

2)判断顺序表的存储空间是否已满。

3)将第L.length至第i个元素依次向后移动一个位置,空出第i个位置。

4)将要插入的新元素e放入第i个位置。

5)表长加1,插入成功返回true。

例:顺序表中的第5个位置之前插入一个元素9。

在这里插入图片描述
插入过程如下。

1)移动元素。从最后一个元素(下标为L.length-1)开始后移一位,移动元素过程如下,一共移动四位

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
2)插入元素。此时第5个位置空出来,将要插入的新元素9放入第5个位置,表长加1

在这里插入图片描述

要插入元素到数组中,可以分为如下3 中情况
1.插入数组开头
2.插入数组结尾
3.插入数组中间

package com.maweiqi;import java.util.Arrays;public class AddArrDemo {/*** 将元素插入到中间*/public static int[] insertMid(int[] arr ,int index,int val){//创建一个新的数组,用于存放插入的元素int[] destArr = new int[arr.length + 1];//将原数组插入到元素位置前半段赋值给新数组for (int i = 0; i< index; i++) {destArr[i] = arr[i];}//将原数组插入到新数组的后半段for (int j = index; j < arr.length; j++) {destArr[j+1] = arr[j];}//将元素插入到新数组的指定位置destArr[index] = val;return destArr;}/*** 插入元素到数组的结尾*/public static int[] insertEnd(int[] arr ,int val){//创建一个新的数组,用于存放插入的元素int[] destArr = new int[arr.length + 1];//将元素插入到数组的结尾,同时将原数组整体复制到新数组destArr[arr.length] = val;for (int i = 0; i < arr.length; i++) {destArr[i] = arr[i];}return destArr;}/*** 插入元素到数组的开头*/public static int[] insertStart(int[] arr ,int val){//创建一个新的数组,用于存放插入的元素int[] destArr = new int[arr.length + 1];//将元素插入到数组的开头,同时将原数组整体复制到新数组destArr[0] = val;for (int i = 0; i < arr.length; i++) {destArr[i + 1] = arr[i];}return destArr;}public static void main(String[] args) {int[] arr = new int[10];for (int i = 0;i < arr.length; i++) {if(i== 5){break;}arr[i] = i+ 1;}// [1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
//        System.out.println(Arrays.toString(arr));//        int[] destArr01 = insertEnd(arr,11);//[1, 2, 3, 4, 5, 0, 0, 0, 0, 0, 11]
//         System.out.println(Arrays.toString(destArr01));//          int[] destArr02 = insertStart(arr,12);//[12, 1, 2, 3, 4, 5, 0, 0, 0, 0, 0]
//          System.out.println(Arrays.toString(destArr02));int[] destArr03 = insertMid(arr, 3 , 13);//[1, 2, 3, 13, 4, 5, 0, 0, 0, 0, 0]System.out.println(Arrays.toString(destArr03));}
}

删除数组中的元素

在顺序表中删除第i个元素,需要把该元素暂存到变量e中,然后从i+1个元素开始前移……直到把第n个元素也前移一位,即可完成删除操作

在这里插入图片描述
算法步骤

1)判断删除位置i是否合法(1≤i≤L.length)。
2)将欲删除的元素保存在e中。
3)将第i+1至第n个元素依次向前移动一个位置。
4)表长减1,删除成功,返回true。

删除第5个元素。

在这里插入图片描述

删除过程如下。

1)移动元素。首先将待删除元素2暂存到变量e中,以后可能有用,如果不暂存,将会被覆盖。然后从第6个元素开始前移一位,移动元素过程

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

2)表长减1,删除元素后的顺序表如图

在这里插入图片描述

同样的,假设我们要删除数组中的元素,也要考虑如下 3 种情况:

1.删除数组开头元素
2.删除数组末尾元素
3.删除数组中间元素

删除数组开头元素

删除开头元素,相当于将原数组开头元素后边的元素整体向前移动一位,而不用管开头的元素,

package com.maweiqi;import java.util.Arrays;public class DeleteArrDemo {/***删除数组开头的元素*/public static int[]deleteStart(int[]arr){//删除元素后,数组的容量减小int[] destArr = new int[arr.length - 1];//删除开头的元素,将后面的元素移动到前面for (int i = 1;i < arr.length; i++) {destArr[i - 1] = arr[i];}return destArr;}/***删除数组末尾的元素*/public static int[] deleteEnd(int[] arr){//删除元素后,数组的容量减小int[] destArr = new int[arr.length - 1];//删除末尾的元素for (int i = 0;i < arr.length - 1; i++){destArr[i] = arr[i];}return destArr;}/***删除元素从中间开始删除*/public static int[] deleteMid(int[] arr,int index){//删除元素后,数组的容量减小int[] destArr = new int[arr.length - 1];//删除指定位置元素,前半段保持for (int i =0;i < index; i++) {destArr[i] = arr[i];}//后半段整体向前移动一位for (int j = index; j < arr.length - 1 ; j++) {destArr[j] = arr[j + 1];}return destArr;}public static void main(String[] args) {int[] arr = new int[10];for (int i = 0; i < arr.length; i++) {arr[i] = i + 1;}// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]System.out.println(Arrays.toString(arr));//调用头部开始删除
//        int[] ints = deleteStart(arr);
//      [2, 3, 4, 5, 6, 7, 8, 9, 10]//       System.out.println(Arrays.toString(ints));//调用从结尾删除数组元素
//         int[] ints = deleteEnd(arr);//[1, 2, 3, 4, 5, 6, 7, 8, 9]
//         System.out.println(Arrays.toString(ints));//删除中间元素int[] ints1 = deleteMid(arr, 3);
//        [1, 2, 3, 5, 6, 7, 8, 9, 10]System.out.println(Arrays.toString(ints1));}
}

修改数组元素

要修改数组元素,实际上只要知道需要修改数组元素的索引位置即可,然后将对应索引位置的值修改为你要修改的值即可

package com.maweiqi;import java.util.Arrays;public class UpdateArrDemo {/*** 修改数组任意位置元素* @param arr 待修改元素的数组* @param index 待修改元素索引位置* @param val 修改后的元素值* @return 修改元素后的数组*/public static int[] update(int[] arr,int index,int val){arr[index] = val;return arr;}public static void main(String[] args) {int[] arr = new int[10];for (int i = 0; i < arr.length; i++) {arr[i] = i + 1;}// [1, 2, 3, 4, 5, 6, 7, 8, 9, 10]System.out.println(Arrays.toString(arr));//修改数组任意位置元素int[] ints1 = update(arr, 3,100);
//        [1, 2, 3, 5, 6, 7, 8, 9, 10]System.out.println(Arrays.toString(ints1));}}

查找数组中的元素

要查找数组中的某一个元素,最常用的方法有如下两种

线性查找,主要针对数组较小时
二分查找,主要针对数组较大时,提高查询效率

线性查找

在顺序表中查找一个元素e,可以从第一个元素开始顺序查找,依次比较每一个元素值。如果相等,则返回元素位置(位序,即第几个元素);如果查找整个顺序表都没找到,则返回-1。例如,下图的顺序表中,查找元素8,查找到其下标为5,返回位序为6。

在这里插入图片描述

线性查找即遍历数组,然后判断各元素是否是目标值,是则输出对应索引位置,否则返回1,此时时间复杂度为O(n);

package com.maweiqi;public class SearchArrDemo {/***查找元素的方法*/public static int linearSearch(int[] array ,int target){//查找到目标值时,返回目标索引for (int i = 0; i < array.length; i++) {if(target == array[i]){return i;}}return -1;}public static void main(String[] args) {int[] arr = {2,3,5,7,8,11,15,18};int index = linearSearch(arr,7) ;
//        返回的索引为:3
//        找到的值时:7System.out.println("返回的索引为:"+index);System.out.println("找到的值时:"+arr[index]);}
}

二分查找

你小时候或许玩过这样一种猜谜游戏:我心里想着一个1到100之间的数字,在你猜出它之前,我会提示你的答案应该大一点还是小一点。

你应该凭直觉就知道这个游戏的策略。一开始你会先猜处于中间的50,而不是1。为什么?因为不管我接下来告诉你更大或是更小,你都能排除掉一半的错误答案!

如果你说50,然后我提示要再大一点,那么你应该会选75,以排除掉剩余数字的一半。如果在75之后我告诉你要小一点,你就会选62或63。总之,一直都猜中间值,就能不断地缩小一半的范围。

下面来演示这个过程,但仅以1到10为例。

在这里插入图片描述
这就是二分查找的通俗描述。

有序数组相比常规数组的一大优势就是它除了可以用线性查找,还可以用二分查找。常规数组因为无序,所以不可能运用二分查找。

为了看出它的实际效果,假设有一个包含9个元素的有序数组。计算机不知道每个格子的值,如下图所示。

在这里插入图片描述

然后,用二分查找来找出7,过程如下。

第1步:检查正中间的格子。因为数组的长度是已知的,将长度除以2,我们就可以跳到确切的内存地址上,然后检查其值。

在这里插入图片描述

值为9,可推测出7应该在其左边的某个格子里。而且,这下我们也排除了一半的格子,即9右边的那些(以及9本身)。

在这里插入图片描述
第2步:检查9左边的那些格子的最中间那个。因为这里最中间有两个,我们就随便挑了左边的。

在这里插入图片描述

它的值为4,那么7就在它的右边了。由此4左边的格子也就排除了。

在这里插入图片描述

第3步:还剩两个格子里可能有7。我们随便挑个左边的。

在这里插入图片描述
第4步:就剩一个了。(如果还没有,那就说明这个有序数组里真的没有7。)

在这里插入图片描述
终于找到7了,总共4步。是的,这个有序数组要是用线性查找也会是4步,但稍后你就会见识到二分查找的强大。

当数组长度很小时,使用线性查找方法很快就能找到目标值是否存在并返回对应索引位置但当数组很大时,线性查找的方法效率就太低了。这时候二分查找是更理想的查找手段,二分查找实质是使用双指针,每次对半查找,大大提高效率,时间复杂度缩减为o(logn);

package com.maweiqi;public class BinarySearchDemo {//编写二分查找的方法public static int binarySearch(int[] array ,int target){//左指针和右指针int left = 0;int right = array.length - 1;while (left <= right) {//计算midint mid = left + (right - left) / 2;//当前值等于目标值,直接返回目标的索引即可if(array[mid] == target){return mid;}else if (array[mid] < target){//当前值小于目标值,左指针向右移动一位left = mid + 1;}else {//当前值大于目标值,,右指针向左移动一位right = mid - 1;}}return -1;}public static void main(String[] args) {int[] arr = {-1,2,3,5,7,11,18,21,33,56,78,98};//2
//        int index = binarySearch(arr,3);//-1int index = binarySearch(arr,13);System.out.println(index);}
}

顺序表的优点:操作简单,存储密度高,可以随机存取,只需要O(1)的时间就可以取出第i个元素。

顺序表的缺点:需要预先分配最大空间,最大空间数估计过大或过小会造成空间浪费或溢出。插入和删除操作需要移动大量元素。

在实际问题中,如果经常需要插入和删除操作,则顺序表的效率很低。为了克服该缺点,可以采用链式存储。

二分查找与线性查找区别

对于长度太小的有序数组,二分查找并不比线性查找好多少。但我们来看看更大的数组。

对于拥有100个值的数组来说,两种查找需要的最多步数如下所示。

❏ 线性查找:100步

❏ 二分查找:7步

用线性查找的话,如果要找的值在最后一个格子,或者比最后一格的值还大,那么就得查遍每个格子。有100个格子,就是100步。

二分查找则会在每次猜测后排除掉一半的元素。100个格子,在第一次猜测后,便排除了50个。

再换个角度来看,你就会发现一个规律。

长度为3的有序数组,二分查找所需的最多步数是2。

若长度翻倍,变成7(以奇数为例会方便选择正中间的格子,于是我们把长度翻倍后又增加了一个数),则最多步数会是3。

若再翻倍(并加1),变成15个元素,那么最多步数会是4。

规律就是,每次有序数组长度乘以2,二分查找所需的最多步数只会加1。这真是出奇地高效。

相反,在3个元素的数组上线性查找,最多要3步,7个元素就最多要7步,100个元素就最多要100步,即元素有多少,最多步数就是多少。数组长度翻倍,线性查找的最多步数就会翻倍,而二分查找则只是增加1步。

这种规律可以用下图来展示。

在这里插入图片描述

如果数组变得更大,比如说10000个元素,那么线性查找最多会有10000步,而二分查找最多只有14步。再增大到1000000个元素,则线性查找最多有1000000步,二分查找最多只有20步。

不过还要记住,有序数组并不是所有操作都比常规数组要快。如你所见,它的插入就相对要慢。衡量起来,虽然插入是慢了一些,但查找却快了许多。还是那句话,你得根据应用场景来判断哪种更合适。

总结

关于算法的内容就是这些。很多时候,计算一样东西并不只有一种方法,换种算法可能会极大地影响程序的性能。

同时你还应意识到,世界上并没有哪种适用于所有场景的数据结构或者算法。你不能因为有序数组能使用二分查找就永远只用有序数组。在经常插入而很少查找的情况下,显然插入迅速的常规数组会是更好的选择。

如之前所述,比较算法的方式就是比较各自的步数。

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

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

相关文章

blender 火焰粒子

效果展示 创建火焰模型 新建立方体&#xff08;shift A &#xff09;,添加表面细分修改器&#xff08;ctrl 2 &#xff09;&#xff0c;视图层级调整为 3 &#xff0c;这样布线更密集&#xff1b; 右键将模型转换为网格&#xff0c;tab 进入编辑模式&#xff0c;7 切换到顶…

五、多表查询-4.1子查询和分类

一、概念 SQL语句中嵌套select语句&#xff0c;成为嵌套查询&#xff0c;又称子查询。 子查询外部的语句 可以是 insert / update / delete / select 的任何一个。 二、子查询分类 1、根据子查询结果不同 标量子查询&#xff08;子查询结果为单个值&#xff09;、列子查询&a…

创建python环境——Anaconda

在Windows中安装Anaconda和简单使用 一.Anaconda发行概述 Anaconda是一个可以便捷获取和管理包&#xff0c;同时对环境进行统一管理的发行版本&#xff0c;它包含了conda、 Python在内的超过180个科学包及其依赖项。 1.Anaconda发行版本具有以下特点&#xff1a; (1)包含了…

1.RabbitMQ介绍

一、MQ是什么&#xff1f;为什么使用它 MQ&#xff08;Message Queue&#xff0c;简称MQ&#xff09;被称为消息队列。 是一种用于在应用程序之间传递消息的通信方式。它是一种异步通信模式&#xff0c;允许不同的应用程序、服务或组件之间通过将消息放入队列中来进行通信。这…

DP读书:不知道干什么就和我一起读书吧

DP读书&#xff1a;不知道干什么就和我一起读书吧 为啥写博客&#xff1a;好处一&#xff1a;记录自己的学习过程优点二&#xff1a;让自己在各大社群里不那么尴尬推荐三&#xff1a;坚持下去&#xff0c;找到一个能支持自己的伙伴 虽然清楚知识需要靠时间沉淀&#xff0c;但在…

Ansible项目实战管理/了解项目环境/项目管理

一&#xff0c;项目环境 1.项目基础 项目过程 调研阶段 设计阶段 开发阶段 测试阶段 运营阶段 2.项目环境 个人开发环境 公司开发环境 项目测试环境 项目预发布环境 灰度环境&#xff1a;本身是生产环境&#xff0c;安装项目规划&#xff0c;最终所有的生产环境都发…

Android JNI系列详解之CMake配置库文件的输出目录

一、前提 阅读上一篇文章Android JNI系列详解之CMake编译工具的使用&#xff0c;里面讲到了需要配置两个文件&#xff1a;CMakeList.txt和build.gradle 二、配置CMake编译工具输出库文件的路径 1.默认的库文件输出路径&#xff1a;app/build/intermediates/cmake/debug/obj 由此…

QtCreator 界面程序 无法输入中文问题

链接: https://pan.baidu.com/s/1cqAI8A 密码: j3jq 下载上面链接里的libfcitxplatforminputcontextplugin.so库&#xff0c; 然后拷贝到Qt的如下3个目录里&#xff0c; /qt/qt5.14.2_soft/Tools/QtCreator/lib/Qt/plugins/platforminputcontexts //需要修改文件权限 /qt/qt…

基于Kohonen网络的聚类算法

1.案例背景 1.1 Kohonen网络 Kohonen网络是自组织竞争型神经网络的一种,该网络为无监督学习网络,能够识别环境特征并自动聚类。Kohonen神经网络是芬兰赫尔辛基大学教授Teuvo Kohonen 提出的,该网络通过自组织特征映射调整网络权值,使神经网络收敛于一种表示形态。在这一形态中…

小程序中如何给会员发送微信服务通知

通过发送微信服务通知&#xff0c;可以及时向会员推送最新的活动、优惠信息等重要通知&#xff0c;从而增加用户参与度和购买意愿。下面就介绍怎么给会员发送微信服务通知的方法和步骤。 1. 找到指定的会员卡。在管理员后台->会员管理处&#xff0c;找到需要接收服务通知的…

非煤矿山风险监测预警算法 yolov8

非煤矿山风险监测预警算法通过yolov8网络模型深度学习算法框架&#xff0c;非煤矿山风险监测预警算法在煤矿关键地点安装摄像机等设备利用智能化视频识别技术&#xff0c;能够实时分析人员出入井口的情况&#xff0c;人数变化并检测作业状态。YOLO的结构非常简单&#xff0c;就…

FPGA原理与结构——时钟IP核原理学习

一、前言 在之前的文章中&#xff0c;我们介绍了FPGA的时钟结构 FPGA原理与结构——时钟资源https://blog.csdn.net/apple_53311083/article/details/132307564?spm1001.2014.3001.5502 在本文中我们将学习xilinx系列的FPGA所提供的时钟IP核&#xff0c;来帮助我们进一…