Java中List、ArrayList与顺序表

news2024/9/22 3:04:56

List、ArrayList与顺序表

  • List
    • 什么是List
    • 常用方法介绍
    • List的使用
  • ArrayList与顺序表
    • 线性表
    • 顺序表
      • 接口的实现
    • ArrayList简介
    • ArrayList的使用
      • ArrayList的构造
      • ArrayList的常见操作
      • ArrayList的遍历
      • ArrayList的扩容机制
    • ArrayList的具体使用
      • 杨辉三角
      • 简单的洗牌算法
    • ArrayList的问题及思考

List

什么是List

在集合框架中,List是一个接口,继承自Collection。
在这里插入图片描述
Collection 也是一个接口,该接口中规范了后序容器中常用的一些方法,具体如下所示:
在这里插入图片描述
Iterable 也是一个接口,表示实现该接口的类是可以逐个元素进行遍历的,具体如下:
在这里插入图片描述
List的官方文档
在数据结构的角度看,List就是一个线性表,即n个具有相同类型元素的有限序列,在该序列上可以进行增删查改以及变量等操作。

常用方法介绍

方法解释
boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c)尾插 c 中的元素
E remove(int index)删除 index 位置元素
boolean remove(Object o)删除遇到的第一个 o
E get(int index)获取下标 index 位置元素
E set(int index, E element)将下标 index 位置元素设置为 element
void clear()清空
boolean contains(Object o)判断 o 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List< E > subList(int fromIndex, int toIndex)截取部分 list

List的使用

注意:List是一个接口,并不能直接用来实例化。
如果要使用,必须去实例化List的实现类。在集合框架中,ArrayList和LinkedList都实现了List接口。

ArrayList与顺序表

线性表

线性表(linear list)是n个具有相同特性的数据元素的有限序列。线性表实际上是一种在实际中广泛使用的数据结构,常见的线性表有:顺序表、链表、栈、队列…
线性表在逻辑上是线性结构,也就是说是一条连续的直线,但是在物理结构上并不是连续的,线性表在物理上存储时,通常以数组和链式结构的形式存储。
在这里插入图片描述

顺序表

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

接口的实现

我们通过模拟实现来进一步学习ArrayList

public interface IList {
    // 新增元素,默认在数组最后新增
    public void add(int data);
    // 在 pos 位置新增元素
    public void add(int pos, int data);
    // 判定是否包含某个元素
    public boolean contains(int toFind);
    // 查找某个元素对应的位置
    public int indexOf(int toFind);
    // 获取 pos 位置的元素
    public int get(int pos);
    // 给 pos 位置的元素设为 value
    public void set(int pos, int value);
    //删除第一次出现的关键字key
    public void remove(int toRemove);
    // 获取顺序表长度
    public int size();
    // 清空顺序表
    public void clear();
    // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    public void display();
}
public class MyArrayList implements IList{
    public int[] array;
    public int usedSize;
    public static final int DEFAULT_CAPACITY = 5;

    public MyArrayList() {
        this.array = new int[DEFAULT_CAPACITY];
    }
}

接下来对接口中的方法进行重写
首先从最简单的打印顺序表和获取顺序表长度开始

@Override
public int size() {
    return this.usedSize;
}
@Override
public void display() {
    for (int i = 0; i < usedSize; i++) {
        System.out.print(array[i] + " ");
    }
}

接下来实现add(新增元素,默认在数组最后新增)
在实现之前,我们需要思考该数组会不会已经放满了,我们需要对其检查,若是放满我们还需要对其扩容,我们默认大小只有5,代码并不是简简单单的插入元素这么简单。

public boolean isFull(int[] array){
    return this.usedSize == array.length;
}

private void grow(){
    this.array = Arrays.copyOf(this.array,this.array.length * 2);
}

@Override
public void add(int data) {
    if(isFull(this.array)){
        grow();
    }
    array[usedSize] = data;
    usedSize++;
}

在 pos 位置新增元素,我们需要对pos及后面的元素往后移,从而空出位置来插入,我们需要从usedSize-1开始往后移,而不是pos,因为从pos往后会将后面的元素进行覆盖,从而丢失数据。还有我们依然需要对数组是否已经放满进行检查,而且需要对pos是否合法进行检查,负数是不可以的,超过数组usedSize是不可以插入的。usedSize这个位置是可以插入的,因为规定每次插入数据的位置,前驱必须存在,也就是说插入位置的前一个不能是空的。

public class PosIllegal extends RuntimeException{
    public PosIllegal() {

    }
    public PosIllegal(String msg) {
        super(msg);
    }
}
private void checkPosOfAdd(int pos) throws PosIllegal{
    if(pos < 0 || pos > this.usedSize){
        throw new PosIllegal("插入位置不合法");
    }
}
@Override
public void add(int pos, int data) {
    try{
        checkPosOfAdd(pos);
        if(isFull(this.array)){
            grow();
        }
        for (int i = usedSize - 1; i >= pos ; i--) {
            this.array[i + 1] = this.array[i];
        }
        array[pos] = data;
        usedSize++;
    }catch(PosIllegal e){
        e.printStackTrace();
    }
}

接下来实现判定是否包含某个元素和查找某个元素对应的位置的方法。

 @Override
 public boolean contains(int toFind) {
     for (int i = 0; i < this.usedSize; i++) {
         if(array[i] == toFind){
             return true;
         }
         //如果存放的不是整型元素,而是引用类型的元素,则需要使用equals方法来比较,并且重写该方法。
     }
     return false;
 }

 @Override
 public int indexOf(int toFind) {
     for (int i = 0; i < this.usedSize; i++) {
         if(array[i] == toFind){
             return i;
         }
         //如果存放的不是整型元素,而是引用类型的元素,则需要使用equals方法来比较,并且重写该方法。
     }
     return -1;
 }

获取 pos 位置的元素,对于获取元素,我们依然需要对pos位置是否合法进行检查,但是这一次pos位置不能小于0,而且不能大于usedSize -1,因为数组从0开始,usedSize不可能存放元素,对于获取元素我们还应该考虑一点,就是当数组为空时,我们是不能获取到任何元素的,所以我们需要对数组是否为空进行检查,当我们发现其为空时,我们需要抛出异常,因为无论我们返回何整数,都有可能在数组中存在,所以抛出异常是最好的。

public boolean isEmpty(){
    return this.usedSize == 0;
}

private void checkEmpty(){
    if(isEmpty()){
        throw new EmptyException("顺序表为空");
    }
}

private void checkPosOfGet(int pos){
    if(pos < 0 || pos > this.usedSize -1){
        throw new PosIllegal("获取元素的位置不合法");
    }
}

@Override
public int get(int pos) {
    try{
        checkEmpty();
        checkPosOfGet(pos);
        return array[pos];
    }catch(PosIllegal e){
       e.printStackTrace();
    }catch(EmptyException e){
        e.printStackTrace();
    }
    return -1;
}

给 pos 位置的元素设为 value,这就是更新的意思,也就是与得到元素类似需要对其位置进行检查,不能不能小于0,而且不能大于usedSize -1,也要对数组是否为空进行检查。

@Override
public void set(int pos, int value) {
    try{
        checkEmpty();
        checkPosOfGet(pos);
        array[pos] = value;
    }catch(PosIllegal e){
        e.printStackTrace();
    }catch(EmptyException e){
        e.printStackTrace();
    }
}

删除第一次出现的关键字key,首先要对顺序表是否为空进行判断,空是没办法删除的。不为空之后我们可以通过遍历查找该元素的下标,找不到直接返回,找到对其后的元素进行挪动来覆盖,最后不要忘了usedSize进行减一。

 @Override
 public void remove(int toRemove) {
     try{
         checkEmpty();
         int pos = indexOf(toRemove);
         if(pos == -1){
             return;
         }
         for (int i = pos; i < this.usedSize - 1; i++) {
             this.array[i] = this.array[i+1];
         }
         this.usedSize--;
     }catch(EmptyException e){
         e.printStackTrace();
     }
 }

清空顺序表,对于清空顺序表,在int数组中我们可以对其usedSize置为0,后面在add也只是覆盖,但是如果是引用类型,这样会造成内存泄漏,因为数组中依然有一段地址指向一个空间,而这个空间并没有什么作用,所以应该将其置为null。

@Override
public void clear() {
    this.usedSize = 0;
    /*for (int i = 0; i < this.usedSize; i++) {
        this.array[i] = null;
    }*/
}

到这里我们就将ArrayList中常用的方法模拟实现了。下面为完整代码和测试代码

public interface IList {
    // 新增元素,默认在数组最后新增
    public void add(int data);
    // 在 pos 位置新增元素
    public void add(int pos, int data);
    // 判定是否包含某个元素
    public boolean contains(int toFind);
    // 查找某个元素对应的位置
    public int indexOf(int toFind);
    // 获取 pos 位置的元素
    public int get(int pos);
    // 给 pos 位置的元素设为 value
    public void set(int pos, int value);
    //删除第一次出现的关键字key
    public void remove(int toRemove);
    // 获取顺序表长度
    public int size();
    // 清空顺序表
    public void clear();
    // 打印顺序表,注意:该方法并不是顺序表中的方法,为了方便看测试结果给出的
    public void display();
}
public class PosIllegal extends RuntimeException{
    public PosIllegal() {

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

    public EmptyException(String message) {
        super(message);
    }
}
import java.util.Arrays;
public class MyArrayList implements IList{
    public int[] array;
    public int usedSize;
    public static final int DEFAULT_CAPACITY = 5;

    public MyArrayList() {
        this.array = new int[DEFAULT_CAPACITY];
    }

    public boolean isFull(int[] array){
        return this.usedSize == array.length;
    }

    private void grow(){
        this.array = Arrays.copyOf(this.array,this.array.length * 2);
    }

    @Override
    public void add(int data) {
        if(isFull(this.array)){
            grow();
        }
        array[usedSize] = data;
        usedSize++;
    }

    private void checkPosOfAdd(int pos) throws PosIllegal{
        if(pos < 0 || pos > this.usedSize){
            throw new PosIllegal("插入位置不合法");
        }
    }
    @Override
    public void add(int pos, int data) {
        try{
            checkPosOfAdd(pos);
            if(isFull(this.array)){
                grow();
            }
            for (int i = usedSize - 1; i >= pos ; i--) {
                this.array[i + 1] = this.array[i];
            }
            array[pos] = data;
            usedSize++;
        }catch(PosIllegal e){
            e.printStackTrace();
        }

    }

    @Override
    public boolean contains(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if(array[i] == toFind){
                return true;
            }
            //如果存放的不是整型元素,而是引用类型的元素,则需要使用equals方法来比较,并且重写该方法。
        }
        return false;
    }

    @Override
    public int indexOf(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if(array[i] == toFind){
                return i;
            }
            //如果存放的不是整型元素,而是引用类型的元素,则需要使用equals方法来比较,并且重写该方法。
        }
        return -1;
    }

    public boolean isEmpty(){
        return this.usedSize == 0;
    }

    private void checkEmpty(){
        if(isEmpty()){
            throw new EmptyException("顺序表为空");
        }
    }

    private void checkPosOfGet(int pos){
        if(pos < 0 || pos > this.usedSize -1){
            throw new PosIllegal("获取元素的位置不合法");
        }
    }

    @Override
    public int get(int pos) {
        try{
            checkEmpty();
            checkPosOfGet(pos);
            return array[pos];
        }catch(PosIllegal e){
           e.printStackTrace();
        }catch(EmptyException e){
            e.printStackTrace();
        }
        return -1;
    }

    @Override
    public void set(int pos, int value) {
        try{
            checkEmpty();
            checkPosOfGet(pos);
            array[pos] = value;
        }catch(PosIllegal e){
            e.printStackTrace();
        }catch(EmptyException e){
            e.printStackTrace();
        }
    }

    @Override
    public void remove(int toRemove) {
        try{
            checkEmpty();
            int pos = indexOf(toRemove);
            if(pos == -1){
                return;
            }
            for (int i = pos; i < this.usedSize - 1; i++) {
                this.array[i] = this.array[i+1];
            }
            this.usedSize--;
        }catch(EmptyException e){
            e.printStackTrace();
        }
    }

    @Override
    public int size() {
        return this.usedSize;
    }

    @Override
    public void clear() {
        this.usedSize = 0;
        /*for (int i = 0; i < this.usedSize; i++) {
            this.array[i] = null;
        }*/
    }

    @Override
    public void display() {
        for (int i = 0; i < usedSize; i++) {
            System.out.print(array[i] + " ");
        }
        System.out.println();
    }
}
public class Test {
    public static void main(String[] args) {
        MyArrayList list1 = new MyArrayList();
        IList list2 = new MyArrayList();
        System.out.println("初始有效元素个数:");
        System.out.println(list1.usedSize);
        System.out.println("打印初始顺序表");
        list1.display();
        System.out.println("打印初始数组大小");
        System.out.println(list1.size());
        list1.add(1);
        list1.add(2);
        list1.add(3);
        list1.add(4);
        System.out.println("打印插入元素后的顺序表");
        list1.display();
        System.out.println("打印插入元素后的顺序表大小");
        System.out.println(list1.size());
        list1.add(2,33);
        System.out.println("打印在指定位置插入元素后的顺序表");
        list1.display();
        //list1.add(44,4);
        System.out.println("顺序表是否包含某个元素");
        System.out.println(list1.contains(2));
        System.out.println(list1.contains(55));
        System.out.println("查找某个元素的指定位置");
        System.out.println(list1.indexOf(2));
        System.out.println(list1.indexOf(44));
        System.out.println("获取某个位置的元素");
        System.out.println(list1.get(1));
        //System.out.println(list1.get(100));
        System.out.println("更新某个位置的元素");
        list1.set(0,11);
        list1.display();
        System.out.println("删除第一次出现的关键字key");
        list1.remove(33);
        list1.display();
        System.out.println("清空顺序表");
        list1.clear();
        list1.display();
        //结果为:
        //初始有效元素个数:
        //0
        //打印初始顺序表
        //
        //打印初始数组大小
        //0
        //打印插入元素后的顺序表
        //1 2 3 4 
        //打印插入元素后的顺序表大小
        //4
        //打印在指定位置插入元素后的顺序表
        //1 2 33 3 4 
        //顺序表是否包含某个元素
        //true
        //false
        //查找某个元素的指定位置
        //1
        //-1
        //获取某个位置的元素
        //2
        //更新某个位置的元素
        //11 2 33 3 4 
        //删除第一次出现的关键字key
        //11 2 3 4 
        //清空顺序表
    }
}

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底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表。

ArrayList的使用

ArrayList的构造

方法解释
ArrayList ()无参构造
ArrayList (Collection<? extends E> c)利用其他 Collection 构建 ArrayList
ArrayList (int initialCapacity)指定顺序表初始容量
public class Test {
    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中的元素一致
        ArrayList<Integer> list3 = new ArrayList<>(list2);

        //避免省略类型,否则:任意类型的元素都可以存放,使用时很麻烦
        List list4 = new ArrayList();
        list4.add(1);
        list4.add("hello");
    }
}

ArrayList的常见操作

方法解释
boolean add(E e)尾插 e
void add(int index, E element)将 e 插入到 index 位置
boolean addAll(Collection<? extends E> c)尾插 c 中的元素
E remove(int index)删除 index 位置元素
boolean remove(Object o)删除遇到的第一个 o
E get(int index)获取下标 index 位置元素
E set(int index, E element)将下标 index 位置元素设置为 element
void clear()清空
boolean contains(Object o)判断 o 是否在线性表中
int indexOf(Object o)返回第一个 o 所在下标
int lastIndexOf(Object o)返回最后一个 o 的下标
List< E > subList(int fromIndex, int toIndex)截取部分 list
public class Test {
    public static void main(String[] args) {
        List<String> list = new ArrayList<>();
        list.add("food");
        list.add("book");
        list.add("clothes");
        list.add("drink");
        System.out.println(list);   //[food, book, clothes, drink]

        //获取list中有效元素的个数
        System.out.println(list.size());    //4

        //获取和设置index位置上的元素,注意index必须介于[0,size)间
        System.out.println(list.get(1));    //book
        list.set(1,"BOOK");
        System.out.println(list.get(1));    //BOOK

        //在list的index位置插入指定元素,index及后续的元素统一往后搬移一个位置
        list.add(1,"shoes");
        System.out.println(list);   //[food, shoes, BOOK, clothes, drink]

        //删除指定元素,找到了就删除,该元素之后的元素统一往前搬移一个位置
        list.remove("shoes");
        System.out.println(list);   //[food, BOOK, clothes, drink]

        //删除list中的index位置上的元素,注意index不用超过list中有效元素个数,否则会抛出下标越界异常
        list.remove(list.size() - 1);   
        System.out.println(list);   //[food, BOOK, clothes]

        //检测list中是否包含指定元素,包含返回true,否则返回false
        if(!list.contains("drink")){
            list.add("drink");
        }
        System.out.println(list);   //[food, BOOK, clothes, drink]

        //查找指定元素第一次出现的位置:indexOf从前往后找,lastIndexOf从后往前找
        list.add("bag");
        System.out.println(list);   //[food, BOOK, clothes, drink, bag]
        System.out.println(list.indexOf("bag"));    //4
        System.out.println(list.lastIndexOf("bag"));    //4

        //使用list中[0,4)之间的元素构成一个新的subList返回,但是和ArrayList共用一个elementData数组,
        //也就的引用指向同一个空间,当你修改subList中的元素,List指向的空间中的元素自然也改变了。
        List<String> ret = list.subList(0,4);
        System.out.println(ret);    //[food, BOOK, clothes, drink]
        System.out.println(list);   //[food, BOOK, clothes, drink, bag]
        ret.set(0,"FOOD");
        System.out.println(list);   //[FOOD, BOOK, clothes, drink, bag]
        System.out.println(ret);    //[FOOD, BOOK, clothes, drink]

        list.clear();
        System.out.println(list.size());    //0

    }
}

ArrayList的遍历

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

public class Test {
    public static void main(String[] args) {
        List<Integer> list = new ArrayList<>();
        list.add(1);
        list.add(2);
        list.add(3);
        list.add(4);
        System.out.println(list);

        System.out.println("=== for循环遍历 ===");
        for (int i = 0; i < list.size(); i++) {
            System.out.print(list.get(i) + " ");
        }
        System.out.println();
        System.out.println("=== foreach遍历 ===");
        for (Integer x : list) {
            System.out.print(x + " ");
        }
        System.out.println();
        System.out.println("=== 使用迭代器Iterator输出 ===");
        Iterator<Integer> it1 = list.iterator();
        while(it1.hasNext()){
            System.out.print(it1.next() + " ");
        }
        System.out.println();
        System.out.println("=== 使用迭代器ListIterator输出 ===");
        ListIterator<Integer> it2 = list.listIterator();
        while(it2.hasNext()){
            System.out.print(it2.next() + " ");
        }
        System.out.println();
        System.out.println("=== 使用迭代器ListIterator输出 拓展 ===");
        ListIterator<Integer> it3 = list.listIterator(list.size());
        while(it3.hasPrevious()){
            System.out.print(it3.previous() + " ");
        }
    }
    //结果为:
    //[1, 2, 3, 4]
    //=== for循环遍历 ===
    //1 2 3 4 
    //=== foreach遍历 ===
    //1 2 3 4 
    //=== 使用迭代器Iterator输出 ===
    //1 2 3 4 
    //=== 使用迭代器ListIterator输出 ===
    //1 2 3 4 
    //=== 使用迭代器ListIterator输出 拓展 ===
    //4 3 2 1 
}

ArrayList的扩容机制

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

Object[] elementData; // 存放元素的空间
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {}; // 默认空间
private static final int DEFAULT_CAPACITY = 10; // 默认容量大小
public boolean add(E e) {
	ensureCapacityInternal(size + 1); // Increments modCount!!
	elementData[size++] = e;
	return true;
}
private void ensureCapacityInternal(int minCapacity) {
	ensureExplicitCapacity(calculateCapacity(elementData, minCapacity));
}
private static int calculateCapacity(Object[] elementData, int minCapacity) {
	if (elementData == DEFAULTCAPACITY_EMPTY_ELEMENTDATA) {
		return Math.max(DEFAULT_CAPACITY, minCapacity);
	}
	return minCapacity;
}
private void ensureExplicitCapacity(int minCapacity) {
	modCount++;
	// overflow-conscious code
	if (minCapacity - elementData.length > 0)
		grow(minCapacity);
}
private static final int MAX_ARRAY_SIZE = Integer.MAX_VALUE - 8;
private void grow(int minCapacity) {
	// 获取旧空间大小
	int oldCapacity = elementData.length;
	// 预计按照1.5倍方式扩容
	int newCapacity = oldCapacity + (oldCapacity >> 1);
	// 如果用户需要扩容大小 超过 原空间1.5倍,按照用户所需大小扩容
	if (newCapacity - minCapacity < 0)
		newCapacity = minCapacity;
	// 如果需要扩容大小超过MAX_ARRAY_SIZE,重新计算容量大小
	if (newCapacity - MAX_ARRAY_SIZE > 0)
		newCapacity = hugeCapacity(minCapacity);
	// 调用copyOf扩容
	elementData = Arrays.copyOf(elementData, newCapacity);
}
private static int hugeCapacity(int minCapacity) {
	// 如果minCapacity小于0,抛出OutOfMemoryError异常
	if (minCapacity < 0)
		throw new OutOfMemoryError();
	return (minCapacity > MAX_ARRAY_SIZE) ? Integer.MAX_VALUE : MAX_ARRAY_SIZE;
}

总结:

  1. 检测是否真正需要扩容,如果是调用grow准备扩容
  2. 预估需要容量的大小
    初步预估按照1.5倍大小扩容
    如果用户所需大小超过预估1.5倍大小,则按照用户所需大小扩容
    真正扩容之前检测是否能扩容成功,防止太大导致扩容失败
  3. 使用copyOf进行扩容

ArrayList的具体使用

杨辉三角

杨辉三角
给定一个非负整数 numRows,生成「杨辉三角」的前 numRows 行。
在「杨辉三角」中,每个数是它左上方和右上方的数的和。
在这里插入图片描述
示例 1:
输入: numRows = 5
输出: [[1],[1,1],[1,2,1],[1,3,3,1],[1,4,6,4,1]]
在这里插入图片描述
在这里插入图片描述

public class Test{
    //List<List<Integer>> 为二维数组
    public static List<List<Integer>> generate(int numRows) {
        List<List<Integer>> ret = new ArrayList<>();
        List<Integer> list0 = new ArrayList<>();
        list0.add(1);
        ret.add(list0);
        for (int i = 1; i < numRows; i++) {
            List<Integer> curRow = new ArrayList<>();
            //处理第一个元素
            curRow.add(1);
            //中间
            for (int j = 1; j < i; j++) {
                Integer data = ret.get(i-1).get(j-1) + ret.get(i-1).get(j);
                curRow.add(data);
            }
            //尾部
            curRow.add(1);
            ret.add(curRow);
        }
        return ret;
    }

    public static void main(String[] args) {
        List<List<Integer>> ret = generate(4);
        /*System.out.println(ret);*/
        for (int i = 0; i < ret.size(); i++) {
            for (int j = 0; j < ret.get(i).size(); j++) {
                System.out.print(ret.get(i).get(j) + " ");
            }
            System.out.println();
        }
        //结果为:
        //1 
        //1 1 
        //1 2 1 
        //1 3 3 1 
    }
}

简单的洗牌算法

要求:

  • 买52张牌
  • 洗牌
  • 3个人,每个人轮流拿五张
public class Card {
    public int rank;    //牌面值
    public String suit; //花色

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

    @Override
    public String toString() {
        return "{" + rank + suit + '}';
    }
}
public class CardDemo {
    public static final String[] suits = {"♦","♣","♥","♠"};

    public List<Card> buyCard(){
        List<Card> cardList = new ArrayList<>(52);
        for (int i = 1; i <= 13 ; i++) {
            for (int j = 0; j < 4; j++) {
                int rank = i;
                String suit = suits[j];
                cardList.add(new Card(rank,suit));
            }
        }
        return cardList;
    }

    public void shuffle(List<Card> cardlist){
        Random random = new Random();
        for (int i = cardlist.size() - 1; i > 0; i--) {
            int index = random.nextInt(i);
            swap(cardlist,i,index);
        }

    }

    private void swap(List<Card> cardList, int i, int j){
        /*Card tmp = cardList[i];
        cardList[i] = cardList[j];
        cardList[j] = tmp;*/
        Card tmp = cardList.get(i);
        cardList.set(i,cardList.get(j));
        cardList.set(j,tmp);
    }

    public List<List<Card>> play(List<Card> cardList){
        List<List<Card>> ret = new ArrayList<>();
        List<Card> hand0 = new ArrayList<>();
        List<Card> hand1 = new ArrayList<>();
        List<Card> hand2 = new ArrayList<>();
        ret.add(hand0);
        ret.add(hand1);
        ret.add(hand2);
        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 3; j++) {
                ret.get(j).add(cardList.remove(0));
            }
        }
        return ret;
    }
}
public class Test{
    public static void main(String[] args) {
        //买一副52张的牌
        CardDemo cards = new CardDemo();
        List<Card> cardList = cards.buyCard();
        System.out.println(cardList);
        //洗牌
        cards.shuffle(cardList);
        System.out.println(cardList);
        //3个人,每个人轮流拿五张
        List<List<Card>> players = cards.play(cardList);
        for (int i = 0; i < players.size(); i++) {
            System.out.println("第"+(i+1) +"个人的牌:" + players.get(i));
        }

        //剩下的牌:
        System.out.print("剩下的牌:");
        System.out.println(cardList);

    }
    //结果为:
    //[{1♦}, {1♣}, {1♥}, {1♠}, {2♦}, {2♣}, {2♥}, {2♠}, {3♦}, {3♣}, {3♥}, {3♠}, {4♦}, {4♣}, {4♥}, {4♠}, {5♦}, {5♣}, {5♥}, {5♠}, {6♦}, {6♣}, {6♥}, {6♠}, {7♦}, {7♣}, {7♥}, {7♠}, {8♦}, {8♣}, {8♥}, {8♠}, {9♦}, {9♣}, {9♥}, {9♠}, {10♦}, {10♣}, {10♥}, {10♠}, {11♦}, {11♣}, {11♥}, {11♠}, {12♦}, {12♣}, {12♥}, {12♠}, {13♦}, {13♣}, {13♥}, {13♠}]
    //[{4♠}, {9♥}, {5♣}, {1♦}, {12♣}, {13♥}, {3♦}, {8♣}, {4♦}, {5♠}, {2♠}, {5♦}, {10♥}, {13♦}, {12♥}, {10♦}, {7♥}, {10♠}, {7♣}, {11♦}, {9♦}, {5♥}, {1♠}, {8♠}, {11♥}, {13♣}, {4♥}, {12♦}, {3♥}, {6♠}, {8♦}, {6♥}, {3♠}, {13♠}, {6♦}, {1♥}, {1♣}, {2♦}, {4♣}, {10♣}, {7♠}, {3♣}, {2♣}, {7♦}, {9♠}, {6♣}, {9♣}, {2♥}, {8♥}, {12♠}, {11♣}, {11♠}]
    //第1个人的牌:[{4♠}, {1♦}, {3♦}, {5♠}, {10♥}]
    //第2个人的牌:[{9♥}, {12♣}, {8♣}, {2♠}, {13♦}]
    //第3个人的牌:[{5♣}, {13♥}, {4♦}, {5♦}, {12♥}]
    //剩下的牌:[{10♦}, {7♥}, {10♠}, {7♣}, {11♦}, {9♦}, {5♥}, {1♠}, {8♠}, {11♥}, {13♣}, {4♥}, {12♦}, {3♥}, {6♠}, {8♦}, {6♥}, {3♠}, {13♠}, {6♦}, {1♥}, {1♣}, {2♦}, {4♣}, {10♣}, {7♠}, {3♣}, {2♣}, {7♦}, {9♠}, {6♣}, {9♣}, {2♥}, {8♥}, {12♠}, {11♣}, {11♠}]
}

ArrayList的问题及思考

  1. ArrayList底层使用连续的空间,任意位置插入或者删除元素时,需要将该位置后序元素整体往前或者往后搬移,故时间复杂度为O(N)
  2. 增容需要申请新空间,拷贝数据,释放旧空间,会有不小的消耗。
  3. 增容一般是呈1.5倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到150,我们只想继续插入5个数据,后面没有数据插入了,那么就浪费了45个数据空间。

关于ArrayList我们先了解和学习到这,希望这篇文章能帮助到你,谢谢你的阅读。

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

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

相关文章

一个安卓鸿蒙化工具

DevEco插件&#xff0c;为已有安卓项目鸿蒙化加速。 目前支持&#xff1a; 1、安卓Vector Assets转svg&#xff1b; 2、json转ets model&#xff1b; 3、kotlin model转ets model&#xff1b; 下载地址&#xff1a;andtoharplugin1.1.0 安装&#xff1a; deveco插件安装选硬…

2024年华为杯研赛(E题)|高速公路应急车道启用建模|数学建模竞赛解题思路|完整代码论文集合

我是Tina表姐&#xff0c;毕业于中国人民大学&#xff0c;对数学建模的热爱让我在这一领域深耕多年。我的建模思路已经帮助了百余位学习者和参赛者在数学建模的道路上取得了显著的进步和成就。现在&#xff0c;我将这份宝贵的经验和知识凝练成一份全面的解题思路与代码论文集合…

智慧医院人工智能应用场景 | 智能导诊系统源码

近年来&#xff0c;智能医疗在国内外的发展热度不断提升。图像识别、深度学习、神经网络、大模型、语音等关键技术的突破带来了人工智能技术新一轮的发展。 场景一&#xff1a;智能机器人 医疗机器人是指能够在医疗领域执行特定任务或功能的机器人&#xff0c;包括手术机器人、…

从零到一:打造安全高效敦煌测评自养号体系

敦煌测评自养号是一种提升店铺销售和排名的有效策略&#xff0c;卖家可以自行注册并管理买家账号&#xff0c;通过模拟真实买家行为为自家店铺进行测评和补单。以下是一些关键技巧&#xff0c;帮助卖家快速提升销售和排名&#xff1a; 一、账号注册与养号 环境搭建&#xff1…

js 将二进制文件流,下载为excel文件

吃西瓜 现成的粒子 二进制流&#xff0c;是一种计算机文件格式&#xff0c;它的数据以二进制形式存储&#xff0c;与文本文件不同&#xff0c; 二进制文件可以包含任意类型的数据&#xff0c;例如&#xff1a;图像、音频、视频、可执行文件、压缩文件等&#xff0c;而文本文…

基于协同过滤+python+django+vue的音乐推荐系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI、Python、小程序等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 【2025最新】基于协同过滤pythondjangovue…

linux cat命令的实现

cat 是 Linux 和其他 Unix-like 系统中的一个常用命令&#xff0c;它的名称来源于 "concatenate"&#xff08;连接&#xff09;的缩写。cat 命令主要用于查看、创建和拼接文件。它读取一个或多个文件的内容&#xff0c;并将它们显示在标准输出&#xff08;通常是终端…

DELPHI编译软件时带上当前IDE的版本号

如果通过 CompilerVersion 得到的也只是编译器的版本号。 比如&#xff1a;delphi XE12 是 36 &#xff0c;也仅此而己。 我想得到的是IDE的版本号&#xff0c;比如当前最新版本的DELPHI是&#xff1a;Embarcadero RAD Studio 12 Version 29.0.53571.9782 我想得到 29.0.53…

实验:WLAN无线综合实验

无线综合实验的概述&#xff1a; WLAN无线综合实验是一种针对无线网络技术的综合性实验&#xff0c;旨在通过实践操作加深对无线局域网&#xff08;WLAN&#xff09;技术的理解和应用能力。以下是对该实验的详细概述&#xff1a; 实验目的 掌握认证AP上线的配置方法&#xff…

VisionPro - 基础 - 00 模板匹配技术和在VP中的使用 - PMAlign - PatMax - (4)- 控制模板的匹配

前言&#xff1a; 针对PatMax 的高级应用和原理&#xff0c;在这一节继续进行说明&#xff1a;这一节主要考虑的是PatMax模板匹配的原理&#xff1a;如何控制模板的匹配。 本节先介绍了几个模板匹配的衡量标准&#xff0c;比如模板匹配分数&#xff0c;和模板的几种模板匹配的…

计算机毕业设计 基于Python的美术馆预约系统的设计与实现 Python+Django+Vue 前后端分离 附源码 讲解 文档

&#x1f34a;作者&#xff1a;计算机编程-吉哥 &#x1f34a;简介&#xff1a;专业从事JavaWeb程序开发&#xff0c;微信小程序开发&#xff0c;定制化项目、 源码、代码讲解、文档撰写、ppt制作。做自己喜欢的事&#xff0c;生活就是快乐的。 &#x1f34a;心愿&#xff1a;点…

pytorch实现RNN网络

目录 1.导包 2. 加载本地文本数据 3.构建循环神经网络层 4.初始化隐藏状态state 5.创建随机的数据&#xff0c;检测一下代码是否能正常运行 6. 构建一个完整的循环神经网络 7.模型训练 8.个人知识点理解 1.导包 import torch from torch import nn from torch.nn imp…

如何解决DataGrip的 Public Key Retrieval is not allowed错误

对于 DataGrip 出现 [08001] Public Key Retrieval is not allowed 错误&#xff0c;原因通常是 MySQL 的安全机制不允许客户端检索公钥。你可以通过以下步骤来解决这个问题&#xff1a; 解决步骤&#xff1a; 修改 DataGrip 中的连接设置&#xff1a; 打开 DataGrip。在左侧导…

CLion/Git版本控制

文章目录 文章介绍准备工具操作首次提交修改代码提交第二版 文章介绍 记录用clion和git做代码的版本控制 准备工具 CLion2024.2.0.1 git 操作 首次提交 该文件夹的打开方式选择clion 全部提交 成功提交后查看分支 修改代码提交第二版

hutool 解压缩读取源文件和压缩文件大小失败导致报错

前言 最近处理老项目中的问题&#xff0c;升级安全jar&#xff0c;发现hutool的jar在解压缩的时候报错了&#xff0c;实际上是很简单的防御zip炸弹攻击的手段&#xff0c;但是却因为hutool的工具包取文件大小有bug&#xff0c;造成了解压缩不能用&#xff0c;报错&#xff1a;…

山东潍坊戴尔存储服务器维修 md3800f raid恢复

山东戴尔存储故障维修 存储型号&#xff1a;DELL PowerVault md3800f 故障问题&#xff1a;存储除尘后通电开机&#xff0c;发现有物理硬盘没有插到位&#xff0c;用户带电拔插了多块物理盘&#xff0c;导致关连的磁盘阵列掉线&#xff0c;卷失败&#xff1b; 处理方式&#xf…

Python基于Django、大数据的北极星招聘数据可视化系统

博主介绍&#xff1a;✌程序员徐师兄、7年大厂程序员经历。全网粉丝12w、csdn博客专家、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战✌ &#x1f345;文末获取源码联系&#x1f345; &#x1f447;&#x1f3fb; 精彩专栏推荐订阅&#x1f447;…

[JavaEE] TCP协议

目录 一、TCP协议段格式 二、TCP确保传输可靠的机制 2.1 确认应答 2.2 超时重传 2.3 连接管理 2.3.1 三次握手 2.3.2 四次挥手 2.4 滑动窗口 2.4.1 基础知识 2.4.2 两种丢包情况 2.4.2.1 数据报已经抵达&#xff0c;ACK丢包 2.4.2.2 数据包丢包 2.5 流量控制…

国标GB28181视频融合监控汇聚平台的方案实现及场景应用

Liveweb国标视频融合云平台基于端-边-云一体化架构&#xff0c;部署轻量简单、功能灵活多样&#xff0c;平台可支持多协议&#xff08;GB28181/RTSP/Onvif/海康SDK/Ehome/大华SDK/RTMP推流等&#xff09;、多类型设备接入(IPC/NVR/监控平台)&#xff0c;在视频能力上&#xff0…

图解 | 消息认证码(MAC)到底解决了什么问题?还有什么问题是它解决不了的?

消息认证码&#xff08;Message Authentication Code&#xff0c;MAC&#xff09;是一种用于验证数据完整性和来源可信性&#xff08;对消息进行认证&#xff09;的技术。它通常由一个密钥和被保护的消息通过特定算法计算得出&#xff0c;接收方可以使用相同的密钥&#xff08;…