实现动态数组(Java)

1,154 阅读5分钟

一、数组介绍

数组是一种线性表,用连续的内存空间存储类型相同的数据元素。

线性表,就是数据结构内存储的数据元素只有前和后两个方向的数据结构。

在这里插入图片描述

因为数组用连续的内存空间存储类型相同的数据,所以才拥有它最突出的特性 —— 它支持利用下标进行随机访问。

二、改变数组大小

如何实现动态扩容?

当数组容量不足时,创建一个容量更大的数组,将数组元素搬运过去。这个新的数组作为存储数据的数组。

翻译为代码为:

  /**
     * 改变数组大小
     */
    private void resize(int capacity) {
        this.capacity = capacity;
        T[] newData = (T[]) new Object[capacity];
        // 移动数组
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        // data 指向新创建的数组
        data = newData;
    }

由于需要搬运整个数组,时间复杂度为 O(n)

三、插入元素

在数组中插入元素,需要搬运数据,如图所示

翻译为代码为:

 /**
     * 在指定位置插入新元素
     *
     * @param index
     * @param e
     */
    public void add(int index, T e) {
        checkIndex(index);
        // 判断数组是否已满,数组已满需要对数组进行扩容
        if (size == capacity) {
            resize(2 * this.capacity);
        }
        if (index < size) {
            // 此时 index 已有元素, 数组 index 位置及以后的元素全部向后移,空出 index 的位置
            for (int i = size; i > index; i--) {
                data[i] = data[i - 1];
            }
        }
        data[index] = e;
        size++;
    }

让我们分析一下插入操作的时间复杂度:

  1. 当我们在数组末尾插入时,不需要搬运数据,最好时间复杂度为 O(1)
  2. 当我们在数组头部插入时,需要搬运所有的数据,最坏时间复杂度为 O(n)
  3. 删除的位置 index 有 n 种情况,只需要将数组中的每种情况需要搬运的数据相加就好。平均时间复杂度为(1 + 2 + ..n) / n = O(n)

四、删除元素

为了保证内存的连续性,数组的删除操作需要将被删除元素后面的所有元素向前移。

如图所示:

翻译为代码为:

 /**
     * 删除下标为 index 的数组元素
     *
     * @param index
     */
    public T delIndex(int index) {
        checkIndex(index);
        T res = data[index];
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size--;
        // 如果数组内实际存储的元素个数为数组容量的 1 / 4,则让数组容量减少为原来的一半
        if (size == (capacity / 4)) {
            resize(capacity / 2);
        }
        return res;
    }

让我们来分析一下删除操作的时间复杂度:

  1. 删除数组末尾数据元素时,不需要搬运数据,最好时间复杂度为 O(1)
  2. 删除数组第一个数据元素,需要搬运所有的数据,最坏时间复杂度为 O(n)
  3. 删除的位置 index 有 n 种情况,只需要将数组中的每种情况需要搬运的数据相加就好。平均时间复杂度为(1 + 2 + ..n) / n = O(n)

五、完整代码

/**
 * 动态数组
 *
 * @author liyanan
 * @date 2020/1/7 14:25
 */
public class GenericArray<T> {
    /**
     * 用于存储数据
     */
    public T[] data;
    /**
     * 数组内元素个数
     */
    public int size;
    /**
     * 数组的真实容量
     */
    public int capacity;

    /**
     * 初始容量默认为 10
     */
    public GenericArray() {
        this(10);
    }

    /**
     * 给定数组初始容量
     *
     * @param capacity
     */
    public GenericArray(int capacity) {
        this.capacity = capacity;
        data = (T[]) new Object[capacity];
        size = 0;
    }

    /**
     * 在指定位置插入新元素
     *
     * @param index
     * @param e
     */
    public void add(int index, T e) {
        checkIndex(index);
        // 判断数组是否已满,数组已满需要对数组进行扩容
        if (size == capacity) {
            resize(2 * this.capacity);
        }
        if (index < size) {
            // 此时 index 已有元素, 数组 index 位置及以后的元素全部向后移,空出 index 的位置
            for (int i = size; i > index; i--) {
                data[i] = data[i - 1];
            }
        }
        data[index] = e;
        size++;
    }

    /**
     * 在数组开头插入
     *
     * @param e
     */
    public void addLast(T e) {
        add(size, e);
    }

    /**
     * 在数组末尾插入
     *
     * @param e
     */
    public void addFirst(T e) {
        add(0, e);
    }

    /**
     * 删除值为 e 的数组元素
     *
     * @param e
     */
    public void delE(T e) {
        int index = find(e);
        if (index != -1) {
            // 已找到应该删除的数组元素
            delIndex(index);
        } else {
            // 没有找到应该杀出的数组元素
            System.out.println("没有等于" + e + "的数组元素");
        }
    }

    /**
     * 删除下标为 index 的数组元素
     *
     * @param index
     */
    public T delIndex(int index) {
        checkIndex(index);
        T res = data[index];
        for (int i = index; i < size - 1; i++) {
            data[i] = data[i + 1];
        }
        size--;
        // 如果数组内实际存储的元素个数为数组容量的 1 / 4,则让数组容量减少为原来的一半
        if (size == (capacity / 4)) {
            resize(capacity / 2);
        }
        return res;
    }

    public T delLast() {
        return delIndex(size - 1);
    }

    public T delFirst() {
        return delIndex(0);
    }

    /**
     * 改变数组大小
     */
    private void resize(int capacity) {
        this.capacity = capacity;
        T[] newData = (T[]) new Object[capacity];
        // 移动数组
        for (int i = 0; i < size; i++) {
            newData[i] = data[i];
        }
        // data 指向新创建的数组
        data = newData;
    }

    /**
     * 返回下标为 index 的元素
     *
     * @param index
     */
    public T find(int index) {
        checkIndex(index);
        return data[index];
    }

    /**
     * 寻找值为 e 的元素位置
     *
     * @param e
     * @return 返回数组下标
     */
    public int find(T e) {
        for (int i = 0; i < data.length; i++) {
            if (data[i].equals(e)) {
                return i;
            }
        }
        return -1;
    }

    private void checkIndex(int index) {
        // 判断 index 的合法性
        if (index < 0) {
            throw new RuntimeException("传入的参数 index 不合法,数组下标必须大于等于 0");
        }
    }

    /**
     * 返回所有数据元素
     */
    @Override
    public String toString() {
        StringBuffer sb = new StringBuffer("data = [");
        for (int i = 0; i < size; i++) {
            sb.append(data[i]);
            if (i < size - 1) {
                sb.append(", ");
            }
        }
        sb.append("] , size = ");
        sb.append(size);
        sb.append(", capacity = ");
        sb.append(capacity);
        return sb.toString();
    }

    public static void main(String[] args) {
        GenericArray<Integer> array = new GenericArray<>();
        int len = 15;
        for (int i = 0; i < len; i++) {
            array.addLast(i);
        }
        System.out.println(array);
        array.addFirst(100);
        System.out.println(array);
        array.add(3, 20);
        System.out.println(array);

        System.out.println("开始测试删除");
        int cnt = 0;
        while (cnt != 12) {
            array.delIndex(0);
            cnt++;
        }
        System.out.println(array);
        array.delIndex(0);
        System.out.println(array);
    }
}