【数据结构】ArrayList与顺序表

一、线性表

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

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

二、顺序表

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

1、接口的实现

MyArrayList 实现:

import java.util.Arrays;

public class MyArrayList {
    public int[] elem;
    public int usedSize;
    public static final int DEFAULT_SIZE = 4;

    public MyArrayList() {
        this.elem = new int[DEFAULT_SIZE];
    }

    // 打印顺序表
    public void display() {
        for (int i = 0; i < this.usedSize; i++) {
            System.out.print(elem[i] + " ");
        }
        System.out.println();
    }

    // 新增元素,默认在数组最后新增
    public void add(int data) {
        if (isFull()) {
            this.elem = Arrays.copyOf(this.elem, 2*this.elem.length);
        }

        this.elem[this.usedSize] = data;
        this.usedSize++;
    }

    public boolean isFull() {
        return this.usedSize == this.elem.length;
    }

    private boolean checkPosInAdd(int pos){
        if (pos < 0 || pos > this.usedSize) {
            throw new MyArrayListIndexOutOfException("pos位置不合法!");
        }
        return true;//合法
    }

    // 在 pos 位置新增元素
    public void add(int pos, int data) {
        if (!checkPosInAdd(pos)) {
            return;
        }

        if (isFull()) {
            this.elem = Arrays.copyOf(this.elem, 2*this.elem.length);
        }

        int i = this.usedSize-1;
        while (i >= pos) {
            this.elem[i+1] = this.elem[i];
            i--;
        }
        this.elem[pos] = data;
        this.usedSize++;
    }

    // 判定是否包含某个元素
    public boolean contains(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind) {
                return true;
            }
        }
        return false;
    }

    // 查找某个元素对应的位置
    public int indexOf(int toFind) {
        for (int i = 0; i < this.usedSize; i++) {
            if (this.elem[i] == toFind) {
                return i;
            }
        }
        return -1;
    }

    private boolean checkPosInGet(int pos){
        if (pos < 0 || pos >= this.usedSize) {
            return false;
        }
        return true;//合法
    }

    // 获取 pos 位置的元素
    public int get(int pos) {
        if (!checkPosInGet(pos)) {
            throw new MyArrayListIndexOutOfException("pos位置不合法!");
        }

        return this.elem[pos];
    }

    // 给 pos 位置的元素设为 value
    public void set(int pos, int value) {
        if (!checkPosInGet(pos)) {
            throw new MyArrayListIndexOutOfException("pos位置不合法!");
        }
        this.elem[pos] = value;
    }

    //删除第一次出现的关键字key
    public void remove(int toRemove) {
        if (this.elem == null) {
            System.out.println("顺序表为空!");
            return;
        }
        int index = indexOf(toRemove);
        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--;
    }

    // 获取顺序表长度
    public int size() {
        return this.elem.length;
    }

    // 清空顺序表
    public void clear() {
        // 如果是引用类型,需要一个个置空
//        for (int i = 0; i < this.usedSize; i++) {
//            this.elem[i] = null;
//        }
        this.usedSize = 0;
    }
}

MyArrayListIndexOutOfException 异常实现:

public class MyArrayListIndexOutOfException extends RuntimeException{
    public MyArrayListIndexOutOfException() {
    }

    public MyArrayListIndexOutOfException(String message) {
        super(message);
    }
}

三、ArrayList简介

在集合框架中,ArrayList是一个普通的类,实现了List接口

【说明】

  1. ArrayList实现了RandomAccess接口,表明ArrayList支持随机访问
  2. ArrayList实现了Cloneable接口,表明ArrayList是可以clone的
  3. ArrayList实现了Serializable接口,表明ArrayList是支持序列化的
  4. 和Vector不同,ArrayList不是线程安全的,在单线程下可以使用,在多线程中可以选择Vector或者CopyOnWriteArrayList
  5. ArrayList底层是一段连续的空间,并且可以动态扩容,是一个动态类型的顺序表

四、ArrayList使用

1、ArrayList的构造

方法解释
ArrayList()无参构造
ArrayList(Collection<? extends E> c)利用其他 Collection 构建 ArrayList
ArrayList(int initialCapacity)指定顺序表初始容量
public static void main(String[] args) {
    // ArrayList无参构造,构造一个空的列表
    ArrayList<Integer> arrayList1 = new ArrayList<>();
    // ArrayList无参构造构造的是一个空的列表,只有调用add()函数才会扩容十个空间
    arrayList1.add(1);
    arrayList1.add(2);
    arrayList1.add(3);
    System.out.println(arrayList1);

    // arrayList2构造好之后,与arrayList1中的元素一致
    ArrayList<Integer> arrayList2 = new ArrayList<>(arrayList1);
    System.out.println(arrayList2);

    // 构造一个具有10个容量的列表
    ArrayList<Integer> arrayList3 = new ArrayList<>(10);
}

2、ArrayList常见操作

(1)boolean add(E e) :尾插 e

ArrayList<Integer> arrayList1 = new ArrayList<>();
arrayList1.add(1);
arrayList1.add(2);
arrayList1.add(3);
arrayList1.add(4);
System.out.println(arrayList);

运行结果:

[1, 2, 3, 4]

(2)void add(int index, E element) :将 e 插入到 index 位置

public static void main(String[] args) {
    ArrayList<Integer> arrayList = new ArrayList<>();
    arrayList.add(1);
    arrayList.add(2);
    arrayList.add(3);
    arrayList.add(4);
    System.out.println(arrayList);
    arrayList.add(2, 99);
    System.out.println(arrayList);
}        

运行结果:

[1, 2, 99, 3, 4]

(3)boolean addAll(Collection<? extends E> c) :尾插 c 中的元素

public static void main(String[] args) {
    ArrayList<Integer> arrayList1 = new ArrayList<>();
    arrayList1.add(1);
    arrayList1.add(2);
    arrayList1.add(3);
    arrayList1.add(4);
    System.out.println("arrayList1:" + arrayList1);

    ArrayList<Integer> arrayList2 = new ArrayList<>();
    arrayList2.add(10);
    arrayList2.add(20);
    arrayList2.add(30);
    arrayList2.add(40);
    System.out.println("addAll之前的arrayList2:" + arrayList2);
    arrayList2.addAll(arrayList1);
    System.out.println("addAll之后的arrayList2:" + arrayList2);
}

运行结果:

arrayList1:[1, 2, 3, 4]
addAll之前的arrayList2:[10, 20, 30, 40]
addAll之后的arrayList2:[10, 20, 30, 40, 1, 2, 3, 4]

(4)E remove(int index) :删除 index 位置元素

(5)boolean remove(Object o) :删除遇到的第一个 o

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

    arrayList1.remove(0);
    System.out.println(arrayList);

    arrayList1.remove(new Integer(99));
    System.out.println(arrayList);
}        

运行结果:

[1, 2, 99, 3, 99, 99, 4, 99]
[2, 99, 3, 99, 99, 4, 99]
[2, 3, 99, 99, 4, 99]

(6)E get(int index) :获取下标 index 位置元素

public static void main(String[] args) {
    ArrayList<Integer> arrayList = new ArrayList<>();
    arrayList.add(1);
    arrayList.add(2);
    arrayList.add(3);
    arrayList.add(4);
    System.out.println(arrayList.get(2));
    System.out.println(arrayList.get(3));
}

运行结果:

3
4

(7)E set(int index, E element) :将下标 index 位置元素设置为 element

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

    arrayList.set(0,99);
    System.out.println(arrayList);
}

运行结果:

[1, 2, 3, 4]
[99, 2, 3, 4]

(8)void clear() :清空

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

    arrayList.clear();
    System.out.println(arrayList);
}

运行结果:

[1, 2, 3, 4]
[]

(9)boolean contains(Object o) :判断 o 是否在线性表中,o 在线性表中返回 true,不在则返回 false

public static void main(String[] args) {
    ArrayList<String> arrayList = new ArrayList<>();
    arrayList.add("hello");
    arrayList.add("world");
    arrayList.add("this");
    arrayList.add("is");
    arrayList.add("frost");

    System.out.println(arrayList.contains(new String("hello")));
    System.out.println(arrayList.contains(new Integer(99)));
    System.out.println(arrayList.contains("rost"));
}

运行结果:

true
false
false

(10)int indexOf(Object o) :返回第一个 o 所在下标


运行结果:


(11)int lastIndexOf(Object o) :返回最后一个 o 的下标

public static void main(String[] args) {
    ArrayList<Integer> arrayList = new ArrayList<>();
    arrayList.add(1);
    arrayList.add(2);
    arrayList.add(3);
    arrayList.add(4);

    int index = arrayList.indexOf(new Integer(3));
    System.out.println(index);
    System.out.println(arrayList.indexOf(1));
}

运行结果:

2
0

(12)List<E> subList(int fromIndex, int toIndex) :截取部分 list,截取的区间为前后开,如下截取下标在 [1,3) 区间内的部分。

public static void main(String[] args) {
    ArrayList<Integer> arrayList = new ArrayList<>();
    arrayList.add(1);
    arrayList.add(2);
    arrayList.add(99);
    arrayList.add(3);
    arrayList.add(4);
    System.out.println("arrayList:" + arrayList);

    List<Integer> list = arrayList.subList(1,3); // [1, 3)
    System.out.println("list:" + list);

    System.out.println("============================");
    list.set(0,100);

    System.out.println("list.set(0,100):" + list);
    System.out.println("此时arrayList" + arrayList);
}

运行结果:

arrayList:[1, 2, 99, 3, 4]
list:[2, 99]
============================
list.set(0,100):[100, 99]
此时arrayList[1, 100, 99, 3, 4]

注:截取部分 list,若对 list 进行修改,则 arrayList 也会随之改变

3、ArrayList的遍历

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

    // for循环+下标
    System.out.println("========for循环+下标=======");
    for (int i = 0; i < arrayList.size(); i++) {
        System.out.print(arrayList.get(i) + " ");
    }
    System.out.println();

    // for-each
    System.out.println("========for-each=======");
    for (Integer x : arrayList) {
        System.out.print(x + " ");
    }
    System.out.println();

    // 使用迭代器
    System.out.println("========Iterator迭代器=======");
    Iterator<Integer> it = arrayList.iterator();
    while (it.hasNext()) {
        System.out.print(it.next() + " ");
    }
    System.out.println();

    // 使用迭代器
    System.out.println("========listIterator()迭代器=======");
    Iterator<Integer> it2 = arrayList.listIterator();
    while (it2.hasNext()) {
        System.out.print(it2.next() + " ");
    }
    System.out.println();
}

运行结果:

[1, 2, 3, 4, 5]
========for循环+下标=======
1 2 3 4 5 
========for-each=======
1 2 3 4 5 
========Iterator迭代器=======
1 2 3 4 5 
========listIterator()迭代器=======
1 2 3 4 5 

4、ArrayList的扩容机制

下面代码有缺陷吗?为什么?

public static void main(String[] args) {
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < 100; i++) {
        list.add(i);
    }
}

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进行扩容

五、使用示例

1、扑克牌

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

class Card{
    private String suit; // 花色
    private int rank; // 数值

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

    public String getSuit() {
        return suit;
    }

    public void setSuit(String suit) {
        this.suit = suit;
    }

    public int getRank() {
        return rank;
    }

    public void setRank(int rank) {
        this.rank = rank;
    }

    @Override
    public String toString() {
        return "[ " + suit + " " + rank + " ]";
    }
}

public class TestCard {
    public static final String[] suits = {"♥", "♠", "♣", "♦"};

    public static List<Card> buyCard() {
        List<Card> desk = new ArrayList<>();

        for (int i = 0; i < 4; i++) {
            for (int j = 1; j <= 13; j++) {
                Card card = new Card(suits[i], j);
                desk.add(card);
            }
        }
        return desk;
    }

    public static 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);
        }
    }

    public static void swap(List<Card> cardList, int i, int j) {
        Card temp = cardList.get(i);
        cardList.set(i, cardList.get(j));
        cardList.set(j,temp);
    }

    public static void main(String[] args) {
        List<Card> cardList = buyCard();
        System.out.println("洗牌前:" + cardList);
        shuffle(cardList);
        System.out.println("洗牌后:" + cardList);

        // 三个人,每个人轮流抓 5 张牌
        List<List<Card>> list = new ArrayList<>();
        List<Card> hand1 = new ArrayList<>();
        List<Card> hand2 = new ArrayList<>();
        List<Card> hand3 = new ArrayList<>();
        list.add(hand1);
        list.add(hand2);
        list.add(hand3);

        for (int i = 0; i < 5; i++) {
            for (int j = 0; j < 3; j++) {
                Card card = cardList.remove(0);

                List<Card> hand = list.get(j);
                hand.add(i, card);
                //list.get(j).add(i, card);
            }
        }
        System.out.println("hand1:" + hand1);
        System.out.println("hand2:" + hand2);
        System.out.println("hand3:" + hand3);
        System.out.println("剩余的牌:" + cardList);
    }
}

运行结果:

洗牌前:[[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 ]]
洗牌后:[[1 ], [11 ], [6 ], [5 ], [7 ], [8 ], [6 ], [13 ], [3 ], [7 ], [5 ], [6 ], [12 ], [2 ], [1 ], [8 ], [12 ], [10 ], [8 ], [9 ], [9 ], [10 ], [10 ], [2 ], [13 ], [5 ], [3 ], [7 ], [9 ], [11 ], [1 ], [5 ], [3 ], [4 ], [12 ], [6 ], [4 ], [8 ], [4 ], [7 ], [2 ], [1 ], [11 ], [3 ], [12 ], [11 ], [13 ], [4 ], [2 ], [9 ], [10 ], [13 ]]
hand1:[[1 ], [5 ], [6 ], [7 ], [12 ]]
hand2:[[11 ], [7 ], [13 ], [5 ], [2 ]]
hand3:[[6 ], [8 ], [3 ], [6 ], [1 ]]
剩余的牌:[[8 ], [12 ], [10 ], [8 ], [9 ], [9 ], [10 ], [10 ], [2 ], [13 ], [5 ], [3 ], [7 ], [9 ], [11 ], [1 ], [5 ], [3 ], [4 ], [12 ], [6 ], [4 ], [8 ], [4 ], [7 ], [2 ], [1 ], [11 ], [3 ], [12 ], [11 ], [13 ], [4 ], [2 ], [9 ], [10 ], [13 ]]

注:洗牌后每次运行结果都不一样,所有发牌后每个人拿的牌也不一样,剩余的牌也不一样。

2、杨辉三角

杨辉三角

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<>();
            curRow.add(1);

            for (int j = 1; j < i; j++) {
                List<Integer> tmp = ret.get(i-1);
                int x = tmp.get(j-1) + tmp.get(j);
                curRow.add(x);
            }

            curRow.add(1);
            ret.add(curRow);
        }
        return ret;
    }
}

六、顺序表的问题及思考

  1. 顺序表中间/头部的插入删除,时间复杂度为O(N)
  2. 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  3. 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。
  • 14
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值