[数据结构]顺序表和ArrayList

news2024/11/26 14:36:04

顺序表的介绍

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

线性表(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

查看数组的大小:

image-20230722113004681

成功扩容为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接口,具体框架图如下:

image-20230722172719923

框架说明:

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

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

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

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

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

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

ArrayList的构造

ArrayList有三个构造方法:

image-20230722180228223

无参构造的源码分析

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<? extends E> c的含义:

  • 实现了Collection接口
  • <? extends E>: 你传入的数据,泛型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<Integer>已经限定了,list2中只能存储整形元素
    // list3构造好之后,与list中的元素一致
    List<Integer> list3 = new ArrayList<>(list2);
    // 避免省略类型,否则:任意类型的元素都可以存放,使用时将是一场灾难
    List list4 = new ArrayList();
    list4.add("111");
    list4.add(100);
}

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

image-20230722214950519

ArrayList常见操作

image-20230722215624978

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个元素, 势必会有一定的空间浪费 。

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/778426.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

数学建模入门-如何从0开始,掌握数学建模的基本技能

一、前言 本文主要面向没有了解过数学建模的同学&#xff0c;帮助同学们如何快速地进行数学建模的入门并且尽快地在各类赛事中获奖&#xff0c;或者写出优秀的数学建模论文。 在本文中&#xff0c;我将从什么是数学建模、数学建模的应用领域、数学建模的基本步骤、数学建模的技…

两天学会用Webpack打包前端代码-day01

(创作不易&#xff0c;感谢有你&#xff0c;你的支持&#xff0c;就是我前行的最大动力&#xff0c;如果看完对你有帮助&#xff0c;请留下您的足迹&#xff09; 目录 什么是 Webpack&#xff1f; 使用 Webpack 体验webpack打包过程 修改 Webpack 打包入口和出口 入口 出…

[JAVAee]线程安全

目录 线程安全的理解 线程不安全的原因 ①非原子性 ②可见性 ③代码重排序 体会何为不安全的线程 保证线程安全 一个代码在多线程的环境下就很容易出现错误. 线程安全的理解 线程安全是什么呢?通俗的来讲,线程安全就是在多线程的环境下,代码的结果是符合我们预期的,就…

R语言-数据分析及建模(第1节)分类与预测问题简介

小伙伴们&#xff0c;今天我们学习R语言-数据分析及建模&#xff08;第1节&#xff09;分类与预测问题简介 01-分类与预测问题简介 ◆分类和预测是预测问题的两种主要类型&#xff0c;分类主要是预测分类标号&#xff08;离散属性&#xff09; &#xff0c;而预测主要是建立连续…

Windows11的VS201x编译OpenCV+Contrib+CUDA

(1) CUDA下载&#xff0c;注意要和cudnn版本号相关。 我安装的是cuda11.0,注意VS2015不能编译CUDA11&#xff0c;所以用VS2015的话需要下载CUDA 10。因为更高的版本目前还没有cudnn。 (2) 下载和安装VS2015。 (3) 下载和解压CMake。 CMake地址&#xff1a; Releases Kitw…

性能测试Ⅱ(压力测试与负载测试详解)

协议 性能理论&#xff1a;并发编程 &#xff0c;系统调度&#xff0c;调度算法 监控 压力测试与负载测试的区别是什么&#xff1f; 负载测试 在被测系统上持续不断的增加压力&#xff0c;直到性能指标(响应时间等)超过预定指标或者某种资源(CPU&内存)使用已达到饱和状…

每天五分钟机器学习:多项式非线性回归模型

本文重点 在前面的课程中,我们学习了线性回归模型和非线性回归模型的区别和联系。多项式非线性回归模型是一种用于拟合非线性数据的回归模型。与线性回归模型不同,多项式非线性回归模型可以通过增加多项式的次数来适应更复杂的数据模式。在本文中,我们将介绍多项式非线性回…

[SpringMVC]仿写SpringMVC(注解开发)

文章目录 前提&#xff1a;1、代码结构2、核心&#xff1a;YhzMVC3、初始化步骤是&#xff1a;4、执行 前提&#xff1a; 当前版本无接受网络请求功能&#xff0c;不喜勿喷&#x1f64f;&#x1f64f; 本文将对代码核心进行讲解&#xff0c;源码已上传到gitee仓库 1、代码结构…

SpringBoot集成Druid实现数据库连接池

一、引入依赖 完整的pom文件如下所示: <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0" xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http…

第四讲:MySQL中DDL一些基本数据类型及表的创建、查询

目录 1、创建表:2、DDL一些基本数据类型&#xff1a; 1、创建表: 部分单词及解析&#xff1a; 1、tables:表 2、comment:评论&#xff0c;解释 3、gender:性别 4、neighbor&#xff1a;邻居 1、创建表&#xff1a;&#xff08;注&#xff1a;在自定义数据库操作&#xff0c;…

Vue--》打造个性化医疗服务的医院预约系统(二)

今天开始使用 vue3 + ts 搭建一个医院预约系统的前台页面,因为文章会将项目的每一个地方代码的书写都会讲解到,所以本项目会分成好几篇文章进行讲解,我会在最后一篇文章中会将项目代码开源到我的GithHub上,大家可以自行去进行下载运行,希望本文章对有帮助的朋友们能多多关…

分布式锁:Redis、Zookeeper

1.基于Redis实现分布式锁&#xfeff; Redis分布式锁原理如上图所示&#xff0c;当有多个Set命令发送到Redis时&#xff0c;Redis会串行处理&#xff0c;最终只有一个Set命令执行成功&#xff0c;从而只有一个线程加锁成功 2.SetNx命令加锁 利用Redis的setNx命令在Redis数据库…

01 openEuler操作系统介绍

文章目录 01 openEuler操作系统介绍1.1 发布件1.2 最小硬件要求1.3 硬件兼容性1.4 关键特性1.4.1 openEuler 22.03-LTS基于 Linux Kernel 5.10 内核构建, 在进程调度、内存管理等方面带来10余处创新1.4.2 新介质文件系统1.4.3 内存分级扩展1.4.4 用户态协议栈1.4.5 云原生调度增…

【人工智能】深度优先搜索、代价一致搜索、深度有限搜索、迭代深度优先搜索、图搜索

【人工智能】无信息搜索—BFS 、代价一致、DFS、深度受限、迭代深入深度优先、图搜索 什么是搜索 搜索问题是指既不能通过数学建模解决,又没有其他算法可以套用或者非遍历所有情况才能得出正确结果。这时就需要采用搜索算法来解决问题。搜索就是一种通过穷举所有解的状态,来…

【车载开发系列】AUTOSAR DemDTCAttributes

【车载开发系列】AUTOSAR DemDTCAttributes 【车载开发系列】AUTOSAR DemDTCAttributes 【车载开发系列】AUTOSAR DemDTCAttributes一. DemDTCAttributes概念二. DemAgingCycleCounterThreshold三. DemAgingAllowed四. DemDTCPriority五. DemImmediateNvStorage六. DemMaxNumbe…

BatchNorm, LayerNorm, InstanceNorm和GroupNorm

1. 介绍 Batch Norm: 对NHW计算归一化参数(均值和方差)&#xff0c;总共得到C组归一化参数, 相当于对每个channel进行归一化。BN主要缺点是对batchsize的大小比较敏感&#xff0c;由于每次计算均值和方差是在一个batch上&#xff0c;所以如果batchsize太小&#xff0c;则计算的…

idea2021.安装pojie教程

1、下载ideaIU-2021.3应用包&#xff0c;点击finish 2、先关闭idea窗口&#xff0c;等会激活了脚本再运行打开。 3、双击运行install-current-user.vbs&#xff0c;等待一会会提示运行成功。 4、运行后&#xff0c;在文件中会多出一条配置 5、打开运行idea,输入激活码&#x…

iPhone 开机停留在苹果logo画面(已解决)

一、问题 如下图&#xff0c;开不了机&#xff1a; 标题 二、根因 存储空间满了。 三、解决方法 方法一 用苹果数据线&#xff08;最好是原装&#xff09;连接Mac电脑&#xff0c;在装有 macOS Catalina 10.15 或更高版本的 Mac 上&#xff0c;打开“访达”。在装有 macOS…

Vue-组件高级(上)

一、目标 能够掌握watch侦听器的基本使用能够知道vue中常用的生命周期函数能够知道如何实现组件之间的数据共享能够知道如何在vue3.x的项目中全局配置axios 二、目录 watch侦听器 1.什么是watch侦听器 watch侦听器允许开发之监视数据的变化&#xff0c;从而针对数据的变化做…

什么小程序需要商家自营相关类目?

1、百货&#xff1a;小程序主体公司综合零售商&#xff0c;在线售卖多种日用品&#xff0c;需补充商家自营-百货类目。预包装食品定义&#xff1a; 预包装食品&#xff0c;指预先定量包装或者制作在包装材料和容器中的食品&#xff1b;包括预先定量包装以及预先定量制作在包装…