数组是一组相关数据的集合,一个数组实际上就是一连串的变量,数组按照使用可以分为一维数组、二维数组、多维数组。

简单回顾下Java中的数组使用方法:

public class Main {
    public static void main(String[] args) {
        // 创建一个指定长度的数组
        int[] arr = new int[20];
        // 数组遍历
        for (int i = 0, len = arr.length; i < len; i++)
            arr[i] = i;

        // 创建数组并初始化
        int[] scores = new int[]{100, 99, 98};
        // 数组遍历
        for (int i = 0, len = scores.length; i < len; i++)
            System.out.println(scores[i]);

        // 数组遍历
        for (int score: scores)
            System.out.println(score);

        // 修改数组中的值
        scores[0] = 99;

        for (int score: scores)
            System.out.println(score);
    }
}

数组最大的优点是可以快速查询。我们可以通过索引直接访问数组中的元素。如将上述代码中的scores[0]的值设置成99.

下面我们来简单实现下数组中常用的方法,如增、删、改、查。

public class Array {
    private int[] data;
    // 存放数组中有多少个有效元素
    private int size;

    // 有参数的构造函数,用传入的capacity构造数组
    public Array(int capacity) {
        data = new int[capacity];
        size = 0;
    }

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

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

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

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

    // 向数组末尾添加元素
    public void addLast(int e) {
        add(size, e);
    }

    public void addFirst(int e) {
        add(0, e);
    }

    // 在指定位置插入特定元素
    public void add(int index, int e) {
        if (size == data.length)
            throw new IllegalArgumentException("AddLast failed. Array is full.");

        if (index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");

        for (int i = size - 1; i >= index; i--)
            data[i + 1] = data[i];

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

    public E remove(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal.");

        E res = data[index];
        for (int i = index + 1; i < size; i++)
            data[i - 1] = data[i];

        size--;
        data[size] = null;

        return res;
    }

    // 删除数组中的第一个元素
    public int removeFirst() {
        return remove(0);
    }

    // 删除数组中的最后一个元素
    public void removeLast() {
        remove(size - 1);
    }

    // 从数组中删除某个元素
    public void removeEle(int e) {
        int index = findIndex(e);

        if (index != -1)
            remove(index);
    }

    // 获取数组中指定位置的值
    public int get(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Get failed. Index is illegal.");

        return data[index];
    }

    // 修改指定位置的值
    public void set(int index, int e) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Index is illegal.");

        data[index] = e;
    }

    // 数组中是否包含某个元素
    public boolean contains(int e) {
        for (int i = 0; i < size; i++)
            if (data[i] == e)
                return true;
        return false;
    }

    // 查找特定元素所在的索引
    public int findIndex(int e) {
        for (int i = 0; i < size; i++)
            if (data[i] == e)
                return i;
        return -1;
    }

    @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();
    }
}

测试封装的数组方法:

public class Main {
    public static void main(String[] args) {
        Array arr = new Array(30);

        for (int i = 0; i < 10; i++)
            // 数组末尾依次添加
            arr.addLast(i);

        System.out.println(arr);
        /*
        输出
        Array: size = 10, capacity = 30
        [0,1,2,3,4,5,6,7,8,9]
        */

        // 向索引1的位置插入20
        arr.add(1, 20);
        System.out.println(arr);
        /*
        Array: size = 11, capacity = 30
        [0,20,1,2,3,4,5,6,7,8,9]
        */

        arr.addFirst(-1);
        System.out.println(arr);
        /*
        输出
        Array: size = 12, capacity = 30
        [-1,0,20,1,2,3,4,5,6,7,8,9]
        */

        arr.remove(2);
        System.out.println(arr);
        /*
        Array: size = 11, capacity = 30
        [-1,0,1,2,3,4,5,6,7,8,9]
        */

        arr.removeEle(-1);
        System.out.println(arr);
        /*
        Array: size = 10, capacity = 30
        [0,1,2,3,4,5,6,7,8,9]
        */

        arr.removeFirst();
        System.out.println(arr);
        /*
        Array: size = 9, capacity = 30
        [1,2,3,4,5,6,7,8,9]
        */
    }
}

# 泛型类

上面我们所实现的数组方法只适用于整型,然而实际开中数组中不仅仅只能存放整型还能存放其他类型,如字符串、bool、对象等等。下面我将上面封装的数组类进行修改,让它可以支持其他类型,这里我们需要使用到泛型,具体代码如下:

public class Array<E> {
    private E[] data;
    // 存放数组中有多少个有效元素
    private int size;

    // 有参数的构造函数,用传入的capacity构造数组
    public Array(int capacity) {
        //data = new E[capacity]; 不支持new一个泛型数组
        data = (E[])new Object[capacity];
        size = 0;
    }

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

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

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

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

    // 向数组末尾添加元素
    public void addLast(E e) {
        add(size, e);
    }

    public void addFirst(E e) {
        add(0, e);
    }

    // 在指定位置插入特定元素
    public void add(int index, E e) {
        if (size == data.length)
            throw new IllegalArgumentException("AddLast failed. Array is full.");

        if (index < 0 || index > size)
            throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");

        for (int i = size - 1; i >= index; i--)
            data[i + 1] = data[i];

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

    public E remove(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal.");

        E res = data[index];
        for (int i = index + 1; i < size; i++)
            data[i - 1] = data[i];

        size--;
        data[size] = null;

        return res;
    }

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

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

    // 从数组中删除某个元素
    public void removeEle(E e) {
        int index = findIndex(e);

        if (index != -1)
            remove(index);
    }

    // 获取数组中指定位置的值
    public E get(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Get failed. Index is illegal.");

        return data[index];
    }

    // 修改指定位置的值
    public void set(int index, E e) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Set failed. Index is illegal.");

        data[index] = e;
    }

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

    // 查找特定元素所在的索引
    public int findIndex(E e) {
        for (int i = 0; i < size; i++)
            if (data[i].equals(e))
                return i;
        return -1;
    }

    @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();
    }
}

测试基本类型

public class Main {
    public static void main(String[] args) {
        Array<Integer> arr = new Array<Integer>(30);

        for (int i = 0; i < 10; i++)
            // 数组末尾依次添加
            arr.addLast(i);

        System.out.println(arr);
        /*
        输出
        Array: size = 10, capacity = 30
        [0,1,2,3,4,5,6,7,8,9]
        */
    }
}

测试引用类型

public class Student implements Comparable<Student>{
    private String name;
    private int score;

    public Student(String name, int score) {
        this.name = name;
        this.score = score;
    }

    @Override
    public  int compareTo(Student another) {
        return another.score - this.score;
    }

    @Override
    public boolean equals(Object student) {
        if (this == student)
            return true;
        if (student == null)
            return false;
        if (this.getClass() != student.getClass())
            return false;

        Student another = (Student) student;
        return this.score == another.score;
    }

    @Override
    public String toString() {
        return String.format("Student(name: %s, score: %d)", name, score);
    }

    public static void main(String[] args) {
        Array<Student> arr = new Array<Student>();

        arr.addLast(new Student("fang", 100));
        arr.addLast(new Student("fei", 100));
        arr.addLast(new Student("yue", 100));

        System.out.println(arr);
        /*
        输出
        Array: size = 3, capacity = 10
        [Student(name: fang, score: 100),Student(name: fei, score: 100),Student(name: yue, score: 100)]
        */
    }
}

# 动态数组

上面我们实现的Array类实际还是Java的一个静态数组,它的容量是有限的。当我们使用数组时一开始可能无法预估要使用多大的空间,如果容量开的太大就会浪费,如果开的太小的话空间有可能不够用。有没有容量可伸缩的

public class Array<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++;
    }

    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<Integer>();

        for (int i = 0; i < 10; i++)
            // 数组末尾依次添加
            arr.addLast(i);

        System.out.println(arr);
        arr.addLast(10);
        System.out.println(arr);
        /*
        输出 
        Array: size = 10, capacity = 10
        [0,1,2,3,4,5,6,7,8,9]

        Array: size = 11, capacity = 20
        [0,1,2,3,4,5,6,7,8,9,10]
         */
    }
}

缩容

public class Array<E> {
  // ...

  public E remove(int index) {
      if (index < 0 || index >= size)
          throw new IllegalArgumentException("Remove failed. Index is illegal.");

      E res = data[index];
      for (int i = index + 1; i < size; i++)
          data[i - 1] = data[i];

      size--;
      data[size] = null;

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

      return res;
  }

  // ...
}

测试

public class Main {
    public static void main(String[] args) {
        Array<Integer> arr = new Array<Integer>(4);

        for (int i = 0; i < 4; i++)
            arr.addLast(i);

        System.out.println(arr);

        arr.removeLast();
        arr.removeLast();

        System.out.println(arr);

        /*
        输出:
        Array: size = 4, capacity = 4
        [0,1,2,3]

        Array: size = 2, capacity = 2
        [0,1]
        */
    }
}

# 复杂度分析

添加操作

addLast(e) -> O(1)

addFirst(e) -> O(n)

add(index, e) -> O(n)

添加操作综合来看是一个O(n)级别的操作,虽然addLast(e)是O(1)级别的,但是在算法复杂度分析时通常看最坏的情况的。

删除操作

removeLast(e) -> O(1)

removeFirst(e) -> O(n)

remove(index, e) -> O(n)

resize() -> O(n)

修改操作

set(index, e) -> O(1)

查找操作

get(index) -> O(1)

contains(e) -> O(n)

find(e) -> O(n)

整体来看:

增 -> O(n)

删 -> O(n)

改:已知索引O(1),未知索引O(n)

查:已知索引O(1),未知索引O(n)

增删如果只对最后一个操作时间复杂度应该是O(1),这里说是O(n),是因为有resize,resize是O(n)的

# 均摊时间复杂度

我们不可能每次添加元素的时候都触发resize操作,

假设当前capacity=8,并且每一次添加操作都使用addLast,当添加到第9个元素时数组才满,这个时候需要进行resize操作,要将之前的8个元素全部复制到新的数组中,然后再添加,这样前后总共进行了17次操作。平均来看相当于每次addLast操作进行了2次基本操作。

推而言之,假设capacity=n,要进行n+1次addLast才会触发一次resize,总共进行了2*n+1次基本操作。这样均摊计算removeLast和addLast的时间复杂度为O(1)

# 复杂度震荡

假设一个数组它的capacity为n,并且已经装满了元素,此时调用addLast操作就要扩容,之后马上进行removeLast操作,此时会触发缩容操作。这时我们又调用了一次addLast操作,此时又要扩容,之后又调用removeLast操作,此时又会缩容,如此反复。

出现问题的原因在于removeLast时resize过于着急

假设数组是满的,当调用addLast后会扩容,当调用removeLast时先不急着立马缩容,看看后续是否还有removeLast操作,当删除到数组的四分之一,此时我们再缩容,缩容只缩容数组的二分之一,这样当我们再添加元素时也不用立马触发扩容操作。

修改Array的remove方法如下

public class Array<E> {
  // ...
  public E remove(int index) {
        if (index < 0 || index >= size)
            throw new IllegalArgumentException("Remove failed. Index is illegal.");

        E res = data[index];
        for (int i = index + 1; i < size; i++)
            data[i - 1] = data[i];

        size--;
        data[size] = null;

        if(size == data.length / 4 && data.length / 2 != 0)
            resize(data.length / 2);

        return res;
    }
  // ...
}