一、数组介绍
数组是一种线性表,用连续的内存空间存储类型相同的数据元素。
线性表,就是数据结构内存储的数据元素只有前和后两个方向的数据结构。
因为数组用连续的内存空间存储类型相同的数据,所以才拥有它最突出的特性 —— 它支持利用下标进行随机访问。
二、改变数组大小
如何实现动态扩容?
当数组容量不足时,创建一个容量更大的数组,将数组元素搬运过去。这个新的数组作为存储数据的数组。
翻译为代码为:
/**
* 改变数组大小
*/
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++;
}
让我们分析一下插入操作的时间复杂度:
- 当我们在数组末尾插入时,不需要搬运数据,最好时间复杂度为 O(1)。
- 当我们在数组头部插入时,需要搬运所有的数据,最坏时间复杂度为 O(n)。
- 删除的位置 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;
}
让我们来分析一下删除操作的时间复杂度:
- 删除数组末尾数据元素时,不需要搬运数据,最好时间复杂度为 O(1)。
- 删除数组第一个数据元素,需要搬运所有的数据,最坏时间复杂度为 O(n)。
- 删除的位置 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);
}
}