数组最大的优点:快速查询。比如 scores[2]
数组最好应用于“索引有意义”的情况。比如学生学号。
但并非所有有语意的索引都适用于数组。比如身份证号。
(一)自定义数组
size表示现在数组内的元素数量,size <= capacity;
capacity表示数组容量,即最大可容纳的元素数量,capacity=数组.length;
public class Array<E> {
private E[] data;
private int size;//当前数组内元素数量
public Array(int capacity){
data = (E[])new Object[capacity];
size = 0;
}
public Array(){
data = (E[])new Object[10];
size = 0;
}
public void add(int index,E e){
if (index < 0 || index > size)
throw new RuntimeException("Add failed! Index is illegal.");
if (size == data.length)
throw new RuntimeException("Add failed! Array is full.");
for (int i = size - 1; i >= index ; i --)
data[i + 1] = data[i];
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("Remove failed! Index is illegal.");
E e = data[index];
for (int i = index + 1; i < size ;i ++)
data[i - 1] = data[i];
size --;
data[size] = null;//loitering objects != memory leak,这句可写可不写
return e;
}
public E removeFirst(){
return remove(0);
}
public E removeLast(){
return remove(size - 1);
}
public E get(int index){
if ( index < 0 || index >= size)
throw new RuntimeException("Get failed! Index is illegal.");
return data[index];
}
public E getFirst(){
return get(0);
}
public E getLast(){
return get(size - 1);
}
public void set(int index){
if ( index < 0 || index >= size)
throw new RuntimeException("Set failed! Index is illegal.");
}
@Override
public String toString(){
StringBuilder res = new StringBuilder();
res.append("[ ");
for (int i = 0; i < data.size ; i ++){
res.append(data[i]);
if (i != size - 1)
res.append(",");
}
res.append(" ]");
return res.toString();
}
public boolean isEmpty(){
return size == 0;
}
public int getCapacity(){
return data.length;
}
public int getSize(){
return size;
}
public boolean contains(E e){
for (int i = 0; i < data.length ;i ++)
if (data[i].equals(e))
return true;
return false;
}
public int find(E e){
for (int i = 0;i < data.length ;i ++)
if (data[i].equals(e))
return i;
return -1;
}
private void resize(int newCapacity){
E[] newData = (E[])new Object[newCapacity];
for (int i = 0; i < size ; i ++)
newData[i] = data[i];
data = newData;
}
}
add方法:
在索引index的位置添加元素e,当数组没有空间(size==data.length)时,会抛出错误。
但不能一直这样,毕竟咱这是“动态”数组!所以,我们就考虑没有空间时为数组扩容。
扩大为原来数组容量的2倍,即 2 * data.length。
- 那么可能会有疑问说为什么是2倍呢?为什么不是+5或是+1000呢?
我们可以这么想,如果当前数组容量是10000满了的话,大概率是大量添加数据的,一次只增加个位数容量的话,操作频繁很耗时;增加太多可能用不了。
如果当前容量是50满了的话,太少操作频繁耗时,一下增加1000可能太多用不了浪费空间。
所以不能把额外增加的容量设为一个定值,要根据当先数组容量进行变化。
2 * data.length就很合适,3 * data.length当然也可以,但也不要太大了。
那么add方法就可以修改为:
public void add(int index,E e){
if (index < 0 || index > size)
throw new RuntimeException("Add failed! Index is illegal.");
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){//只在这个类的内部使用,用户不能调用,所以是private
E[] newData = (E[])new Object[newCapacity];
for (int i = 0; i < size ; i ++)
newData[i] = data[i];
data = newData;
}
既然是动态数组,add要扩容,remove到一定程度肯定要缩容。这里肯定也不能减少一个定值,也是要根据数组当前的容量决定的。
前面是增大2倍,这里也就缩小到原来的二分之一吧!也就是:
public void remove(int index){
...
if(size == data.length / 2)
resize(data.length / 2);
...
}
但这样写也是有问题的,我们接着向下看。
(二)时间复杂度
add和remove操作的是末尾元素的情况下,时间复杂度就是O(1),最坏情况下就是O(n)。
但并不是每一次增删操作都能触发resize方法。
所以对于resize来说,完全使用这种最坏情况下的分析是不合理的。
(三)均摊复杂度和防止复杂度的震荡
9次addLast操作,触发一次resize操作,总共进行了17(8+8+1)次操作。
实际,不可能每次addLast都会触发resize操作。
这意味着,这时的时间复杂度跟有多少个元素是没有关系的。
这种情况下计算均摊时间是有意义的,因为最坏情况并不会每次都出现。
均摊复杂度(amortized time complexity):一个相对比较耗时的操作,如果我们能保证他不会每次都触发的话,那么这个比较耗时的操作是可以分摊到其他操作中的。
同理,removeLast操作,均摊复杂度也为O(1)。
-
复杂度震荡
但是有一种特殊情况要注意,会有复杂度震荡。
在一些特殊的情况下,size=capacity=n时(如上图),addLast和removeLast操作都会扩容或缩容,时间复杂度为O(n)。
添加元素容量不够时只能进行扩容,但删除元素时,可以稍微“懒惰”一点,不着急处理。
当size==capacity / 4时,才将capacity = data.length / 2;
所以remove方法应改为:
public E remove(int index){
if (index < 0 || index >= size)
throw new RuntimeException("Remove failed! Index is illegal.");
E e = data[index];
for (int i = index + 1; i < size ;i ++)
data[i - 1] = data[i];
size --;
data[size] = null;//这句可写可不写,都不影响的。loitering objects != leak memory
if (size == data.length / 4 && data.length / 2 != 0)
resize(data.length / 2);
return e;
}
private void resize(int newCapacity){
E[] newData = (E[])new Object[newCapacity];
for (int i = 0; i < size ; i ++)
newData[i] = data[i];
data = newData;
}
有的时候,“犯懒”反而会使性能得到提升。算法“懒”不代表容易编写或是代码量少。
"犯懒"是算法竞赛中用到的高阶的技巧,有兴趣的可以自己查资料深入了解一下。
博主也正在学习中,如果有错误或疑问欢迎评论私信交流讨论!^_^