数据结构-ArrayList与顺序表

1. 线性表

目录

[1. 线性表](#1. 线性表)

2.顺序表

[2.1 接口的实现](#2.1 接口的实现)

[3. ArrayList简介](#3. ArrayList简介)

[4. ArrayList使用](#4. ArrayList使用)

[4.1 ArrayList的构造](#4.1 ArrayList的构造)

[4.2 ArrayList常见操作](#4.2 ArrayList常见操作)

[4.3 ArrayList的遍历](#4.3 ArrayList的遍历)

[4.4 ArrayList的扩容机制](#4.4 ArrayList的扩容机制)

[5. ArrayList的具体使用](#5. ArrayList的具体使用)

[5.1 简单的洗牌算法](#5.1 简单的洗牌算法)

[6. ArrayList的问题及思考](#6. ArrayList的问题及思考)


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

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

2.顺序表

顺序表是一段物理地址连续的存储单元依次存储数据元素的线性结构,一般情况下采用数组存储。

在数组上完成数据的增删改查。

2.1 接口的实现

java 复制代码
public class SeqList {
    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() { }
}

3. ArrayList简介

在集合框架中,ArrayList是一个普通的类,实现了List接口,具体框架图如下:

【说明】

  1. ArrayList是以泛型方式实现的,使用时必须要先实例化

  2. ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问

  3. ArrayList实现了Cloneable接口,表明ArrayList是可以clone的

  4. ArrayList实现了Serializable接口,表明ArrayList是支持序列化的

  5. 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者 CopyOnWriteArrayList

  6. ArrayList底层是一段连续的空间,并且可以动态扩容,是⼀个动态类型的顺序表

4. ArrayList使用

4.1 ArrayList的构造

java 复制代码
public static void main(String[] args) {
        //ArrayList创建,推荐写法
        //创建一个空的列表
        List<Integer> list1=new ArrayList<>();
        //创建一个具有10个容量的列表
        List<Integer> list2=new ArrayList<>(10);
        list2.add(1);
        list2.add(2);
        list2.add(3);
        //将 list2 中的所有元素复制进去。
        //这是 ArrayList 的一个构造函数:public ArrayList(Collection<? extends E> c)
        ArrayList<Integer> list3=new ArrayList<>(list2);
        // 避免省略类型,否则:任意类型的元素都可以存放,使⽤时将是⼀场灾难
        List list4 = new ArrayList();
        list4.add("111");
        list4.add(100);
    }

4.2 ArrayList常见操作

java 复制代码
public static void main(String[] args) {
        List<String> list=new ArrayList<>();
        list.add("JavaSE");
        list.add("JavaWeb");
        list.add("JavaEE");
        list.add("JVM");
        list.add("测试课程");
        System.out.println( list);
        //获取list中有效元素的个数
        System.out.println(list.size());
        // 获取和设置index位置上的元素,注意index必须介于[0, size)间
        System.out.println(list.get(0));
        list.set(0,"JAVA");
        System.out.println(list.get(0));
        System.out.println( list);
        // 在list的index位置插⼊指定元素,index及后续的元素统⼀往后搬移⼀个位置
        list.add(0,"JAVA2");
        System.out.println(list);
        // 删除指定元素,找到了就删除,该元素之后的元素统⼀往前搬移⼀个位置
        list.remove("JVM");
        System.out.println(list);
        // 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
        list.remove(0);
        System.out.println(list);
        // 检测list中是否包含指定元素,包含返回true,否则返回false
        if (list.contains("JavaEE")){
            list.add("测试课程");
        }
        // 查找指定元素第⼀次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
        list.add("JavaSE");
        System.out.println(list.indexOf("JavaSE"));
        System.out.println(list.lastIndexOf("JavaSE"));
        // 使⽤list中[0, 4)之间的元素构成⼀个新的SubList返回,但是和ArrayList共⽤⼀个elementData数组
        List<String> ret=list.subList(0, 3);
        System.out.println(ret);
        list.clear();
        System.out.println(list);
    }

4.3 ArrayList的遍历

ArrayList 可以使用三种方式遍历:for循环+下标、foreach、使用迭代器

java 复制代码
public static void main(String[] args) {
        List<Integer> list=new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        list.add(5);
        // 使⽤下标+for遍历
        for (int i=0;i<list.size();i++){
            System.out.print(list.get(i)+" ");
        }
        System.out.println();
        //借助foreach遍历
        for (Integer i:list){
            System.out.print(i+" ");
        }
        System.out.println();
        //使用迭代器遍历
        Iterator<Integer> it=list.iterator();
        while (it.hasNext()){
            System.out.print(it.next()+" ");
        }
        System.out.println();
    }

4.4 ArrayList的扩容机制

ArrayList是一个动态类型的顺序表,即:在插入元素的过程中会自动扩容。以下是ArrayList源码 (JDK17)中扩容方式:

java 复制代码
public boolean add(E e) {
        modCount++;
        add(e, elementData, size);
        return true;
    }
    private void add(E e, Object[] elementData, int s) {
        if (s == elementData.length) //第⼀次存储的时候 elementData.length=0,s = 0
            elementData = grow();
        elementData[s] = e;
        size = s + 1;
    }
    private Object[] grow() {
        return grow(size + 1);
    }
    private Object[] grow(int minCapacity) {
        int oldCapacity = elementData.length;
        if (oldCapacity > 0 || elementData != DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
            int newCapacity = ArraysSupport.newLength(oldCapacity,
                    minCapacity - oldCapacity, /* minimum growth */
                    oldCapacity >> 1 /* preferred growth */);
            return elementData = Arrays.copyOf(elementData, newCapacity);
        } else {
            return elementData = new Object[Math.max(DEFAULT_CAPACITY,
                    minCapacity)];
        }
    }
    //oldLength: 当前数组的⻓度
//minGrowth: 最⼩需要增加的⻓度
//prefGrowth: ⾸选的增⻓ 度(通常是当前⼤⼩的⼀半)
    public static int newLength(int oldLength, int minGrowth, int prefGrowth) {
        // preconditions not checked because of inlining
        // assert oldLength >= 0
        // assert minGrowth > 0
        //当前⻓度加上 minGrowth 和 prefGrowth 中的较⼤值。
        int prefLength = oldLength + Math.max(minGrowth, prefGrowth); // might
        overflow
        if (0 < prefLength && prefLength <= SOFT_MAX_ARRAY_LENGTH) {
            return prefLength;
        } else {
            // put code cold in a separate method
            return hugeLength(oldLength, minGrowth);
        }



    }
    private static int hugeLength(int oldLength, int minGrowth) {
        int minLength = oldLength + minGrowth;
        if (minLength < 0) { // overflow
            throw new OutOfMemoryError(
                    "Required array length " + oldLength + " + " + minGrowth + " is too large");
        } else if (minLength <= SOFT_MAX_ARRAY_LENGTH) {
            return SOFT_MAX_ARRAY_LENGTH;
        } else {
            return minLength;
        }
    }

如果调用不带参数的构造方法,第一次分配数组大小为10

后续扩容为1.5倍扩容

5. ArrayList的具体使用

5.1 简单的洗牌算法

java 复制代码
class Card{
    public int rank;//牌面值
    public String suit;//花色

    @Override
    public String toString() {
        return String.format("[%s %d]", suit, rank);
    }
}
public class CardDemo {
    public static final String[] SUITS={"♠","♥","♣","♦"};
    //买一副牌
    private static List<Card> buyDeck(){
        List<Card> deck=new ArrayList<>();
        for (int i=0;i<4;i++){
            for (int j=1;j<=13;j++){
                String suit=SUITS[ i];
                int rank=j;
                Card card=new Card();
                card.rank=rank;
                card.suit=suit;
                deck.add(card);
            }
        }
        return deck;
    }
    //交换两张牌的位置
    private static void swap(List<Card> deck,int i,int j){
        Card t=deck.get(i);
        deck.set(i,deck.get(j));
        deck.set(j,t);
    }
    //从后往前依次遍历[0,i),交换随机r位置的牌和当前i位置的牌
    private static void shuffle(List<Card> deck){
        Random random=new Random();
        for (int i=deck.size()-1;i>0;i--){
            int r=random.nextInt(i);
            swap(deck,i,r);
        }
    }

    public static void main(String[] args) {
        List<Card> deck=buyDeck();
        System.out.println("刚买回来的牌:");
        System.out.println( deck);
        shuffle(deck);
        System.out.println("洗过的牌:");
        System.out.println(deck);
        // 三个⼈,每个⼈轮流抓 5 张牌
        List<List<Card>> hands=new ArrayList<>();
        hands.add(new ArrayList<>());
        hands.add(new ArrayList<>());
        hands.add(new ArrayList<>());
        for (int i=0;i<5;i++){
            for (int j=0;j<3;j++){
                hands.get(j).add(deck.remove(0));
            }
        }
        System.out.println("剩余的牌");
        System.out.println(deck);
        System.out.println("A ⼿中的牌:");
        System.out.println(hands.get(0));
        System.out.println("B ⼿中的牌:");
        System.out.println(hands.get(1));
        System.out.println("C ⼿中的牌:");
        System.out.println(hands.get(2));

    }
}

6. ArrayList的问题及思考

  1. ArrayList底层使用连续的空间,任意位置插入或删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)。

  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。

  3. 增容一般是呈1.5倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到150, 我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了45个数据空间。

相关推荐
Java 码农4 小时前
Centos7 maven 安装
java·python·centos·maven
harmful_sheep4 小时前
maven mvn 安装自定义 jar 包
java·maven·jar
007php0075 小时前
某大厂跳动面试:计算机网络相关问题解析与总结
java·开发语言·学习·计算机网络·mysql·面试·职场和发展
JH30735 小时前
第七篇:Buffer Pool 与 InnoDB 其他组件的协作
java·数据库·mysql·oracle
程序员莫小特6 小时前
老题新解|大整数加法
数据结构·c++·算法
皮皮林5516 小时前
订单分库分表后,商家如何高效的查询?
java
Roye_ack7 小时前
【项目实战 Day12】springboot + vue 苍穹外卖系统(Apache POI + 工作台模块 + Excel表格导出 完结)
java·spring boot·后端·excel·苍穹外卖
小刘max7 小时前
深入理解队列(Queue):从原理到实践的完整指南
数据结构
蒙奇D索大8 小时前
【数据结构】考研数据结构核心考点:二叉排序树(BST)全方位详解与代码实现
数据结构·笔记·学习·考研·算法·改行学it