线性表的动态数组实现ArrayList
1.何为线性表
首先说什么是线性表
线性表的定义:线性表中数据元素之间的关系是一对一的关系,即除了第一个元素没有前驱,最后一个元素没有后继之外,其余元素既有唯一前驱和唯一后继,如下图所示
搞懂什么是线性表之后,我们再考虑一个问题,线性表能干啥?
手机上的通讯录是不是符合一个线性表的定义?
刺激战场中背包内容是不是符合一个线性表的定义?
学校中的成绩单是不是符合一个线性表的定义?
是的,对于上述三个场景都符合线性表的定义,只不过主要的区别在于所存储的数据元素不同罢了
这里大家注意,所谓的数据元素不只是整数int,小数double,布尔boolean和字符串String等等简单的数据类型
数据元素,是由多个数据项组成的集合体,大家可以定义一个类对这些数据项进行封装,那么线性表中只需要存储由这个类所创建出来的若干个对象即可
比如手机通讯录里的联系人数据元素,由头像、姓名、电话三个数据项组成
刺激战场中背包的物品数据元素,由图片、名称、说明、属性四个数据项组成
成绩单中学生分数的数据元素,由姓名、语文分数、数学分数、英语分数、总分、平均分六个数据项组成
到这里大家就能够明白,线性表的主要作用就是存储类似上述场景中的数据
除了存储之外,还应该具有增加数据,删除数据,查寻数据,修改数据这四个基本的操作,对吧~
2.逻辑结构和物理结构
到目前为止,我们一直在讨论线性表能够做什么,但是如何让计算机能够认识且存储线性表就是我们现在要考虑的问题
上述讨论的线性表其实是我们臆想出来的,还没有真实的存储在于计算机内存中,我们把它称之为线性表的逻辑结构
什么是逻辑结构,就是只知道他的定义,但不知如何将其进行实际的实现
在计算机领域中,对于线性表的实现方式主要有两种,称之为线性表的物理结构:
- 动态数组:用编程语言中自带的数组去实现线性表,如图所示
- 动态链表:定义节点数据类型,将元素存入节点,然后将节点进行串联实现线性表,如图所示
我们会发现,如果用动态数组实现线性表,那么元素在数组中存储的话是地址连续的,因为数组中的存储空间是连续的
如果用动态链表实现线性表的话,那么元素的地址是随机的,因为节点对象创建时的地址是由系统底层决定的且随机的,所以为了保持线性表的性质,每一个节点除了存储数据信息外,还需要存储其下一个节点的地址
无论是动态数组实现线性表也好,还是动态链表实现也罢,它们对线性表的操作无非还是增删改查,所以我们可以先对两者的具体实现定义统一的操作规范,即定义线性表的接口,如下代码所示
创建List.java写入代码
- //让List线性表支持泛型,E表示任意的数据类型
- public interface List<E> {
- public int getSize(); //获取线性表中元素的个数
- public boolean isEmpty(); //判断线性表是否为空表
- public void add(int index,E e); //在线性表中指定角标index处插入元素e
- public void addFirst(E e); //在线性表中第一个位置插入元素e
- public void addLast(E e); //在线性表中最后一个位置插入元素e
- public E get(int index); //获取线性表中指定角标index处的元素
- public E getFirst(); //获取线性表中第一个元素
- public E getLast(); //获取线性表中最后一个元素
- public void set(int index,E e); //修改线性表中指定角标index处的元素为新元素e
- public boolean contains(E e); //判断线性表中是否包含指定元素e
- public int find(E e); //获取线性表中元素e从头到尾第一次出现的位置
- public E remove(int index); //删除并返回线性表中指定角标index处的元素
- public E removeFirst(); //删除并返回线性表中第一个元素
- public E removeLast(); //删除并返回线性表中最后一个元素
- public void removeElement(E e); //删除线性表中指定元素e
- public void clear(); //清空线性表
- }
3.Java中数组的特点
创建好List接口之后,我们接下来就来讨论如何用动态数组实现线性表
首先我们来回顾一下Java中数组的特点,下面代码是Java中创建一维数组的三种常用方式:
- int[] arr1=new int[100];
- int[] arr2=new int[]{1,2,3,4,5};
- int[] arr3={1,2,3,4,5};
- 第一种,创建名为arr1的一维整型数组,指定长度为100,且元素默认为0
- 第二种,创建名为arr2的一维整型数组,指定元素为[1,2,3,4,5],且长度为5
- 第三种,和第二种同理
除了创建方式之外,数组还有以下几个特点:
- 数组提供角标访问元素
- 数组长度一旦确定则不可更改
- 数组只有唯一的属性length表示数组的长度
那么数组的本质是什么的?数组本质就是一组空间大小相同且连续的存储结构,如图所示
已知第1个空间的地址为0x100,且每个空间大小都是4byte,那么第n个空间的大小是多少呢?
大家会发现,这是一个等差公式,对的!此时A1=0x100,d=4,所以一个公式直接算出第n个位置元素的地址
所以基于这一特点,我们可以用O(1)的时间去查找一个元素
注意此处 n 为元素在数组中的位置,而 n-1 表示元素在数组中的角标
- int[] arr={1,2,3,4,5};
- arr[3]; //表示角标3的元素,即就是位置4的元素 A4=A1+(4-1)*4
- arr[4]; //表示角标4的元素,即就是位置5的元素 A5=A1+(5-1)*4
- arr[5]; //表示角标5的元素,即就是位置6的元素,但是该元素不存在,所以报错
OK,数组的本质说完了,再来看看数组的操作
我们之前学过和数组相关的操作,基本上都是将数组当做参数传入到一个函数当中
- //查找数组中的最大值
- public int findMax(int[] arr){...}
- //对数组进行排序
- public void sort(int[] arr){...}
- //交换角标i,j元素的位置
- public void swap(int[] arr,int i,int j){...}
这样子的话,我们是将数组这个数据和函数这个操作进行分离的,并没有很好的体现面向对象的思想
如果能够将数据和操作进行封装成一个类MyArray,形成如下的操作方式,岂不是更好?
- class MyArray{
- private int[] arr;
- public MyArray(){
- arr=new int[10];
- }
- public void add(int e){...}
- public int findMax(){...}
- public void sort(){...}
- public void swap(int i,int j){...}
- }
- MyArray arr=new MyArray();
- arr.add(1);
- arr.add(2);
- arr.add(3);
- arr.sort();
- arr.swap(0,1);
- arr.findMax();
那么,我们就可以把对数组的所有操作都定义在这个MyArray类中,将来调用起来会很方便的
所以,将数组和其相关操作进行类封装的过程称之为动态数组
4.线性表的动态数组实现
哈哈,结合线性表的定义和动态数组的概念,我们就可以真正开始写线性表的动态数组实现方式了
4.1 创建ArrayList类并实现List接口
- public class ArrayList<E> implements List<E>{
- }
至于List中的方法,稍后我们在一个个实现,别急
4.2 成员变量
对于线性表的动态数组实现而言,如果只封装一个数组数据够用吗?
如上图所示,虽然数组data长度为10,但是只有5个元素存入到数组当中
10很好表示,data.length即可
5怎么表示呢?所以我们还需要维护一个记录线性表中有效元素个数的变量 size
如下图所示
小技巧提示,size不仅可以表示有效元素的个数,也可以表示在末尾添加元素时新元素的位置
- public class ArrayList<E> implements List<E>{
- private E[] data; //作为容器存储元素 data.length为最大容量
- private int size; //当前线性表中元素的个数-有效长度
- }
4.3 构造函数
可以让调用者创建一个指定容量大小的线性表,也可以创建一个默认容量大小的线性表
- public class ArrayList<E> implements List<E>{
- private E[] data; //作为容器存储元素 data.length为最大容量
- private int size; //当前线性表中元素的个数-有效长度
- private static final int DEFAULT_CAPACITY=10;//默认容量为10
- //默认构造函数中创建一个默认容量的线性表
- public ArrayList(){
- this(DEFAULT_CAPACITY);
- }
- //构造函数中创建一个容量为capacity的线性表
- public ArrayList(int capacity){
- if(capacity<0){
- capacity=DEFAULT_CAPACITY;
- }
- this.data=(E[]) new Object[capacity];
- this.size=0;
- }
- }
增删功能在后头,一会再说哈,先把几个简单的问题解决了~困难的放后头
4.4 getSize()函数
获取线性表中元素的个数
直接返回size即可
- public int getSize() {
- return this.size;
- }
4.5 isEmpty()函数
判断线性表是否为空表
直接判断size==0的结果即可
- public boolean isEmpty() {
- return this.size==0;
- }
4.6 get()函数
获取线性表中指定角标index处的元素
直接返回数组[index],注意角标越界问题
- public E get(int index) {
- if(index<0||index>=data.length){
- throw new IllegalArgumentException("角标不存在!");
- }
- return data[index];
- }
4.7 getFirst()函数
获取线性表中第一个元素
复用get()函数
- public E getFirst() {
- return get(0);
- }
4.8 getLast()函数
获取线性表中最后一个元素
复用get()函数
- public E getLast(){
- return get(size-1);
- }
4.9 set函数
修改线性表中指定角标index处的元素为新元素e
- public void set(int index, E e) {
- if(index<0||index>=size){
- throw new IllegalArgumentException("角标不存在!");
- }
- data[index]=e;
- }
4.10 find()函数
获取线性表中元素e从头到尾第一次出现的位置
- public int find(E e) {
- for(int i=0;i<size;i++){
- if(data[i].equals(e)){
- return i;
- }
- }
- return -1;
- }
4.11 contains()函数
判断线性表中是否包含指定元素e
- public boolean contains(E e) {
- return find(e)!=-1;
- }
4.12 clear()函数
清空线性表
简单粗暴
- public void clear() {
- this.data=(E[]) new Object[DEFAULT_CAPACITY];
- this.size=0;
- }
4.13 getCapacity()函数
获取线性表的最大容量
- public int getCapacity(){
- return data.length;
- }
4.14 swap()函数
交换顺序表中指定角标i,j的两个元素
- public void swap(int i,int j){
- if(i<0||i>=size||j<0||j>=size){
- throw new IllegalArgumentException("角标非法!");
- }
- E temp=data[i];
- data[i]=data[j];
- data[j]=temp;
- }
好了,现在开始复杂的部分了,增删!其实也不难哈
4.15 add()函数
在线性表中指定角标index处插入元素e
先别急着写代码,分析一下,添加元素主要分为三种情况
- 在表头插入:index=0
- 在表中插入:index∈(0,size)任意
- 在表尾插入:index=size
如果在表头插入元素,原先已存在的元素就得挨个后移,别忘了size++
思路:另 i 从 size 开始,i 到角标 0 结束,每次将 i-1 的元素赋予 i ,完毕后将新元素加入角标0 如下图所示
如果在表中插入元素,基本思路和上述一直,插入位置 index 之后的元素就得挨个后移,别忘了size++
思路:另 i 从 size 开始,i 到角标 index 结束,每次将 i-1 的元素赋予 i ,完毕后将新元素加入角标 index 如下图所示
如果在表尾插入元素,直接把元素放入size位置,size++即可,此处无图,自己想
其实三者插入方式都是一个意思的,完全可以按照一个思路写,只不过表尾插入不需要循环,可以直接插入即可,代码如下
- public void add(int index, E e) {
- if(index<0||index>size){
- throw new IllegalArgumentException("角标非法!");
- }
- if(size==data.length){
- resize(data.length*2);
- }
- for(int i=size;i>index;i--){
- data[i]=data[i-1];
- }
- data[index]=e;
- size++;
- }
- private void resize(int newLen) {
- E[] newData=(E[]) new Object[newLen];
- for(int i=0;i<Math.min(data.length,newData.length);i++){
- newData[i]=data[i];
- }
- data=newData;
- }
有同学会发现,这里怎么还多了个resize函数呢?
其实这个函数是为了实现数组的自动扩容和缩容功能的
如果元素填满了数组( size==data.length),此时就要需要考虑数组该扩容了,一般扩容为原先的2倍,如图所示
思路:创建一个比原先数组大2倍的新数组,然后将老数组中的值挨个放入到新数组中,最后新数组替代老数组即可
恩,在任意位置添加元素讲完了
4.16 addFirst()函数
在线性表中第一个位置插入元素e
复用add()即可
- public void addFirst(E e) {
- add(0,e);
- }
4.17 addLast()函数
在线性表中最后一个位置插入元素e
复用add()即可
- public void addLast(E e) {
- add(size,e);
- }
4.18 remove()函数
删除并返回线性表中指定角标index处的元素
哈哈,此处的删除也分为删除表头,删除表中,和删除表尾
此处我就不给出演示图了,希望大家自己可以动手画一画
删除表头思路:先取出要删除的元素,然后另 i 从 0 开始,到size-2结束,将 i+1 的元素赋予 i 即可,最后 size--
删除表中思路:先取出要删除的元素,然后另 i 从 index 开始,到size-2结束,将 i+1 的元素赋予 i 即可,最后 size--
删除表尾思路:直接size--即可
此处的重点在于,删除元素之后,可能有缩容的情况,什么时候缩容呢,当 size==data.length/4 时缩容
为什么是除以4,而不是除以2呢?
如果是 size==data.length/2 时缩容,大家可以考虑一个问题,如果新加元素之后满了,扩容;接着删除元素之后,size到达length的一半,缩容;这样子的话我们的线性表是不是太“勤快”了些呢?是的,这样子的话会影响效率,所以删除元素之后,就算size到达length的一半,别急着缩,让它再删几个,删到足够少的时候再缩容,为时也不晚,一般设置在1/4处即可;这样子就避免了缩的太勤的问题。
还有就是,如果已达到默认容量的话,则不需要再缩了
- public E remove(int index) {
- if(index<0||index>=size){
- throw new IllegalArgumentException("角标非法!");
- }
- E res=data[index];
- for(int i=index;i<size-1;i++){
- data[i]=data[i+1];
- }
- size--;
- if(size==data.length/4&&data.length>DEFAULT_CAPACITY){
- resize(data.length/2);
- }
- return res;
- }
4.19 removeFirst()函数
删除并返回线性表中第一个元素
复用remove()即可
- public E removeFirst() {
- return remove(0);
- }
4.20 removeLast()函数
删除并返回线性表中最后一个元素
复用remove()即可
- public E removeLast() {
- return remove(size-1);
- }
4.21 removeElement()函数
删除线性表中指定元素e
先找,再删
- public void removeElement(E e) {
- int index=find(e);
- if(index!=-1){
- remove(index);
- }
- }
欧克,好啦,到这里线性表的动态数组实现方式讲完了,大家再接再厉,附上完整代码
ArrayList.java
- /**
- @Author Teacher_HENG
- */
- //动态数组实现的线性表->顺序表
- public class ArrayList<E> implements List<E>{
- private E[] data; //作为容器存储元素 data.length为最大容量
- private int size; //当前线性表中元素的个数-有效长度
- private static final int DEFAULT_CAPACITY=10;//默认容量为10
- //默认构造函数中创建一个默认容量的线性表
- public ArrayList(){
- this(DEFAULT_CAPACITY);
- }
- //构造函数中创建一个容量为capacity的线性表
- public ArrayList(int capacity){
- if(capacity<0){
- capacity=DEFAULT_CAPACITY;
- }
- this.data=(E[]) new Object[capacity];
- this.size=0;
- }
- //将传入数组封装成动态数组
- public ArrayList(E[] arr){
- data=(E[]) new Object[arr.length];
- for(int i=0;i<data.length;i++){
- data[i]=arr[i];
- }
- size=arr.length;
- }
- public void add(int index, E e) {
- if(index<0||index>size){
- throw new IllegalArgumentException("角标非法!");
- }
- if(size==data.length){
- resize(data.length*2);
- }
- for(int i=size;i>index;i--){
- data[i]=data[i-1];
- }
- data[index]=e;
- size++;
- }
- public void addFirst(E e) {
- add(0,e);
- }
- public void addLast(E e) {
- add(size,e);
- }
- public E remove(int index) {
- if(index<0||index>=size){
- throw new IllegalArgumentException("角标非法!");
- }
- E res=data[index];
- for(int i=index;i<size-1;i++){
- data[i]=data[i+1];
- }
- size--;
- if(size==data.length/4&&data.length>DEFAULT_CAPACITY){
- resize(data.length/2);
- }
- return res;
- }
- public E removeFirst() {
- return remove(0);
- }
- public E removeLast() {
- return remove(size-1);
- }
- @Override
- public void removeElement(E e) {
- int index=find(e);
- if(index!=-1){
- remove(index);
- }
- }
- private void resize(int newLen) {
- E[] newData=(E[]) new Object[newLen];
- for(int i=0;i<Math.min(data.length,newData.length);i++){
- newData[i]=data[i];
- }
- data=newData;
- }
- public int getSize() {
- return this.size;
- }
- public boolean isEmpty() {
- return this.size==0;
- }
- public E get(int index) {
- if(index<0||index>=data.length){
- throw new IllegalArgumentException("角标不存在!");
- }
- return data[index];
- }
- public E getFirst() {
- return get(0);
- }
- public E getLast(){
- return get(size-1);
- }
- public void set(int index, E e) {
- if(index<0||index>=size){
- throw new IllegalArgumentException("角标不存在!");
- }
- data[index]=e;
- }
- public int find(E e) {
- for(int i=0;i<size;i++){
- if(data[i].equals(e)){
- return i;
- }
- }
- return -1;
- }
- public boolean contains(E e) {
- return find(e)!=-1;
- }
- public int getCapacity(){
- return data.length;
- }
- public void clear() {
- this.data=(E[]) new Object[DEFAULT_CAPACITY];
- this.size=0;
- }
- public void swap(int i,int j){
- if(i<0||i>=size||j<0||j>=size){
- throw new IllegalArgumentException("角标非法!");
- }
- E temp=data[i];
- data[i]=data[j];
- data[j]=temp;
- }
- }
线性表的动态数组实现ArrayList相关推荐
- 线性表的动态顺序存储和实现(C语言实现)【线性表】(4)
ElemType.h DynaSeqList.h DynaSeqList.cpp ElemType.cpp Lab.cpp 测试结果 注意 ElemType.h /*** *ElemType.h - ...
- 【数据结构基础】线性数据结构——线性表概念 及 数组的封装(C和Java)
前言 数据结构,一门数据处理的艺术,精巧的结构在一个又一个算法下发挥着他们无与伦比的高效和精密之美,在为信息技术打下坚实地基的同时,也令无数开发者和探索者为之着迷. 也因如此,它作为博主大二上学期最重 ...
- 有十五个数按由大到小顺序存放在一个数组中_数据结构基础 (代码效率优化, 线性表, 栈, 队列, 数组,字符串,树和二叉树,哈希表)...
作者:张人大 代码效率优化 复杂度 -- 一个关于输入数据量n的函数 时间复杂度 -- 昂贵 与代码的结构设计有着紧密关系 一个顺序结构的代码,时间复杂度是O(1), 即任务与算例个数 n 无关 空间 ...
- 【线性表】—动态顺序表的增删查改实现
小菜坤日常上传gitee代码:https://gitee.com/qi-dunyan(所有的原码都放在了我上面的gitee仓库里) 数据结构知识点存放在专栏[数据结构]后续会持续更新 ❤❤❤ 个人简介 ...
- 数据结构之线性表—数组描述
目录 定义 抽象数据类型(ADT)linearList 抽象类 linearList 数组描述的线性表 变长一维数组 类 arrayList 类arrayList的定义 类arrayList的构造函数 ...
- 已知线性表最多可能有20个元素,存储每个元素需要8字节,存储每个指针需要4字节。当元素个数为( )时使用单链表比使用数组存储此线性表更加节约空间。
已知线性表最多可能有20个元素,存储每个元素需要8字节,存储每个指针需要4字节.当元素个数为( 大于等于13 )时使用单链表比使用数组存储此线性表更加节约空间. 使用数组存储线性表需要提前分配好数组空 ...
- Algorithms_基础数据结构(01)_线性表之数组数组的应用案例分析
文章目录 大纲图 数组的经典面试题目 数据结构三要素 数据逻辑结构(线性结构&非线性结构) 数据存储结构(顺序存储.链式存储.索引存储和散列存储) 顺序存储 链式存储 索引存储 散列存储 数据 ...
- 数据结构和算法详解(二)——线性表(数组、链表、栈、队列)
一.数组 线性表: 线性表就是数据排成像一条线一样的结构.每个现行表上的数据最多只有前和后两个方向.常见的线性表结构:数组,链表.队列.栈等. 什么是数组: 数组(Array)是一种线性表数据结构 ...
- 线性表——顺序数组seqList的实现 C++
本文主要总结常用的数据结构--线性表之顺序数组,实现其基本的增删查改功能,不涉及完整功能,目的是为了熟悉常用数据结构属性和代码实现底层原理. 一.线性表--顺序数组seqList 1.1线性表定义 从 ...
- 其他类型的链表和线性表的总结(一级)
现在我们来看其他的链表单链表每个节点包括两个部分,一部分包括数据,另一部分保存下一个节点的地址,根据这个指针我们就可以依次找到下一个节点这是单链表,但是他有一个缺点,只能通过前驱节点找到后继节点,a1 ...
最新文章
- pandas以前笔记
- Alpha版验收通过
- 比较不错的一个ios找茬游戏源码
- 设计模式(10)-装饰模式详解(易懂)
- Installing ROS 2 on Ubuntu20.04 Linux
- 编写程序模拟“主人”喂养“宠物”的场景,利用多态的思想!!!
- .NET : 在定义项目模板的时候使用占位符
- c语言错误spawning,C语言一直出现Error spawning cl.exe的解决办法
- 1106 冒泡排序的语法树
- ORB-SLAM3 yaml文件介绍
- caffe编译好后,需要配置.bashrc
- c++11 多线程依次打印ABC
- ORK进行物体检测过程中出现的报错及解决方案
- 机场VIP会员管理系统
- flash对联广告代码: 两边显示 不移动 可关闭
- mpa和pis_压力单位pis、bar与Mpa换算
- win 7 系统(x64)安装vs2012时遇到的问题
- Linux主分区文件系统,Linux_Linux磁盘和文件系统管理,1、 分区MBR(Master Boot Recor - phpStudy...
- 《论语》原文及其全文翻译 学而篇4
- 2021.12.11 新星杯简单总结
热门文章
- 2023电商购物网站有哪些知名和靠谱的?
- mysql静态视图_在Django中创建第一个静态视图
- 微型计算机中普遍使用的字符编号是,微型计算机中普遍使用的字符编码是什么...
- 《2012》职场危机意识启示录 解读职场中生存哲学
- 磁铁会损坏或擦拭笔记本电脑的硬盘吗?
- Windows 端口占用查看/释放 [包含80端口占用与释放] - 学习/实践
- 教你如何用Python获取今日头条上面三千美女图
- 计算机技术在园林管理中的应用,《计算机技术在园林管理中的应用》优秀论文.doc...
- 1024----程序员们节日快乐!周末快乐!
- IMageLoader加载各种类型图片