[数据结构]顺序表和ArrayList

顺序表的介绍

在了解顺序表之前先了解一下什么叫做线性表:

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

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

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

顺序表接口的实现

顺序表中是以数组来实现的,数据的大小是由数组的大小来决定的话每次新增和删除数据的话数组的大小实时,会造成频繁改变数组的大小,使用一个标志来记录顺序表的大小会更方便,不会经常改变数组大小 。

public class SeqList {
    private int[] elem;
    private int usedSize;//记录当前顺序表当中 有多少个有效的数据
    private static final int DEFAULT_CAPACITY = 10;
    public SeqList() {
        this.elem = new int[DEFAULT_CAPACITY];
    }
    // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    public void display() {
    }
    // 新增元素,默认在数据 最后新增
    public void add(int data) {
    }
	//判断顺序表是否满了
    private boolean isFull() {
    }
    // 判定是否包含某个元素
    public boolean contains(int toFind) {
        return false;
    }
    // 查找某个元素对应的位置
    public int indexOf(int toFind) {
        return -1;
    }
    // 获取 pos 位置的元素
    public int get(int pos)  {
    }
    // 获取顺序表长度
    public int size() {
    }
    // 给 pos 位置的元素设为 value【更新的意思 】
    public void set(int pos, int value) {
    }
    //检查pos下表是否合法
    private boolean checkPos(int pos){
    }
    // 在 pos 位置新增元素
    public void add(int pos, int data) {
    }
    private void resize() {
    }
    //删除第一次出现的关键字key
    public void remove(int toRemove) {
    }
    //判断顺序表是否为空
    public Boolean isEmpty(){
    }
    // 清空顺序表
    public void clear() {
    }
}

实现的第一个接口就是打印顺表,其实就是对数组进行一个遍历。

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

实现第二个接口新增数据,判定是否包含某个元素,查找某个元素所对应的位置。第一个是需要进行判断然后尾插就行。后两个直接遍历数组就可以:

// 新增元素,默认在数据 最后新增
public void add(int data) {
    if (isFull()){
        resize();//由于不仅仅有一个需要扩容的方法,所有将扩容单独封装成一个方法
    }
    this.elem[usedSize++] = data;
}
//判断顺序表是否满了
private boolean isFull() {
  return this.usedSize == this.elem.length;
}
//扩容
private void resize() {
    this.elem = Arrays.copyOf(this.elem,this.elem.length * 2);//扩容增加2倍的空间
}
// 判定是否包含某个元素
public boolean contains(int toFind) {
    for (int i = 0; i < this.usedSize; i++) {
        if (toFind == this.elem[i]){
            return true;
        }
    }
    return false;
}
// 查找某个元素对应的位置
public int indexOf(int toFind) {
    for (int i = 0; i < this.usedSize; i++) {
        if (toFind == this.elem[i]){
            return i;
        }
    }
    return -1;
}

进行新增数据的测试 :

public static void main(String[] args) {
    SeqList seqList = new SeqList();
    seqList.add(1);
    seqList.add(2);
    seqList.add(3);
    seqList.add(4);
    seqList.add(5);
    seqList.add(6);
    seqList.add(1);
    seqList.add(2);
    seqList.add(3);
    seqList.add(4);
    seqList.add(5);
    seqList.add(6);
    seqList.display();
    System.out.println(seqList.contains(1));//包含1
    System.out.println(seqList.contains(100));//不包含100
    System.out.println(seqList.indexOf(4));//由元素4的下标
    System.out.println(seqList.indexOf(50));//没有元素50
}

最后输出结果:

1 2 3 4 5 6 1 2 3 4 5 6
true
false
3
-1

查看数组的大小:

[数据结构]顺序表和ArrayList_第1张图片

成功扩容为20大小的数组。

实现获取pos位置元素和顺序表的长度,以及设置元素这三个接口。由于获取和设置元素都需要检查下标是否合法。所以我们写一个判断位置是否合法的方法。我们在判断非法的时候我们直接输出非法不合适,我们应该建立一个异常,然后抛出异常.

建立异常:

public class PosOutBoundsException extends RuntimeException{
    public PosOutBoundsException() {
    }
    public PosOutBoundsException(String message) {
        super(message);
    }
}

接口的实现:

// 获取 pos 位置的元素
public int get(int pos)  {
    if (!checkPos(pos)){
        throw new RuntimeException("get 元素位置错误");
    }
    return this.elem[pos];
}
// 获取顺序表长度
public int size() {
    return this.usedSize;
}
// 给 pos 位置的元素设为 value【更新的意思 】
public void set(int pos, int value) {
    if (!checkPos(pos)){
        throw new RuntimeException("set 元素位置错误");
    }
    this.elem[pos] = value;
}
//检查pos下表是否 合法
private boolean checkPos(int pos){
    if (pos < 0 || pos >= this.usedSize) return false;
    return true;
}

进行新增数据的测试 :

public static void main(String[] args) {
    SeqList seqList = new SeqList();
    seqList.add(1);
    seqList.add(2);
    seqList.add(3);
    seqList.add(4);
    seqList.add(5);
    seqList.add(6);
    System.out.println(seqList.get(3));
    seqList.display();
    seqList.set(3,1000);
    seqList.display();
    seqList.get(10);//获取元素位置错误抛出异常。
}

最后输出结果:

4
1 2 3 4 5 6
1 2 3 1000 5 6
Exception in thread “main” java.lang.RuntimeException: get 元素位置错误
at demoList2.SeqList.get(SeqList.java:56)
at demoList2.Test.main(Test.java:17)

实现剩下的四个接口,这四个接口有的是应用了之前的一些接口。

// 在 pos 位置新增元素
public void add(int pos, int data) {
    if (!checkPos(pos)){//判断位置受否合理
        throw new RuntimeException("add 元素位置错误");
    }
    if (isFull()){//判断是否满了,满了的话进行扩容
        resize();
    }
    //把后面的元素pos及其之后的元素往后挪一位
    for (int i = this.usedSize - 1; i >= pos; i--) {
        this.elem[i + 1] = this.elem[i];
    }
    this.elem[pos] = data;
    this.usedSize++;
}
//删除第一次出现的关键字key
public void remove(int toRemove) {
    if (isEmpty()){//判断顺序表是否为空
        return;//顺序表为空就不能进行删除
    }
    int pos = indexOf(toRemove);//找到该元素的下标
    if (pos == -1){//没有找到你要删除的数字
        return;
    }
    for (int i = pos; i < this.usedSize - 1; i++) {//将pos后的元素向前挪一位
        this.elem[i] = this.elem[i + 1];
    }
    this.usedSize--;
}
//判断顺序表是否为空
public Boolean isEmpty(){
    return this.usedSize == 0;
}
// 清空顺序表
public void clear() {
    this.usedSize = 0;
    //我们模拟实现的是基本数据类型。如果是引用数据类型的话,我们得遍历每个位置然后再进行赋值为null
}

进行新增数据的测试 :

public static void main(String[] args) {
    SeqList seqList = new SeqList();
    seqList.add(1);
    seqList.add(2);
    seqList.add(3);
    seqList.add(4);
    seqList.add(5);
    seqList.add(6);
    seqList.display();
    seqList.add(3,100);//将下标为3的位置插入100
    seqList.display();
    seqList.remove(2);//删除第二个位置的元素
    seqList.display();
}

最后输出结果:

1 2 3 4 5 6
1 2 3 100 4 5 6
1 3 100 4 5 6

上述就是顺序表的接口的实现。下面就来看一下java当中的容器是如何实现顺序表的。

ArrayList简介

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

[数据结构]顺序表和ArrayList_第2张图片

框架说明:

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

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

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

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

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

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

ArrayList的构造

ArrayList有三个构造方法:

[数据结构]顺序表和ArrayList_第3张图片

无参构造的源码分析

public ArrayList() {
    this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}

上面是ArrayList()无参构造的源码。那么elementData 和 DEFAULTCAPACITY_EMPTY_ELEMENTDATA是啥呢?我们接着找。

transient Object[] elementData; // non-private to simplify nested class access
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};

看到上面两行源码,我们可以得出elementData是一个没有指向数组的索引。DEFAULTCAPACITY_EMPTY_ELEMENTDATA是一个大小为0的数组

在无参构造中的:this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;这行代码表示让elementData 存储大小为0的数组的索引。

按照上述分析,这个数组没有分配大小,那么是如何存放数据的??为什么不会报错?

我们看一下add的源码就可以解决这个问题:

public boolean add(E e) {
    ensureCapacityInternal(size + 1);  // Increments modCount!!
    elementData[size++] = e;
    return true;
}

ensureCapacityInternal(size + 1);这个方法源码的注释是递增模数!!。其实就和我们在顺序表中判断其是否满了然后扩容这个操作是十分相似 的。我们接下来看一下他的源码:

private void ensureCapacityInternal(int minCapacity) {
    ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}

如果想要了解ensureCapacityInternal就必须要了解ensureExplicitCapacity和calculateCapacity的源码

查看calculateCapacity源码 (中文注释为我的解释):

private static int calculateCapacity(Object[] elementData, int minCapacity) {
    //DEFAULTCAPACITY_EMPTY_ELEMENTDATA是ArrayList进行无参初始化的时候
    //给其赋值的一个大小为0的数组。没有发生什么特殊的变化则是可以进入这个判断
    if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
        //private static final int DEFAULT_CAPACITY = 10;
        //上一行是DEFAULT_CAPACITY的源码,
        //下面一行代码是DEFAULT_CAPACITY,和minCapacity哪个值最大所比较
        return Math.max(DEFAULT_CAPACITY, minCapacity);//返回的是:10和1比较最大的10
    }
    return minCapacity;
    //若是传入的大小大于10的话返回的就是传入的minCapacity数值
}

查看ensureExplicitCapacity(calculateCapacity源码(中文注释为我的解释):

private void ensureExplicitCapacity(int minCapacity) {//minCapacity是10
    modCount++;
    
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        //minCapacity的大小为10,elementData.length的大小为0 这个判断语句可以进入
        grow(minCapacity);
}

查看grow源码(中文注释为我的解释):

private void grow(int minCapacity) {//minCapacity 为 10 
    // overflow-conscious code
    int oldCapacity = elementData.length;//oldCapacity 大小为 0
    //newCapacity 大小oldCapacity的1.5倍为 0
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //newCapacity - minCapacity的值为 -10 这个判断能够给进入
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;//newCapacity 为 10
    //这个判断语句进不去,MAX_ARRAY_SIZE是一个很大的值newCapacity - MAX_ARRAY_SIZE < 0
    //这个判断的作用在下面详解add源码的时候再解释
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    //newCapacity的值为10,elementData扩容大小为10
    elementData = Arrays.copyOf(elementData, newCapacity);
}

上面已经了解了第一次添加数据的情况,那么第11次添加数据需要再次扩容会发生什么?

我们主要是查看ensureExplicitCapacity(calculateCapacity 和 grow源码。原因是上面calculateCapacity 源码的分析时,若是传入的大小大于10的话返回的就是传入的minCapacity数值。所以我们ensureExplicitCapacity传递的minCapacit数据就是11

查看ensureExplicitCapacity(calculateCapacity源码(中文注释为我的解释):

private void ensureExplicitCapacity(int minCapacity) {//minCapacity是11
    modCount++;
    
    // overflow-conscious code
    if (minCapacity - elementData.length > 0)
        //minCapacity的大小为11,elementData.length的大小为10 这个判断语句可以进入
        grow(minCapacity);
}

查看grow源码(中文注释为我的解释):

private void grow(int minCapacity) {//minCapacity 为 11 
    // overflow-conscious code
    int oldCapacity = elementData.length;//oldCapacity 大小为 10
    //newCapacity 大小oldCapacity的1.5倍为 15
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //newCapacity - minCapacity的值为 4 这个判断不能够给进入
    // 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //这个判断语句进不去,MAX_ARRAY_SIZE是一个很大的值newCapacity - MAX_ARRAY_SIZE < 0
    //这个判断的作用在下面详解add源码的时候再解释
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);
    // minCapacity is usually close to size, so this is a win:
    //newCapacity的值为15,elementData扩容大小为15
    elementData = Arrays.copyOf(elementData, newCapacity);
}

那么前两次分析的时候MAX_ARRAY_SIZE时一个很大的数值,数值小的时候进入不了 if (newCapacity - MAX_ARRAY_SIZE > 0) 这个语句。那么数据很大的时候会发生什么情况?

我们查看MAX_ARRAY_SIZE 的源码:

  • private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;

这个数据是Integer.MAX_VALUE - 8。Integer.MAX_VALUE = 0x7fffffff,是一个奇数

如果我们的oldCapacity 等于(2/3)*(Integer.MAX_VALUE - 2)。那么会是怎样的逻辑。

private void grow(int minCapacity) {//minCapacity 为 (2/3)*(Integer.MAX_VALUE - 2)+1 
    // overflow-conscious code
    //oldCapacity 大小为 (2/3)*(Integer.MAX_VALUE - 2)
    int oldCapacity = elementData.length;
    //newCapacity 大小oldCapacity的1.5倍为 Integer.MAX_VALUE - 2
    int newCapacity = oldCapacity + (oldCapacity >> 1);
    //newCapacity - minCapacity的值为 大于0 这个判断不能够给进入
    if (newCapacity - minCapacity < 0)
        newCapacity = minCapacity;
    //MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8 
    //Integer.MAX_VALUE - 2 -  (Integer.MAX_VALUE - 8)> 0 这个判断可以进入
    if (newCapacity - MAX_ARRAY_SIZE > 0)
        newCapacity = hugeCapacity(minCapacity);//下面来看一下hugeCapacity的源码
    // minCapacity is usually close to size, so this is a win:
    //查看完成hugeCapacity代码之后发现newCapacity = Integer.MAX_VALUE
 	//扩容的大小为Integer.MAX_VALUE
    elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
    //minCapacity 的值为 Integer.MAX_VALUE - 2
    //if (minCapacity < 0)是为了让避免数据量超过整形数据的范围
    if (minCapacity < 0) // overflow
        throw new OutOfMemoryError();
    //Integer.MAX_VALUE - 2 > Integer.MAX_VALUE - 8
    //返回的参数Integer.MAX_VALUE
    return (minCapacity > MAX_ARRAY_SIZE) ?
        Integer.MAX_VALUE :
        MAX_ARRAY_SIZE;
}

综上所述,

  • ArrayList的无参构造是再没有添加数据的空间大小为0.

  • 进行添加数据时候进行再进行空间的增容,大小为10.

  • 不是第一次增容的时候按照1.5倍大小增容

  • 数据量很大的时候,扩容就不是1.5倍大小扩容了。而是最终扩展到Integer.MAX_VALUE大小

  • ArrayList最大的容量是Integer.MAX_VALUE

  • ArrayList容量大于Integer.MAX_VALUE 就抛出OutOfMemoryError();异常

其他构造的源码分析

利用其他 Collection 构建 ArrayList:

public ArrayList(Collection<? extends E> c) {
    elementData = c.toArray();
    if ((size = elementData.length) != 0) {
        // c.toArray might (incorrectly) not return Object[] (see 6260652)
        if (elementData.getClass() != Object[].class)
            elementData = Arrays.copyOf(elementData, size, Object[].class);
    } else {
        // replace with empty array.
        this.elementData = EMPTY_ELEMENTDATA;
    }
}

Collection c的含义:

  • 实现了Collection接口
  • : 你传入的数据,泛型E自己后者E的子类

指定顺序表初始容量:

public ArrayList(int initialCapacity) {
    if (initialCapacity > 0) {
        this.elementData = new Object[initialCapacity];
    } else if (initialCapacity == 0) {
        this.elementData = EMPTY_ELEMENTDATA;
    } else {
        throw new IllegalArgumentException("Illegal Capacity: "+
                                           initialCapacity);
    }
}

指定容量等于0 根据之前分析的源码可知add时 if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA)这个判断不会进入,第一次增加后增容的大小是1,然后一直是1.5倍的大小新增。

  • 指定容量大于0 创建一个指定容量的数组
  • 指定容量等于0 指向EMPTY_ELEMENTDATA(是一个大小为0的数组)
  • 指定容量等于0 add时第一次增加后增容的大小是1,然后一直是1.5倍的大小新增。
  • 指定容量小于0 抛出异常

创建ArrayList

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.add("hello"); // 编译失败,List已经限定了,list2中只能存储整形元素
    // list3构造好之后,与list中的元素一致
    List<Integer> list3 = new ArrayList<>(list2);
    // 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
    List list4 = new ArrayList();
    list4.add("111");
    list4.add(100);
}

通过idea查看四个list中的数据

[数据结构]顺序表和ArrayList_第4张图片

ArrayList常见操作

[数据结构]顺序表和ArrayList_第5张图片

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(1));
    list.set(1, "JavaWEB");
    System.out.println(list.get(1));
    // 在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
    list.add(1, "Java数据结构");
    System.out.println(list);
    // 删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
    list.remove("JVM");
    System.out.println(list);
    // 删除list中index位置上的元素,注意index不要超过list中有效元素个数,否则会抛出下标越界异常
    list.remove(list.size()-1);
    System.out.println(list);
    // 检测list中是否包含指定元素,包含返回true,否则返回false
    if(list.contains("测试课程")){
        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, 4);
    System.out.println(ret);
    list.clear();
    System.out.println(list.size());
}

AbstractCollection 重写了toString()方法。ArrayList 继承了AbstractCollection所以打印类名能够实现遍历

ArrayList的遍历

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 integer : list) {
        System.out.print(integer + " ");
    }
    System.out.println();
    Iterator<Integer> it = list.listIterator();
    while (it.hasNext()){
        System.out.print(it.next() + " ");
    }
    System.out.println();
}
  • ArrayList最长使用的遍历方式是:for循环+下标 以及 foreach
  • 迭代器是设计模式的一种,后序容器接触多了再给介绍

ArrayList的具体使用

我们用ArrayList 实现一个洗牌算法:

public class Card {
    public int rank;//牌面色
    public String suit;//花色

    public Card(String suit,int rank) {
        this.rank = rank;
        this.suit = suit;
    }

    @Override
    public String toString() {
        return suit + " " + rank;
    }
}
public class Test {
    //花色: 红桃 黑桃 梅花 方片
    private static final String[] SUITS = {"♥","♠","♣","♦"};

    public static void main(String[] args) {
        List<Card> cards = buyCard();//买牌
        System.out.println(cards);
        System.out.println("洗牌:");
        shuffle(cards);
        System.out.println(cards);
        //三个人每个人轮流揭5张牌
        List<Card> hand1 = new ArrayList<>();
        List<Card> hand2 = new ArrayList<>();
        List<Card> hand3 = new ArrayList<>();
        List<List<Card>> hand = new ArrayList<>();
        hand.add(hand1);
        hand.add(hand2);
        hand.add(hand3);
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 3; j++) {
                //揭牌动作
                Card card = cards.remove(0);
                //如何翻到指定人的手里呢?
                hand.get(j).add(card);
            }
        }
        System.out.println("第1个人的牌:");
        System.out.println(hand1);
        System.out.println("第2个人的牌:");
        System.out.println(hand2);
        System.out.println("第3个人的牌:");
        System.out.println(hand3);

    }

    private static void shuffle(List<Card> cards) {
        Random random = new Random();
        for (int i = cards.size() - 1; i > 0; i--) {
            int j = random.nextInt(i);
            //交换
            Card card = cards.get(i);
            cards.set(i,cards.get(j));
            cards.set(j,card);
        }
    }

    private static List<Card> buyCard() {
        List<Card> cards = new ArrayList<>();
        for (int i = 0; i < SUITS.length; i++) {
            for (int j = 1; j <= 13; j++) {
                Card card = new Card(SUITS[i],j);
                cards.add(card);
            }
        }
        return cards;
    }
}

输出结果:

[♥ 1, ♥ 2, ♥ 3, ♥ 4, ♥ 5, ♥ 6, ♥ 7, ♥ 8, ♥ 9, ♥ 10, ♥ 11, ♥ 12, ♥ 13, ♠ 1, ♠ 2, ♠ 3, ♠ 4, ♠ 5, ♠ 6, ♠ 7, ♠ 8, ♠ 9, ♠ 10, ♠ 11, ♠ 12, ♠ 13, ♣ 1, ♣ 2, ♣ 3, ♣ 4, ♣ 5, ♣ 6, ♣ 7, ♣ 8, ♣ 9, ♣ 10, ♣ 11, ♣ 12, ♣ 13, ♦ 1, ♦ 2, ♦ 3, ♦ 4, ♦ 5, ♦ 6, ♦ 7, ♦ 8, ♦ 9, ♦ 10, ♦ 11, ♦ 12, ♦ 13]
洗牌:
[♥ 11, ♦ 7, ♦ 8, ♦ 2, ♦ 9, ♦ 10, ♥ 1, ♦ 11, ♥ 7, ♣ 12, ♦ 6, ♦ 3, ♦ 4, ♠ 12, ♦ 5, ♠ 13, ♠ 10, ♠ 3, ♣ 11, ♦ 12, ♣ 6, ♣ 7, ♣ 5, ♥ 6, ♦ 13, ♠ 4, ♥ 13, ♠ 6, ♥ 12, ♣ 8, ♣ 9, ♥ 5, ♥ 3, ♥ 10, ♥ 8, ♣ 4, ♠ 8, ♣ 13, ♠ 2, ♥ 2, ♠ 5, ♠ 1, ♣ 10, ♣ 3, ♠ 11, ♥ 9, ♦ 1, ♣ 1, ♠ 7, ♣ 2, ♥ 4, ♠ 9]
第1个人的牌:
[♥ 11, ♦ 2, ♥ 1, ♣ 12, ♦ 4]
第2个人的牌:
[♦ 7, ♦ 9, ♦ 11, ♦ 6, ♠ 12]
第3个人的牌:
[♦ 8, ♦ 10, ♥ 7, ♦ 3, ♦ 5]

ArrayList的优缺点

优点:

  • 根据指定的下标(索引)去查找元素,效率非常高!时间复杂度O(1)
  • 更新元素也很快:更新指定下标的元素

缺点:

  • 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  • 每次插入数据,都需要移动元素,极端情况下,如果插入到0下标,那么移动的元素复杂度O(n)
  • 每次删除数据的时候,都需要移动元素,极端情况下,删除下标为0的元素:O(N)
  • 当满了之后,进行1.5扩容倍扩容,然后只放了1个元素, 势必会有一定的空间浪费 。

总结:顺序表适用于经常查找和更新元素的场景下才推荐使用

你可能感兴趣的:(算法和数据结构,数据结构,windows)