数据结构之数组和列表

目录

1、基于数组列表的元素添加

2、基于数组列表的元素查找

3、基于数组列表的元素修改

4、基于数组列表的元素包含判断

5、基于数组列表的元素删除

6、基于数组列表的动态扩容


数组是用于储存多个相同类型数据的集合,可以通过索引来访问数组中具体的元素。但是因为数组在一创建时就需要明确数组的大小(开辟一块连续的空间),所以数组在程序中使用起来就具有局限性。

数组就是把数据码成一排进行存放。 

数组最大的优势就是可以快速查询,在数组中,每个数据存放位置的索引是没有语义的(仅表示位置,没有业务逻辑语义,比如不适合表示学号,身份证号等),因此,往往在实际开发中,不直接使用数组,而是使用基于数组结构封装的列表容器,比如 Java 中的 ArrayList 。

1、基于数组列表的元素添加

Java 中 ArrayList 类的添加方法实现

在 Java 中,如果向下标大于列表中实际数据量的位置添加数据,会抛出 IndexOutOfBoundsException 异常

 (1)向数组的末尾添加元素,只需要维护数组实际容量指针就可以了,不需要移动数据,效率很高

向数组的末尾添加元素

(2)向数组的指定位置添加元素,假如向队首添加元素的话,将需要移动整个数组的数据

向数组的指定位置添加元素

2、基于数组列表的元素查找

Java 中 ArrayList 类的查找方法实现,直接使用索引下标,在Java中,如果列表索引位置不存在数据,会抛出 IndexOutOfBoundsException 异常

使用下标的数据访问,查找效率很快

3、基于数组列表的元素修改

Java 中 ArrayList 类的修改方法实现,也是通过下标,一步到位

通过下标修改数据,直接定位到下标所在位置,进行元素替换,如 arr.set(4,68),在Java中,如果列表索引位置不存在数据,会抛出 IndexOutOfBoundsException 异常

4、基于数组列表的元素包含判断

Java 中 ArrayList 类的包含方法实现,需要遍历整个列表

例如,查找列表中是否包含 68 的元素,会遍历整个列表,查找是否有等于 68 的元素

5、基于数组列表的元素删除

Java 中 ArrayList 类的删除方法实现,可以根据下标删除,也可以根据对象删除,根据对象删除需要遍历整个列表,如果不是删除队尾数据,还需要移动列表中其他元素

例如,删除下标为 2 的元素,下标2后边的元素都会向前移动,保持数组的连续性,删除的数据下标越小,移动的数据越多,性能越差

删除操作,是增加的反向操作,把需要删除元素后边的元素向前移动,同时维护 size-- ;移动后,最后一个元素它还存在(置灰的 17),但是没有关系,因为维护了 size ,此时 size 指示位置的元素永远也访问不到。

6、基于数组列表的动态扩容

静态数组转变为可动态扩容的动态数组,主要是为了解决使用静态数组容量固定不灵活问题

实际业务场景中,往往无法精确预估需要存储的数据量,如果数组容量开的太大,会浪费存储空间,如果容量开得太小,又会导致数组的容量不够用。因此,使用数组封装的数据结构,都需要考虑合适的动态扩容问题。

Java 中 ArrayList 类的容量扩容

ArrayList 类每次进行 add() 操作时,都会对数组容量进行一次判断,如果元素个数+1 <= Capacity,那就可以放心添加元素

元素个数+1 > Capacity(总容量) ,那 ArrayList 就发生扩容,容量 Capacity 变为 1.5 倍Capacity。核心代码:newCapacity = oldCapacity + (oldCapacity >> 1);对 oldCapacity 进行位运算,左移一位,所以 oldCapacity >>1 其实就是 oldCapacity / 2 ,所以 oldCapacity + (oldCapacity >> 1) 就等于1.5 oldCapacity);

Java 中 ArrayList 类的容量缩容

在 Java 中并没有提供自动缩容方式,可以手动调用 trimToSize() 方法,这个方法会将 ArrayList 内置数组缩容到当前的 size 大小

列表的动态扩容机制如下图,扩容需要复制之前数组中的所有元素,尽量避免频繁扩容

缩容和扩容带来的时间复杂度震荡

问题出现,假如,当数组需要扩容时,扩充新数组为原来容量的2倍,另外,又指定存储的数组元素为现有数组容量的1/2时,触发缩容,数组容量缩减为原来容量的1/2。// 缩放比一样

这种情况下,如果程序恰好在数组扩容的这个位置上进行元素的增删操作,就意味着每次操作都会伴随着数组的扩容和缩容,此时方法的时间复杂就会提升为O(n^2)。这种现象称为时间复杂度的震荡。

解决方案:采用不相同的缩放比率,如果当数组的容量为1/4时,才将数组的容量缩减为原来的一半,正因为如此,即便是缩容后再添加元素也不需要立即扩充数组的容量。

避免缩容过于着急,把缩容的条件由1/2变为1/4,采用更加懒惰的方案。

附:自定义封装数组的列表实现

public class Array<E> { // 使用泛型
    // 基于java的数组进行二次封装,private类型不允许外部修改
    private E[] data;
    // 维护一个size,指定了数组中存放了多少元素
    private int size;

    // 构造函数,传入数组的容量capacity构造Array
    public Array(int capacity) {
        data = (E[]) new Object[capacity];
        size = 0;
    }

    // 无参数的构造函数,默认数组的容量capacity=10
    public Array() {
        this(10);
    }

    // 获取数组的容量
    public int getCapacity() {
        return data.length;
    }

    // 获取数组中的元素个数
    public int getSize() {
        return size;
    }

    // 返回数组是否为空
    public boolean isEmpty() {
        return size == 0;
    }

    // 在index索引的位置插入一个新元素e
    public void add(int index, E e) {
        if (index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");

        if (size == data.length)
            resize(2 * data.length); // 扩容

        for (int i = size - 1; i >= index; i--)
            data[i + 1] = data[i]; // 移动元素

        data[index] = e;
        size++;
    }

    // 向所有元素后添加一个新元素
    public void addLast(E e) {
        add(size, e);
    }

    // 在所有元素前添加一个新元素
    public void addFirst(E e) {
        add(0, e);
    }

    // 获取index索引位置的元素
    public E get(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Get failed. Index is illegal.");
        return data[index];
    }

    // 修改index索引位置的元素为e
    public void set(int index, E e) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Index is illegal.");
        data[index] = e;
    }

    // 查找数组中是否有元素e
    public boolean contains(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e))
                return true;
        }
        return false;
    }

    // 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
    public int find(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e))
                return i;
        }
        return -1;
    }

    // 从数组中删除index位置的元素, 返回删除的元素
    public E remove(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal.");

        E ret = data[index];
        for (int i = index + 1; i < size; i++)
            data[i - 1] = data[i];
        size--;
        data[size] = null; // loitering objects != memory leak
        // 当实际存储元素只有数组容量的1/4时,触发缩容,且缩容的数量量不能为 0
        if (size == data.length / 4 && data.length / 2 != 0)
            resize(data.length / 2); // 缩容
        return ret;
    }

    // 从数组中删除第一个元素, 返回删除的元素
    public E removeFirst() {
        return remove(0);
    }

    // 从数组中删除最后一个元素, 返回删除的元素
    public E removeLast() {
        return remove(size - 1);
    }

    // 从数组中删除元素e
    public void removeElement(E e) {
        int index = find(e);
        if (index != -1)
            remove(index);
    }

    @Override
    public String toString() {
        StringBuilder res = new StringBuilder();
        res.append(String.format("Array: size = %d , capacity = %d\n", size, data.length));
        res.append('[');
        for (int i = 0; i < size; i++) {
            res.append(data[i]);
            if (i != size - 1)
                res.append(", ");
        }
        res.append(']');
        return res.toString();
    }

    // 将数组空间的容量变成 newCapacity 大小
    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity];
        for (int i = 0; i < size; i++)
            newData[i] = data[i];
        data = newData;
    }
}

测试自定义封装数组的列表

public class Main {
    public static void main(String[] args) {
        Array<Integer> arr = new Array<>();
        for (int i = 0; i < 10; i++)
            arr.addLast(i); // 向列表添加元素
        System.out.println("初始数组:" + arr);

        arr.add(1, 100); // 再次向列表添加元素,触发扩容
        System.out.println("触发扩容:" + arr);

        arr.addFirst(-1); // 向队首添加元素
        System.out.println(arr);

        arr.remove(2); // 移除指定位置元素
        System.out.println(arr);

        arr.removeElement(4); // 移除元素
        System.out.println(arr);

        arr.removeFirst(); // 移除队首元素
        System.out.println(arr);

        for (int i = 0; i < 4; i++) {
            arr.removeFirst(); // 4次移除元素,达到缩容条件
            System.out.println(arr);
        }
    }
}

至此,数据结构之数组和列表介绍完毕。

  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

swadian2008

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值