一篇看懂顺序表!!(刘欣大佬《码农翻身》特别提及)
顺序表
1.1定义
顺序表是用一段物理地址(内存)连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成数 据的增删查改。
1.2分类
顺序表一般可以分为:
静态顺序表:使用定长数组存储。
动态顺序表:使用动态开辟的数组存储。
静态顺序表适用于确定知道需要存多少数据的场景.
静态顺序表的定长数组导致N定大了,空间开多了浪费,开少了不够用.
相比之下动态顺序表更灵活, 根据需要动态的分配空间大小.
1.3自己动手实现一个顺序表
我依稀还记得自己动手实现一个顺序表的代码出现在了刘欣老师的《码农翻身》一书当中,当时留心老师的书中就要求自己去动手实现一个顺序表出来
下来我先将要自己实现的接口(方法)列举出来:
// 打印顺序表
public void display() { }
// 在 pos 位置新增元素
public void add(int pos, int data) { }
// 判定是否包含某个元素
public boolean contains(int toFind) { return true; }
// 查找某个元素对应的位置
public int search(int toFind) { return -1; }
// 获取 pos 位置的元素
public int getPos(int pos) { return -1; }
// 给 pos 位置的元素设为 value
public void setPos(int pos, int value) { }
//删除第一次出现的关键字key
public void remove(int toRemove) { }
// 获取顺序表长度
public int size() { return 0; }
// 清空顺序表
public void clear() { }
代码示例:
顺序表实现核心代码:
import java.util.Arrays;public class MyArrayList {//定义一个数组private int[] elem;//usedSize数据用于计算数组中有效数据的个数private int usedSize;//利用构造方法来进行elem这个实例成员变量的初始化public MyArrayList() {this.elem = new int[10];}public MyArrayList(int capacity) {this.elem = new int[capacity];}//在 pos 位置新增元素public boolean isFull() {if (this.usedSize == this.elem.length) {//代表顺序表已满return true;}return false;}//定义一个数组扩容方法resizepublic void resize() {System.out.println("满了就进行扩容,扩大两倍");//调用copyOf方法进行扩容this.elem = Arrays.copyOf(this.elem, this.elem.length * 2);}public void add(int pos, int data) {//如果顺序表满,则不能插入if (isFull()) {System.out.println("顺序表已满");//如果此时顺序表满了,调用resize方法来进行扩容resize();}if (pos < 0 || pos > this.usedSize) {System.out.println("pos位置不合法");return;}//插入后,其他元素后移for (int i = this.usedSize - 1; i >= pos; i--) {this.elem[i + 1] = this.elem[i];}//将data数据插入到pos处this.elem[pos] = data;//插入成功后有效数据+1this.usedSize++;}//假设此时我们默认插入的数据都在数组最后面,方法如add2所示public void add2(int data) {if (isFull()) {System.out.println("数组已满,禁止插入");//调用resize方法进行扩容resize();}this.elem[this.usedSize] = data;this.usedSize++;}// 打印顺序表public void display() {for (int i = 0; i < this.usedSize; i++) {System.out.print(this.elem[i] + " ");}System.out.println();}// 判定是否包含某个元素public boolean contains(int toFind) {for (int i = 0; i < this.usedSize; i++) {if (this.elem[i] == toFind) {return true;}}//找不到返回falsereturn false;}// 查找某个元素对应的位置public int search(int toFind) {for (int i = 0; i < this.usedSize; i++) {if (this.elem[i] == toFind) {return i;}}//找不到返回-1return -1;}// 获取 pos 位置的元素public int getPos(int pos) {if (pos < 0 || pos >= this.usedSize) {return -1;}return this.elem[pos];}// 给 pos 位置的元素设为 valuepublic void setPos(int pos, int value) {if (pos < 0 || pos >= this.usedSize) {return;}this.elem[pos] = value;}//删除第一次出现的关键字keypublic void remove(int key) {int j = search(key);if (j == -1) {System.out.println("没有这个元素呀");return;}for (int i = j; i < this.usedSize - 1; i++) {this.elem[i] = this.elem[i + 1];}this.usedSize--;}// 获取顺序表长度public int size() {return this.usedSize;}// 清空顺序表public void clear() {this.usedSize = 0;}
}
下面我们来挨个分析这段代码:
//定义一个数组private int[] elem;//usedSize数据用于计算数组中有效数据的个数private int usedSize;//利用构造方法来进行elem这个实例成员变量的初始化public MyArrayList() {this.elem = new int[10];}public MyArrayList(int capacity) {this.elem = new int[capacity];}
首先一开始我们定义一个数组, 因为顺序表底层就是一个数组,用于存储数据用的,usedSize代表我们这个数组中有效的数据的个数 ,例如数组长度为10,元素个数为5,那么usedSize=5.当然我们给这两个实例成员的权限为private,当然可以使用setter和getter方法进行初始化,为了方便起见,我们就使用构造函数来进行初始化了。
//在 pos 位置新增元素public boolean isFull() {if (this.usedSize == this.elem.length) {//代表顺序表已满return true;}return false;}//定义一个数组扩容方法resizepublic void resize() {System.out.println("满了就进行扩容,扩大两倍");//调用copyOf方法进行扩容this.elem = Arrays.copyOf(this.elem, this.elem.length * 2);}public void add(int pos, int data) {//如果顺序表满,则不能插入if (isFull()) {System.out.println("顺序表已满");//如果此时顺序表满了,调用resize方法来进行扩容resize();}if (pos < 0 || pos > this.usedSize) {System.out.println("pos位置不合法");return;}//插入后,其他元素后移for (int i = this.usedSize - 1; i >= pos; i--) {this.elem[i + 1] = this.elem[i];}//将data数据插入到pos处this.elem[pos] = data;//插入成功后有效数据+1this.usedSize++;}//假设此时我们默认插入的数据都在数组最后面,方法如add2所示public void add2(int data) {if (isFull()) {System.out.println("数组已满,禁止插入");//调用resize方法进行扩容resize();}this.elem[this.usedSize] = data;this.usedSize++;}
在向一个顺序表中插入数据的时候,一般有两种方法,第一种是向顺序表中插入数据,第二种方法是在顺序表的尾部插入数据
先来看第一种add方法,当我们向顺序表中插入数据的时候,需要注意两点:
1.判断此时数组是否满,满了的话就不能再插入了
所以我们定义了一个isFull函数来判断此时数组是否已经满了
2.判断插入的位置是否合法,插入的数组下标位置应既不能小于0,也不能大于usedSize
原因是如果插入的数组下标位置小于0,说明插在了这个数组最前面,显然不符合当前情况,如果大于了usedSize,打个比方,假如usedSize的值为3, 说明此时顺序表中有三个有效数据,那么在顺序表中插入的下标位置的取值范围一定是在【0,3】这样的一个闭区间内, 取0-2说明在数组内部某个下标处插入,那么这个下标后面的原有元素都要后移 假如在下标为3的位置插入,相当于紧挨着顺序表插入,若大于3,是不可行的,相当于跟当前数组中最后一个有效元素隔了一个格子进行插入。
进行完上述判断后,此时我们来进行元素在数组间的插入操作,当一个元素插入一个位置后,原来插入的位置上的元素及其之后的元素都要相应的后移,例如现有一个数组存储了1,2,4,5这四个数字,现在要往下标为2的地方存入3这个数字,那么就需要将原来下标为2处所对应的4这个数字和5这个数字依次向后移动,也就有了this.elem[i + 1] = this.elem[i]这条语句,并且注意我们这个循环中i是从数组最后一个元素的下标开始的,数组中的有效数字个数为usedSize,那么最后一个数字的下标为usedSize-1,然后i的边界为>=pos,i可以等于pos,原因是我们原本pos对应的数字也是要往后移动的,所以需要移动完4这个数字后再将3这个数字插入2这个下标处,对应this.elem[pos] = data这条语句,最终数组中的有效数据计量单位usedSize加1,完成了数据插入数组间的操作.
此外,我们还可以看到此处定义了一个扩容函数resize,为什么要用到扩容函数呢?
原因是假如我们的数组已经满了,即无法再插入进去了,那么就需要对数组进行扩容,否则我们插入的时候会报数组越界异常错误。我们自行实现扩容的方法是运行Arrays工具类中的copyOf方法来进行扩容,因为copyOf方法中的数组长度是可以自定义的,具体可以看我之前数组的博客。https://blog.csdn.net/qq_41972686/article/details/111411606
当然顺序表(ArrayList)是有其自己的扩容方式的,是1.5倍扩容
最后我们就还定义了一个add2方法,这个方法是为了解决在顺序表尾部插入数据的问题,可以看到只需一次便可插入成功,故时间复杂度为O(1)
第一种方法的时间复杂度为O(N),第二种方法的时间复杂度为O(1).
所以根据时间复杂度是按照最坏情况来界定的话,顺序表的插入删除操作的时间复杂度为O(N)
同时还有一个写法大家需要注意:如果在一个返回值为void的方法中遇到了return;这样的语句,代表直接退出本函数。
return;这个语句一般出现在一个无返回值的函数里面, 函数里面可以有很多行,有的时候需要程序运行到某一行就要停止退出本函数,就需要用到return;语句
记住在方法中如果遇到return语句的时候就代表结束了,不管这个return后面有多少语句.
//打印顺序表public void display() {for (int i = 0; i < this.usedSize; i++) {System.out.print(this.elem[i] + " ");}System.out.println();}
打印顺序表的方法跟遍历数组的方法相同,这块没有什么好说的,注意i的边界是小于usedSize即可.因为usedSize-1就是数组中最后一个元素的下标.
// 判定是否包含某个元素public boolean contains(int toFind) {for (int i = 0; i < this.usedSize; i++) {if (this.elem[i] == toFind) {return true;}}//找不到返回falsereturn false;}
判断顺序表中是否包含某个元素,其核心思想还是遍历顺序表底层的数组,根据遍历判断每个下标对应的数字是否是我们所想要的数字,如果是,返回true,否则返回false.
// 查找某个元素对应的位置public int search(int toFind) {for (int i = 0; i < this.usedSize; i++) {if (this.elem[i] == toFind) {return i;}}//找不到返回-1return -1;}
查找顺序表中某个元素的位置,其实就是返回这个元素在数组中所对应的下标的位置,同样的方式去遍历顺序表的底层数组,通过判断某个下标所对应的数值跟我们所要找的元素是否相等,来返回对应的下标值,如果找不到就返回-1.
// 获取 pos 位置的元素public int getPos(int pos) {if (pos < 0 || pos >= this.usedSize) {return -1;}return this.elem[pos];}
获取pos位置的元素的时候需要注意一个问题:pos值的取值问题.
因为pos值就相当于数组的下标,所以当我们去获取pos位置的元素的时候,需要考虑到pos的取值问题,如果小于0或者大于了usedSize,相当于取到了数组下标取值范围以外的数字,那么如果我们不处理这种情况,就会发生数组下标越界异常,所以当出现这种情况的时候,我们返回-1即可,否则就返回pos位置的元素.
// 给 pos 位置的元素设为 valuepublic void setPos(int pos, int value) {if (pos < 0 || pos >= this.usedSize) {return;}this.elem[pos] = value;}
将pos位置的元素设置为value,其实就是直接将pos位置所对应的值改成value值即可,并不需要遍历循环去找到pos位置的元素再修改,这样无非增加了时间复杂度。
当然在进行修改前要先判断所修改的数值对应的下标pos是否符合要求,如代码所示,如果不符合要求直接return结束.
//删除第一次出现的关键字keypublic void remove(int key) {int index = search(key);if (index == -1) {System.out.println("没有这个元素呀");return;}for (int i = index; i < this.usedSize - 1; i++) {this.elem[i] = this.elem[i + 1];}this.usedSize--;}
// 查找某个元素对应的位置public int search(int toFind) {for (int i = 0; i < this.usedSize; i++) {if (this.elem[i] == toFind) {return i;}}//找不到返回-1return -1;}
删除第一次出现的关键字key,也就是找到我们顺序表底层数组中是否有跟这个key值相同的元素,如果有,那么就删掉这个元素,如果这个元素在这个数组中出现超过一次,那么就按照下标顺序删除第一次出现的这个元素.
当然在进行删除之前还要判断下当前数组中是否包含我们所要删除的元素,如果没有的话,就直接return结束,那么怎么确定当前数组中是否有这个要删除的元素呢?
此时就需要用到search方法了,这个方法是用来查找数组中元素的位置的,假如数组中有这个元素,就返回这个元素的下标值,否则就返回-1.
那么这个search方法刚好可以用到我们的删除方法remove中,首先调用search方法来判断数组中是否有我们要删除的元素,如果没有,search方法便会返回-1,那么当其返回-1时,我们的remove方法就直接return结束,如果返回值不是-1,就开始执行我们的for循环来进行删除工作。
这里大家可能会疑惑为什么删除插入操作都要用到for循环呢?因为当我们执行删除或者插入操作的时候是牵扯到元素的前移和后移的,所以用循环可以更好的表示所涉及到的需要移动的元素。
在删除操纵中有两点需要注意:
1:第一点for循环中i的初始值为index,index就是我们所要删除的第一次出现的key值的下标,因为元素的删除和移动都是从当前这个下标开始的,所以i的初始值为index.
当前i的边界也就是终止条件为i小于this.usedSize-1,这块的小于是需要额外注意下的,原因是我们后期元素移动的语句为 this.elem[i] = this.elem[i + 1],我们可以看到假如此时i<=this.usedSize-1,相当于i是可以取到最后一个元素的下标值的,那么this.elem[i] = this.elem[i + 1]这条语句会报错,原因是i+1此时数组下标越界,最终会显示数组下标越界异常.
2:最后要记得每当我们删除一个元素的时候,我们的数组中有效数据的计量单位usedSize要对应减掉一个,别忘了this.usedSize--这条语句.
// 获取顺序表长度public int size() {return this.usedSize;}
获取顺序表的长度就非常简单了,有usedSize这个负责记录数组中有效数据的计量单位,我们可直接返回usedSize即可,usedSize的值就代表了当前数组中元素的个数,也就是我们顺序表的长度.
// 清空顺序表public void clear() {this.usedSize = 0;}
清空顺序表的话就直接将usedSize置为0就好,代表我们的顺序表中此时不再有有效数据了,说明元素全部清空.
好了到这里我们自主实现的顺序表代码和解析就结束啦,写到这里大家可能也会有了一些感悟和思考,下面我们来进行下总结。
1.4顺序表的总结和思考
顺序表缺点
(1)顺序表中间或头部插入,时间复杂度为O(N),原因是当我们使用中间插入时,循环要执行的次数为N-1(N为当前线性表中有效数据的个数)
那么经过大O渐进法三部曲后,最终时间复杂度为O(N)
尾部直接插入,时间复杂度为O(1)
所以就插入删除元素来说,在最坏情况下,顺序表的时间复杂度可以达到O(N).所以在插入删除的时候一般使用链表.
(2)假设此时顺序表的大小为10,当顺序表存满后我们再向内部进行插入数据的时候,就需要对其进行扩容,java中顺序表的扩容是按照原来数组长度的1.5倍进行扩容的,那么扩容后的数组长度为15,但最后我只插入了一个数据,说明有4个空位此时没有用,浪费掉了。说明原数组长度越大,浪费的空间越大。说明顺序表增容的代价很大.
顺序表优点
如果是通过下标直接取数据,那么时间复杂度是可以达到O(1).
由此,为了改变顺序表的缺点问题,我们引入了链表.
一篇看懂顺序表!!(刘欣大佬《码农翻身》特别提及)相关推荐
- 数据结构之线性表----一文看懂顺序表、单链表、双链表、循环链表
线性表是数据结构中比较基础的内容,不过也是入门的所需要客服的第一个难关.因为从这里开始,就需要我们动手编程,这就对很多同学的动手能力提出了挑战.不过这些都是我们需要克服的阵痛,学习新的知识总是痛苦 ...
- 新一配:一篇看懂加油站产业链解决方案
新一配:一篇看懂加油站产业链解决方案的文章 一.加油站市场规模 截至2018年9月,全国机动车保有量达3.22亿辆,机动车驾驶人达4.03亿人.在这庞大的汽车生活消费生态链中,加油是汽车消费必不可少的 ...
- 阿联酋外汇业务的监管宽松?一篇看懂如何在阿联酋做外汇交易!
阿联酋外汇业务的监管宽松?一篇看懂如何在阿联酋做外汇交易! 因为互联网技术的高速发展,个体交易者现在可以通过国际银行或经纪人轻松地进入外汇市场.就每日交易量而言,外汇市场可以称之为是世界上最大的市场. ...
- 编程难在哪里? 一个美国实习生的故事。(转自刘欣老师的微信公众号——码农翻身)
阿莱克西斯 码农翻身 3天前 作者:阿莱克西斯 来源: https://www.zhihu.com/question/22508677/answer/276595266 记得之前组里来了一个美国实习生 ...
- 小编带你两图看清北京大厂公司、码农分布——谷歌,微软,百度,腾讯,阿里,滴滴,美团,头条,独角兽
小编带你两图看清北京大厂公司.码农分布--谷歌,微软,百度,腾讯,阿里,滴滴,美团,头条 北京一二线大厂公司.码农分布 年关将至,意味着2021年即将过去,2022年即将到来 现在,就是大家开始为 ...
- 给大忙人看的码农翻身记
码农翻身记个人读后感 文章目录 码农翻身记个人读后感 本书介绍 大纲 启发 分享 个人的能力欠缺的地方 书中比较棒的建议 本书介绍 <码农翻身>用故事的方式讲解了软件编程的若干重要领域,侧 ...
- [小森数据结构]看电影-顺序表技术精讲
故事前研(言) 小森去看电影<<流浪地球2>>的时候买电影票的场景. 突然一个叫高启强的人买电影票 拉了两个Hei友,来看电影 <<流浪地球2>> 当场 ...
- 想学会财务分析:先看懂三大表
想真正读透财务报表肯定是没有捷径的,但对于新手朋友,可以快速地了解财务报表的大概情况是可以有些技巧的,下面我来分享一些快速判断财务报表的方法 财务报表是很重要的辅助分析工具,但不是唯一,不可以迷信财务 ...
- ubuntu自定义安装里怎么选_超市里的五香粉怎么选?看懂配料表,两个小技巧,不怕选不好。...
点击右上角[关注],可获得本头条号推荐的更多美食干货 五香粉是家庭常用的一种调味料,焖.炖.烧.煮.卤都常备使用,如今市场上的五香粉品牌也是相当繁多,质量也是参差不齐,有时买到的五香粉烧制的菜肴根本就 ...
最新文章
- 1 元秒杀 1000+ 册爆款电子书,错过再等一年!
- evnetlet hub
- javascript中实现读取指定格式的日期(format)
- Merge into的使用详解-你Merge了没有
- (R)?ex 0.41.2 发布,软件配置和部署
- power designer 连接数据库提示“connection test failed”
- 微服务的好处与弊端_在云原生时代,就一定要用微服务吗?
- 只能是做的HTML5审批流项目
- python学习笔记六 初识面向对象上(基础篇)
- java中none applicable_Java线程使用技巧学习(二)
- Java--Json解析
- 七、数值微分与数值积分
- Graph Convolutional Networks 图卷积网络
- 关于限制水晶报表的导出格式
- 如何安装uclient_uclient软件下载|
- Web网页设计作业html
- 微信第三方授权获取用户信息
- android ipad 播放器,[AS3]专业级跨平台网页播放器支持PC、iPad、iPhone、Android系统...
- oracle12c用plsql连不上,PLSQL连接oracle12c
- 双十一大促直通车引流,低客单价的产品怎样引流 低客单价怎样开直通车