3、动态数组

发布时间 2023-04-10 13:53:48作者: lidongdongdong~

在这里,我们新创建一个数组类,对 Java 语言中的原始数组进行封装,使得它可以动态的扩容和缩容
Java 语言中也有类似的实现,叫 ArrayList,我们创建的数据类是它的简化版本,下面是代码实现

public class Array<E> {
    
    private E[] data;
    private int size;

    public Array(int capacity) {
        data = (E[]) new Object[capacity];
        size = 0;
    }

    public Array() {
        this(10);
    }

    public Array(E[] arr) {
        data = Arrays.copyOf(arr, arr.length);
        size = arr.length;
    }

    public int getSize() {
        return size;
    }
    
    public int getCapacity() {
        return data.length;
    }
    
    public boolean isEmpty() {
        return size == 0;
    }

    /**
     * 添加
     */
    public void add(int index, E e) {
        if (index < 0 || index > size) throw new RuntimeException("need 0 <= index <= size");
        if (size == data.length) resize(data.length * 2);
        
        System.arraycopy(data, index, data, index + 1, size - index);
        data[index] = e;
        size++;
    }
    
    public void addFirst(E e) {
        add(0, e);
    }
    
    public void addLast(E e) {
        add(size, e);
    }

    /**
     * 删除
     */
    public E remove(int index) {
        if (index < 0 || index >= size) throw new RuntimeException("need 0 <= index < size");
        
        E ret = data[index];
        System.arraycopy(data, index + 1, data, index, size - index - 1);
        size--;
        data[size] = null;
        
        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);
    }
    
    public void removeElement(E e) {
        int index = find(e);
        if (index != -1) remove(index);
    }

    /**
     * 修改
     */
    public void set(int index, E e) {
        if (index < 0 || index > size) throw new RuntimeException("need 0 <= index <= size");
        data[index] = e;
    }

    /**
     * 查看
     */
    public E get(int index) {
        if (index < 0 || index >= size) throw new RuntimeException("need 0 <= index < size");
        return data[index];
    }
    
    public E getFirst() {
        return get(0);
    }
    
    public E getLast() {
        return get(size - 1);
    }
    
    public boolean contains(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) return true;
        }
        return false;
    }
    
    public int find(E e) {
        for (int i = 0; i < size; i++) {
            if (data[i].equals(e)) return i;
        }
        return -1;
    }

    /**
     * 动态数组
     */
    private void resize(int newCapacity) {
        E[] newData = (E[]) new Object[newCapacity];
        System.arraycopy(data, 0, newData, 0, size);
        data = newData;
    }

    public void swap(int a, int b) {
        if (a < 0 || a >= size || b < 0 || b >= size) {
            throw new IllegalArgumentException("Swap failed, require 0 <= index < size");
        }
        E k = data[a];
        data[a] = data[b];
        data[b] = k;
    }

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

值得注意的是 resize 函数,resize 函数新创建了一个数组,并把原先数组里的元素挨个搬进新数组
乍一看 resize 函数的复杂度为 O(n),添加函数 add 和删除函数 remove 都会调用它,你可能会觉得我们的数据类性能极其低下
但事实上,添加函数 add 和删除函数 remove 并不是每次都会调用 resize 函数,我们来举一个列子
假设当前 capacity = 8,并且每一次添加操作都使用 addLast
我们进行 9 次 addLast 操作,只有在最后一次 addLast 操作时才会触发 resize(对前 8 次数据进行复制),总共进行了 8 + 1 + 8 = 17 次基本操作
9 次 addLast 操作,触发 resize,总共进行了 17 次基本操作,平均每次 addLast 操作,进行 2 次基本操作
假设 capacity = n,n + 1 次 addLast,触发 resize,总共进行 2n + 1 次基本操作,平均每次 addLast 操作,进行 2 次基本操作
这样均摊计算,时间复杂度是O(1)的!
在这个例子里,这样均摊计算,比计算最坏情况有意义