值得思索的:ArrayList和线性表,你确定错过这次机会

举报
念君思宁 发表于 2023/02/07 18:18:58 2023/02/07
【摘要】 值得思索的:ArrayList和线性表,你确定错过这次机会

线性表:

线性表(linear list)是n个具有相同特性的数据元素的有限序列。 线性表是一种在实际中广泛使用的数据结 构,常见的线性表:顺序表、链表、栈、队列...

线性表在逻辑上是线性结构,也就说是连续的一条直线。但是在物理结构上并不一定是连续的,线性表在物 理上存储时,通常以数组和链式结构的形式存储。

顺序表的大致画图:

 链表的大致画图:

 顺序表:顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。在数组上完成 数据的增删查改。

顺序表接口的实现:

    private int[] array;
    private int size;
 
    // 默认构造方法
    SeqList(){ }
 
    // 将顺序表的底层容量设置为initcapacity
    SeqList(int initcapacity){ }
 
    // 新增元素,默认在数组最后新增
    public void add(int data) { }
 
    // 在 pos 位置新增元素
    public void add(int pos, int data) { }
 
    // 判定是否包含某个元素
    public boolean contains(int toFind) { return true; }
 
    // 查找某个元素对应的位置
    public int indexOf(int toFind) { return -1; }
 
    // 获取 pos 位置的元素
    public int get(int pos) { return -1; }
 
    // 给 pos 位置的元素设为 value
    public void set(int pos, int value) { }
 
    //删除第一次出现的关键字key
    public void remove(int toRemove) { }
 
    // 获取顺序表长度
    public int size() { return 0; }
 
    // 清空顺序表
    public void clear() { }
 
    // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    public void display() { }
 

对于上述的接口,想必让大家看,肯定干看也看不懂多少,所以,笔者来带领大家用一个代码来实现一下!!!相信大家跟着代码,一定会可以实现自主敲写滴!!

话不多说,请看实现顺序表的接口的代码:根据上面的简介,我们可以看出,顺序表的底层是一个数组,因此,我们可以定义一个数组来实现顺序表!!

public class MyArraylist {
    public int[] elem; //该数组在定义的时候,初始状态下默认为null
    public int usedSize;//记录存储了多少个数据
    public static final int DEFAULT_SIZE =5;//定义数组的长度
    
    public MyArraylist(){
        this.elem=new int[DEFAULT_SIZE];//定义数组的长度
    }
}

上述代码,是我们多需要进行的前提准备!!那么,有了这些,我们将可以进行后续的实现顺序表的各个接口了!!

1.   打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的

对于这个的实现,是笔者自己加上去的主要还是为了能够更好的观察结果的运行!!

那么请看笔者的代码;

 // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    public void display() {
        for (int i = 0; i < this.usedSize; i++) {
            //usedSize是数组存储的有效数字
            System.out.print(this.elem[i]+" ");
        }
        System.out.println();
    }

这样我们就可以实现对顺序表的打印了!!
2.获取顺序表长度

对于顺序表的长度,其实我们用usedSize在存储数据的时候,有意识的来统计一下,最后返回就行了!!

 //获取顺序表长度
    public int size () {
        return usedSize;  //usedSize是存储的有效数据
    }

上面两个是最简单的方法了!!那么在后面将会进行深入下去了!!请各位笔者多多思考哟!!

3. 判定是否包含某个元素

对于这个问题,想必大家在之前的解题过程中,就已经有了自己的独特的思维了!那么请看笔者自己的想法吧!!

    // 判定是否包含某个元素
    public boolean contains (int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i]==toFind) {
                return true;
            }
        }
        return false;
    }

笔者写的上述代码的时间复杂度为O(N)  ,我们必须要遍历一遍才行,不管顺序表中含有几个想要寻找的元素,只要有了就可以!!

在上述代码中,我们用了 “==” ,因为我们使用的是整型(int)类型的数组来进行演示的,当我们用其他类型的数组(如,引用类型)那么,就需要使用equals这个来进行比较了!!返回值是false/true,但是,想必会有很多的粉丝想必会选择ComparaTo这个来进行比较吧!但是,在这里不行,因为ComparaTo这个方法的返回值类型为整型的-1,0,1,而我们在本方法中定义的返回值类型为boolean,所以不符合要求!!

4.查找某个元素对应的位置

需要注意的是,我们使用该方法一般返回的是第一次出现的位置!!

    //查找某个元素对应的位置
    public int indefOf(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if(this.elem[i] == toFind) {
                return i;
            }
        }
        return -1;
        //返回-1,是因为数组中没有负数下标
    }

这个方法的使用情况跟3. 判定是否包含某个元素相类似,因此,笔者便不再进行解析了!!有问题的话,大家可以看一些=下上面的解析!!

5.新增元素,默认在数组最后新增

新增元素??我们是否需要判断是否在新增之前数组就已经存储满了呢??若是没有就……否则就……因此,我们需要首先对数组进行判断!!

5.(1)判断数组是否已经存储满了??

   //判断数组是否已经存储满了??
    public boolean isFull() {
        //方法1
        if (this.usedSize==this.elem.length) {
            return true;
        }
        return false;
 
        //方法2:
        //return this.usedSize==this.elem.length;
    }

上述笔者所写的两个方法都能实现:判断数组是否已经存储满了的目的!!其实也是很简单的,主要部分还是在:this.usedSize==this.elem.length 这段代码中!!this.usedSize是用来统计数组中存储多少数据的!而this.elem.length,则可以得出数组的长度!!如果这两个相等,那么数组肯定存储满了,就需要扩容了!

5.(2)扩容

  //扩容
    private void resize() {
        this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
        //实现数组扩容,并且将扩容好的重新赋值给elem
    }

在实现数组扩容的这个方法中,我们用了copyOf()这个方法!对于这个方法不明白的各位老铁,可以看一下copyOf()的底层实现的源码:

5(2).1看一下copyOf()方法的底层实现源码:

   public static int[] copyOf(int[] original, int newLength) {
        int[] copy = new int[newLength];
        System.arraycopy(original, 0, copy, 0,
                         Math.min(original.length, newLength));
        return copy;
    }

根据上述copyOf()方法,我们可以看出来;copyOf()实现了,将一个数组:int[] original扩容为一个新的长度:int newLength 最后的返回值类型为:int[] ,理解了源码的实现逻辑,因此,我们就可以使用了!

5.(3)根据上述两个方法来实现:新增元素

    //新增元素,默认在数组最后新增
    public void add(int data) {
        if (this.isFull()) {  //判断数组是否已经存储满了??
            resize();//扩容
        }
        this.elem[this.usedSize]=data;//数组的下标从0开始!
        this.usedSize++;
    }

对于上述实现新增元素,涉及到了判断数组是否存储满了,并且实现对数组的扩容,在这两个作为前提的情况下,才能进行后续的新增元素!!当我们新增元素之后,不能忘记对usedSize++ 这样才能准确的得出新增元素之后的长度!!

6.在 pos 位置新增元素

注意,我们所说的是:在 pos 位置新增元素,这就要求我们对其余元素实现依次后移!

在我们进行思考这个题目的时候,需要想到,想要插入的位置,是否大于数组的长度呢??(在数组的长度之外??或者为负数(-1)数组中没有定义负数的下标!!),但是,当放在数组的最后一个位置的时候,数组是否存储满了??是否需要进行扩容处理??这些都是我们需要进行思考的问题!!

6(1)在实现这个在 pos 位置新增元素目的的情况下笔者重新定义了一个类IndexoutOfException.java在这个类中,抛出了异常!

总体抛异常的代码为;

public class IndexoutOfException extends RuntimeException{
    public IndexoutOfException() {
 
    }
 
    public IndexoutOfException(String msg) {
        super(msg);
    }
//重写构造方法!!
}

6(2)因此,基于抛异常的这个实现逻辑:我们可以进行下面的代码:

    //在 pos 位置新增元素  时间复杂度为O(N)
    public void add(int pos , int data) {
        checkIndex(pos);//首先要检查pos的位置是否合法??
        if (isFull()) {  //判断数组是否存储满了??
            resize();//扩容
        }
        for (int i = usedSize-1; i >= pos; i--) {
            elem[i+1] = elem[i];
        }
        elem[pos] =data;
        usedSize++;
    }
 

具体的解析为:

6(3) 检查pos的位置是否合法?

   //检查add数据的时候,判断pos是否合法??
    private void checkIndex(int pos) {
        if (pos <0 || pos >=usedSize)
        throw new IndexoutOfException("get获取元素的时候,位置不合法!请检查位置的合法性!!");
        //当pos的位置不合法的时候,抛出异常!!
    }

对于该部分的简单详见为;

基于上面的三部分,我们才能实现对在 pos 位置新增元素的任务!!其实在其中的一丢丢的细小的情节,希望各位读者能够仔细参考一下!!比如在进行移动数据的时候,一点边缘线的取值!希望大家仔细注意!

7.获取 pos 位置的元素

对于该目的的实现,想必大家参考之前两个方法的实现,已经有着自己的想法了吧!!那么笔者便不再多言了!!一切皆在代码中:

  //获取 pos 位置的元素
    public int get(int pos) {
        checkIndex(pos);//检查pos的合法性!
        return elem[pos];
    }

根据检查pos位置的合法性而来的代码,显得就是很简单了!!但是,里面的逻辑思维还是很重要的!!请大家多多思考!

8. 给 pos 位置的元素设为 value

注意,我们这个的要求是给 pos 位置的元素设为 value,而不是在pos位置处进行增添!请大家分清楚!

    //给 pos 位置的元素设为 value
    public void set (int pos , int value) {
        checkIndex(pos); //首先要检查pos位置的合法性!!
        elem[pos] = value;
    }

这个代码也是挺简单的!!是不??各位老铁!!

9.删除第一次出现的关键字key

对于这个要求,我们只要找到第一次出现的key,然后再依次移动,就可以了!!想象的确实挺简单 的,但是……代码确实很复杂(对于当前的我来说!!)

 //删除第一次出现的关键字key  时间复杂度为O(N)
    public boolean remove(int toRemove) {
        int index = indefOf(toRemove);//查找toRemove元素所对应的位置!
        if (index==-1) {
            System.out.println("没有这个元素!");
            return false;
        }
        for (int i = index; i < usedSize-1; i++) {
            elem[i]=elem[i+1];
        }
        usedSize--;//实时记录存储的数据个数!
        elem[usedSize] = 0;  //此时需要手动制空!
        //已经进行usedSize--了,此时的usedSize指向原来的usedSize-1的位置处!!
        return true;
    }

其实对于这个思路,再笔者进行学习的时候,都感觉有点儿绕口!!但是,当你进行深入的理解,以后,将会发现一片新大陆!!

 10. 清空顺序表

对于清空顺序表,其实有很多方法!想到清空,肯定用数组来遍历一遍,每个元素都置为0(引用类型置为null)但是,这样显得不就变得复杂化了吗??其实最简单的办法,就是,将usedSize=0就可以了!!因为,每一个方法的使用,都是用usedSize作为基础来进行调动的!!所以有了下面的简简单单的一行代码:

   // 清空顺序表
    public void clear() {
        usedSize=0;
    }

该数组是以usedSize为基础进行的,当usedSize为0的时候,那么就没有办法……

到目前为止,上面的十个方法都已经写完了!关键不知道大家有没有看懂也!!有点小尴尬!!

那么下面笔者便把这个顺序表所用的代码全部在下文进行列举出来!请大家注意一下文件名!!

Test.java文件当中的内容为:

public class Test {
    public static void main(String[] args) {
        MyArraylist myArraylist = new MyArraylist();
        myArraylist.add(1);
        myArraylist.add(2);
        myArraylist.add(3);
        myArraylist.display();
        myArraylist.clear();
 
        try{
            myArraylist.add(1,99);
        }catch (IndexoutOfException e){
            e.printStackTrace();
        }
        myArraylist.display();
        try {
            System.out.println(myArraylist.get(4));
        }catch (IndexoutOfException e) {
            e.printStackTrace();
        }
    }
}

MyArrayList.java文件当中的内容为:

import java.util.Arrays;
 
public class MyArraylist {
    public int[] elem; //该数组在定义的时候,初始状态下默认为null
    public int usedSize;//记录存储了多少个数据
    public static final int DEFAULT_SIZE =5;//定义数组的长度
 
    public MyArraylist(){
        this.elem=new int[DEFAULT_SIZE];//定义数组的长度
    }
 
    // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    public void display() {
        for (int i = 0; i < this.usedSize; i++) {
            //usedSize是数组存储的有效数字
            System.out.print(this.elem[i]+" ");
        }
        System.out.println();
    }
 
    //获取顺序表长度
    public int size () {
        return usedSize;
    }
 
    // 判定是否包含某个元素
    public boolean contains (int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i]==toFind) {
                return true;
            }
        }
        return false;
    }
 
    //查找某个元素对应的位置
    public int indefOf(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if(this.elem[i] == toFind) {
                return i;
            }
        }
        return -1;
        //返回-1,是因为数组中没有负数下标
    }
 
    //新增元素,默认在数组最后新增
    public void add(int data) {
        if (this.isFull()) {  //判断数组是否已经存储满了??
            resize();//扩容
        }
        this.elem[this.usedSize]=data;
        this.usedSize++;
    }
 
    //扩容
    private void resize() {
        this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
        //实现数组扩容,并且将扩容好的重新赋值给elem
    }
 
    //判断数组是否已经存储满了??
    public boolean isFull() {
        //方法1
        if (this.usedSize==this.elem.length) {
            return true;
        }
        return false;
 
        //方法2:
        //return this.usedSize==this.elem.length;
    }
 
    //在 pos 位置新增元素  时间复杂度为O(N)
    public void add(int pos , int data) {
        checkIndex(pos);//首先要检查pos的位置是否合法??
        if (isFull()) {  //判断数组是否存储满了??
            resize();//扩容
        }
        for (int i = usedSize-1; i >= pos; i--) {
            elem[i+1] = elem[i];
        }
        elem[pos] =data;
        usedSize++;
    }
 
    //检查add数据的时候,判断pos是否合法??
    private void checkIndex(int pos) {
        if (pos <0 || pos >=usedSize)
        throw new IndexoutOfException("get获取元素的时候,位置不合法!" +
                "请检查位置的合法性!!");
        //当pos的位置不合法的时候,抛出异常!!
    }
 
    //获取 pos 位置的元素
    public int get(int pos) {
        checkIndex(pos);//检查pos的合法性!
        return elem[pos];
    }
 
    //给 pos 位置的元素设为 value
    public void set (int pos , int value) {
        checkIndex(pos); //首先要检查pos位置的合法性!!
        elem[pos] = value;
    }
 
    //删除第一次出现的关键字key  时间复杂度为O(N)
    public boolean remove(int toRemove) {
        int index = indefOf(toRemove);//查找toRemove元素所对应的位置!
        if (index==-1) {
            System.out.println("没有这个元素!");
            return false;
        }
        for (int i = index; i < usedSize-1; i++) {
            elem[i]=elem[i+1];
        }
        usedSize--;//实时记录存储的数据个数!
        elem[usedSize] = 0;  //此时需要手动制空!
        //已经进行usedSize--了,此时的usedSize指向原来的usedSize-1的位置处!!
        return true;
    }
 
    // 清空顺序表
    public void clear() {
        usedSize=0;
    }
}

IndexoutOfException.java 文件当中的内容为:

public class IndexoutOfException extends RuntimeException{
    public IndexoutOfException() {
 
    }
 
    public IndexoutOfException(String msg) {
        super(msg);
    }
}

上述代码总的实现结果为:

注意:本文主要为了讲解一下顺序表之ArrayList的实现过程!!并不做其他的必须性!!对于最后的运行结果若有其他要求,请各位读者自行在Test中的main方法中进行更改!!

若有其他疑问,请及时联系笔者进行私聊哟!!

【版权声明】本文为华为云社区用户原创内容,转载时必须标注文章的来源(华为云社区)、文章链接、文章作者等基本信息, 否则作者和本社区有权追究责任。如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。