对数组元素的增删改查
增操作
增加操作分为在数组头增加元素,在数组尾增加元素,在数组一般位置增加元素。在数组头和数组尾的增加操作可以看作是在数组一般位置增加元素的特殊情况。在数组一般位置增加元素,其实现逻辑为:假设要在数组的index处增加元素e,把从数组尾一直到index处的元素依次向后移一个位置,将e赋值给索引为index的数组元素。 在头部增加元素相当于调用此逻辑,并让index = 0,同理在尾部增加元素调用此逻辑,并让index = size(size为数组元素末尾的下一个位置)。
删操作
删除操作也分为在数组头部删除,在尾部删除,在一般位置删除,前两种可以看作是在一般位置删除元素的特殊情形。在数组的一般位置删除操作,实现逻辑为:假设要删除数组索引为index处的元素,事先保存该元素,把数组从索引为index到数组尾依次向前移动一个位置,最后返回删除的元素。 同理,可以通过调用的方法实现删除数组头和数组尾的操作。
改操作
通过传入索引和待修改元素值,直接赋值即可完成修改操作。
查操作
遍历数组,if判断即可。
实现动态数组
可以通过对数组的扩容和缩容实现动态数组。
扩容(先扩容,再增加)
在增加操作前,判断数组长度是否已经达到最大,如果数组已经满了,就要扩容,扩容操作为:新创建一个数组,将数组的长度默认增加为原来数组的一倍,把旧数组元素依次复制到新数组,再将旧数组指向新数组(因为新数组的生命周期仅为方法内),实现扩容。
缩容(先删除,在缩容)
在删除元素后,判断数组长度是否满足缩容的条件,如果满足,则缩容,其实现逻辑与扩容一样,只不过是传入的缩容新数组的长度不同。
Java代码实现动态数组
注意事项:
对于addLast和removeLast,假设数组容量是n,n + 1次addLast,触发resize,总共进行2n + 1次基本操作,平均每次addLast操作,进行2次基本操作,这样均摊计算,意味addLast的时间复杂度是O(1),而不是最坏的O(n),removeLast亦然
当我们同时对数组进行addLast和removeLast时,存在一种情况,当size = capacity时,addLast一个元素,扩容,对应的时间复杂度为O(n),紧接着removeLast,缩容,对应的时间复杂度为O(n),如此往复,每一个addLast和每一个removeLast,所耗费的时间复杂度都为O(n),所以这种情况不需要执行n次才触发addLast或者removeLast,复杂度猛的从O(1)到O(n),产生了复杂度震荡
复杂度震荡出现的原因:removeLast时resize过于着急(Eager),解决方案:变得Lazy,当size == capacity/4,再将capacity减半
public class Array<E> {
private E[] data;
private int size;//数组中有多少个元素
public Array(int capacity){//有参构造函数,传入数组的容量以此来构造数组
data = (E[])new Object[capacity];
size = 0;
}
public Array(){//默认构造函数,默认数组的容量是10
this(10);
}
public int getSize(){//获取数组中的元素个数
return size;
}
public int getCapacity(){//获取数组的容量
return data.length;
}
public boolean isEmpty(){//判断数组是否为空
return size == 0;
}
public void addFirst(E e){//在所有元素前添加一个元素
Add(0, e);
}
public void addLast(E e){//末尾添加元素
if(size == data.length)
throw new IllegalArgumentException("addLast failed.Array is full.");
data[size] = e;
size++;
// Add(size, e);可以直接调用Add方法
}
public void Add(int index, E e){//在index位置上插入元素e
if(index < 0 || index > size)
throw new IllegalArgumentException("Add failed.");
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 E get(int index){//获取index位置的元素
if(index < 0 || index >= size)
throw new IllegalArgumentException("get failed.");
return data[index];
}
public void set(int index, E e){//修改index位置的元素为e
if(index < 0 || index >= size)
throw new IllegalArgumentException("set failed.");
data[index] = e;
}
public boolean contains(E e){//查找数组中是否有元素e
for (int i = 0; i < size; i++) {
if(data[i].equals(e))
return true;
}
return false;
}
public int find(E e){//查找数组中元素e所在的索引,如果不存在,返回-1
for (int i = 0; i < size; i++) {
if(data[i].equals(e))
return i;
}
return -1;
}
public E remove(int index){//删除index位置的元素,返回删除的元素
if(index < 0 || index >= size)
throw new IllegalArgumentException("failed.");
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)//让代码变得更Lazy,防止复杂度震荡,同时防止数组的长度为1,因为1/2 = 0
resize(data.length / 2);
return res;
}
public E removeFirst(){
return remove(0);
}
public E removeLast(){
return remove(size - 1);
}
public void removeElement(E e){//删除元素e
int index = find(e);
if(index != -1)
remove(index);
}
private void resize(int newCapacity){//动态增加或者减小数组
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++) {
newData[i] = data[i];
}
data = newData;
}
@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 static void main(String[] args) {
// write your code here
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.remove(2);
// System.out.println(arr);
arr.addFirst(-1);
System.out.println(arr);
}
}