• 数据结构:ArrayList类和顺序表


    1.前言

    学前须知:

    • ArrayList类底层是实现是使用顺序表来实现的,本文是先通过顺序表来模拟实现ArrayList类,再通过使用ArrayList类来编写一些代码案例,以达到熟练掌握ArrayList类和顺序表相关知识点。
    • 顺序表本质上其实就是用数组来实现的。

    2.ArrayList常见的操作

    在模拟实现ArrayList类之前,我们先要知道ArrayList类中有哪些常见操作,才能有针对性地对类中的这些方法进行模拟实现。

    方法操作
    add(int data)在顺序表中的最后位置添加数据
    add(int pos, int data)在顺序表最后指定位置处添加数据
    indexOf(int toFind)查找顺序表最后的某个元素返回其下标
    get(int pos)获取pos位置上的元素
    set(int pos, int value)将pos位置上的元素替换成value值
    remove(int key)删除顺序表中的某个数据
    clear()清空顺序表

    注意:
            (1)ArrayList是一个动态类型的顺序表,也就是说,当顺序表为满的时候,插入元素会自动对顺序表进行扩容。
            (2)换句话说,接下来准备实现的顺序表中,也是需要手动对顺序表(数组)在必要的时候(数组为满时)进行扩容。

    3.模拟实现ArrayList

    3.1模拟实现add方法

    add方法是在顺序表(数组)中最后位置处添加数据。

    add方法可分为两种情况:一是直接在顺序表后面直接添加数据;二是在顺序表中指定位置处添加数据
            情况一:先判断顺序表是否是满的,如若是满,则需要先对这个顺序表进行扩容;如若不满,则直接在最后处添加即可。

        /**
         * 判断该顺序表是否为满
         * @return
         */
        private boolean isFull(){
            return this.usedSize==this.elem.length;
        }
    
        /**
         * 在顺序表中的最后位置添加数据
         * @param data
         */
        public void add(int data){
            if(isFull()){
                this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
            }
            this.elem[this.usedSize]=data;
            this.usedSize++;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

            情况二:除了需要像上面情况一一样判断顺序表为满,如若是满,则需要对顺序表进行扩容;除此之外,还需要判断指定下标是否合法(越界)。其中,在插入数据的时候,在插入数据的位置开始,往后的元素都需要先向后移动一个元素,再将需要插入的元素插入到该指定位置处即可。

        /**
         * 判断该顺序表是否为满
         * @return
         */
        private boolean isFull(){
            return this.usedSize==this.elem.length;
        }
    
        /**
         * 判断输入下标的有效性
         * @param pos
         * @return
         */
        private boolean checkPosInAdd(int pos){
            if(pos<0||pos>this.usedSize){
                return false;
            }
            return true;
        }
    
        /**
         * 在顺序表指定位置处添加数据
         * @param pos
         * @param data
         */
        public void add(int pos, int data){
            if(!checkPosInAdd(pos)){
                throw new MyArrayListIndexOutException("输入下标不合法");
            }
            if(isFull()){
                this.elem= Arrays.copyOf(this.elem,2*this.elem.length);
            }
            for (int i = this.usedSize-1; i >= pos; i--) {
                this.elem[i+1]=this.elem[i];
            }
            this.elem[pos]=data;
            this.usedSize++;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38

    3.2模拟实现indexOf方法

    indexOf方法是查找顺序表中的某个元素并返回其下标。

    该方法的实现并不是很难,直接上代码:

        /**
         * 查找顺序表中的某个元素返回其下标
         * @param toFind
         * @return
         */
        public int indexOf(int toFind){
            for (int i = 0; i < this.usedSize; i++) {
                if(this.elem[i]==toFind){
                    return i;
                }
            }
            return -1;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    3.3模拟实现 get 和 set 方法

    get方法是获取顺序表中某个位置上的元素;set方法是更新顺序表中某个位置上的元素。

            (1)get方法:需要先判断输入的下标是否是合法的,以及对顺序表的判断,判断其是否为空,再进行获取指定元素的操作。

        /**
         * 判断输入下标的有效性
         * @param pos
         * @return
         */
        private boolean checkPosInAdd(int pos){
            if(pos<0||pos>this.usedSize){
                return false;
            }
            return true;
        }
    
        /**
         * 判断顺序表时候为空
         * @return
         */
        private boolean isEmpty(){
            return this.usedSize==0;
        }
    
        /**
         * 获取pos位置上的元素
         * @param pos
         * @return
         */
        public int get(int pos){
            if(!checkPosInAdd(pos)){
                throw new MyArrayListIndexOutException("输入下标不合法");
            }
            if(isEmpty()){
                throw new MyArrayListEmptyException("顺序表为空");
            }
            return this.elem[pos];
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

            (2)set方法:也是一样需要先判断输入的下标是否是合法的,以及对顺序表的判断,判断其是否为空,再进行更新指定元素的操作。

        /**
         * 判断输入下标的有效性
         * @param pos
         * @return
         */
        private boolean checkPosInAdd(int pos){
            if(pos<0||pos>this.usedSize){
                return false;
            }
            return true;
        }
    
        /**
         * 判断顺序表时候为空
         * @return
         */
        private boolean isEmpty(){
            return this.usedSize==0;
        }
    
        /**
         * 将pos位置上的元素替换成value值
         * @param pos
         * @param value
         */
        public void set(int pos, int value){
            if(!checkPosInAdd(pos)){
                throw new MyArrayListIndexOutException("输入下标不合法");
            }
            if(isEmpty()){
                throw new MyArrayListEmptyException("顺序表为空");
            }
            this.elem[pos]=value;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34

    3.4模拟实现remove方法

    remove方法是用来删除顺序表中的某个元素(数据)。

            步骤一:判断顺序表是否是空的,如若是空的,则无需进行删除操作。
            步骤二:使用前面已经有的indexOf方法,找出所要删除元素的下标,找不到则无需进行下面的删除操作。
            步骤三:以所要删除元素的下标为起始点,将后面的所有元素都向前挪一格即可,最后将usedSize加1。

        /**
         * 查找顺序表中的某个元素返回其下标
         * @param toFind
         * @return
         */
        public int indexOf(int toFind){
            for (int i = 0; i < this.usedSize; i++) {
                if(this.elem[i]==toFind){
                    return i;
                }
            }
            return -1;
        }
    
        /**
         * 判断顺序表时候为空
         * @return
         */
        private boolean isEmpty(){
            return this.usedSize==0;
        }
    
        /**
         * 删除顺序表中的某个数据
         * @param key
         */
        public void remove(int key){
            if(isEmpty()){
                throw new MyArrayListEmptyException("顺序表为空");
            }
            int index=indexOf(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--;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40

    3.5模拟实现 size 和 clear 方法

    size方法是显示顺序表的长度;clear方法是清空顺序表。(这两个方法都只需对usedSize进行操作即可)

            (1)size方法:直接返回usedSize的值即可。

        /**
         * 显示顺序表的长度
         * @return
         */
        public int size(){
            return this.usedSize;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

            (1)get方法:直接将usedSize的值置为0即可。

        /**
         * 清空顺序表
         */
        public void clear(){
            this.usedSize=0;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这样就将顺序表中的一些常用方法都模拟实现出来了。

    4.ArrayList类的基础使用

    4.1一段入门代码

    模拟实现完顺序表之后,接下来就是看看ArrayList的基础使用,在实际写代码的过程中,我们基本都是直接使用ArrayList这个类来实现的,前面的模拟实现是让我们更好地、更深入地理解下面使用ArrayList类的使用代码。

    示例代码如下:

    public class Main {
        public static void main1(String[] args) {
            MyArraylist myArraylist=new MyArraylist();
            myArraylist.add(2);
            myArraylist.add(3);
            myArraylist.add(4);
            myArraylist.display();
            myArraylist.add(2,99);
            myArraylist.display();
            myArraylist.set(2,98);
            myArraylist.display();
            System.out.println(myArraylist.size());
            System.out.println(myArraylist.indexOf(3));
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    运行结果:
    在这里插入图片描述

    4.2一道经典题目

    对于ArrayList类这部分的题目中,有一道比较经典的题目 —— 杨辉三角(此处点开前往LeetCode提前刷此题)

            步骤一:将杨辉三角第一行是保证整个杨辉三角能够往下写的条件,较为特殊,单独处理,直接添加数字1即可。
            步骤二:在杨辉三角每一行头尾处的数字都保证是1,然后将每一行中间部分的每一个位置的数字都添加上一行该位置(j)的数据加上上一行该位置前面(j-1)的数据。
            步骤三:将每一行已经添加下来的数据都存到ret中即可。

    class Solution {
        public List<List<Integer>> generate(int numRows) {
            List<List<Integer>> ret=new ArrayList<>();
            List<Integer> one=new ArrayList<>();
            one.add(1);
            ret.add(one);
            for(int i=1;i<numRows;i++){
                List<Integer> curRow=new ArrayList<>();
                List<Integer> preRow=ret.get(i-1);
                curRow.add(1);
                for(int j=1;j<i;j++){
                    curRow.add(preRow.get(j)+preRow.get(j-1));
                }
                curRow.add(1);
                ret.add(curRow);
            }
            return ret;
        }
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    5.简谈顺序表的优缺点

    序表经常会与后面学到的链表做对比,两者的优缺点是互补的。这里先简单地讲讲顺序表这一部分:
            (1)顺序表对于数据的插入和删除(更新)效率都是比较低的,时间复杂度达到O(n),相对于后面学到的链表时间复杂度是偏大的,所以这是其缺点之一。
            (2)顺序表在插入数据的时候,可能会出现空间不足的情况,然而这时候往往就需要对顺序表进行扩容操作,这样有可能会造成一部分空间未被利用而导致空间浪费,这也是其缺点之一,到后面学习到的链表中则不会出现此类问题。
            (3)顺序表对于数据的查找效率是非常高的,这也是顺序表的一大亮点,可以直接通过下标来找到顺序表的的某一个数据,时间复杂度仅为O(1),相比于链表的O(n)有了明显的提升,所以,这是其优点之一。

    6.代码存放地址

    文中涉及到的全部代码都提交在此处,需要的可到此查看:顺序表完整代码

  • 相关阅读:
    二维数组的最小路径和问题
    活动目录(Active Directory)管理工具
    C语言之字符函数&字符串函数篇(1)
    计算机毕业设计(附源码)python疫情信息管理系统
    从零开始配置 vim(12)——主题配置
    STM32使用库函数点灯实验
    Spring()
    【微信小程序篇】- 组件
    实践了上万次,原来这些才是敏捷测试需要遵循的原则
    区,段,碎片区与表空间结构
  • 原文地址:https://blog.csdn.net/Faith_cxz/article/details/125386779