数据结构
一、动态数组
1.重写Array类:
public class Array {
private T[] data;
private int size;
//构造函数
public Array(int capacity){
data = (T[]) new Object[capacity];
size=0;
}
//无参构造函数,默认容量为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(T e){
add(size,e);
}
//添加元素到数组开头
public void addFirst(T e){
add(0,e);
}
//向数组中指定位置添加元素
public void add(int index,T e){
if(index <0||index >size)
throw new IllegalArgumentException("add is failed, index should >=0 and <=size");
if(size == data.length)
**//throw new IllegalArgumentException("add is failed, Array is full");**
//如果数组容量不够,不抛出异常,再分配一次空间,实现动态数组的目的
resize(2 * data.length);
for(int i = size-1;i >=index;i --)
data[i+1] = data[i];
data[index] = e;
size++;
}
//取得给定下标对应的值
public T get(int index){
if(index<0||index>=size)
throw new IllegalArgumentException("get is failed, index should >=0 and <size");
return data[index];
}
//替换或添加元素
public void set(int index,T e){
if(index<0||index>=size)
throw new IllegalArgumentException("set is failed, index should >=0 and <size");
data[index] = e;
}
//判断数组中是否包含给定的值
public boolean contain(T e){
for(int i=0;i<size;i++){
if(data[i].equals(e))
return true;
}
return false;
}
//寻找数组中是否有给定值,并返回其下标,如果没有,则返回-1
public int find(T e){
for(int i=0;i<size;i++){
if(data[i].equals(e))
return i;
}
return -1;
}
//从数组中删除给定下标对应的元素
public T remove(int index){
if(index<0||index>=size)
throw new IllegalArgumentException("index is illegal");
T res=data[index];
for(int i=index+1;i<size;i++){
data[i-1]=data[i];
}
size--;
data[size]=null;//不这样做也是可以的,因为下标为size的元素用户访问不到
if(size == data.length/4 && data.length/2 != 0)//缩容不能过急,以免在有些情况下导致不断地重新分配空间
resize(data.length/2);
return res;
}
//删除第一个元素
public T removeFirst(){
return remove(0);
}
//删除最后一个元素
public T removeLast(){
return remove(size-1);
}
//删除指定元素
public void removeElement(T e){
int index=find(e);
if(index!=-1)
remove(index);
}
//将数组内容转换为字符串
@Override//在重写toString这个方法出错是会报错
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();
}
private void resize(int newCapacity){
T[] newData = (T[]) new Object[newCapacity];
for(int i=0;i<size;i++)
newData[i]=data[i];
data=newData;
}
}
2.使用泛型
可以修改这个Array类,使其不仅限于存储整型数据
注意:
泛型不可以是基本数据类型,如int、boolean
编写构造函数时,先将数组定义为Object类的,在将其转换为我们需要的类类型
(Object类是所有类的父类)
==运算符比较的是地址的引用,改成泛型后,要用equals方法判断元素是否相等
3.时间复杂度的分析
3.1简单的时间复杂度分析
O:运行时间和输入数据之间的关系(渐进时间复杂度)
O(n):线性关系
O(n^2):T=knn+b
addLast函数时间复杂度为O(1),因为只是添加了一个元素,其它数据没有变化
addFirst函数时间复杂度为O(n),在第一个位置添加元素,原来的所有元素都要向后移动
add函数,考虑到概率,时间复杂度为O(n/2),去掉常数,复杂度也为O(n)
resize函数将原来数组中的所有元素复制到新的数组中,时间复杂度为O(n)
当调用addLast函数时,如果数组已经满了,就需要resize,此时时间复杂度为O(n),这种情况下不好考虑,于是引入均摊复杂度
3.2均摊时间复杂度、复杂度震荡
如果当前数组容量为n,调用addLast函数添加元素,添加到第n+1个元素时,会调用resize
这时,addLast的时间复杂度为添加元素产生的时间复杂度和resize的复杂度之和
也就是:添加了n+1个元素,resize复制了n个元素到新的数组中,总的时间复杂度为2n+1
平均下来,addLast的时间复杂度为2,也还是O(1)
复杂度震荡
如果数组容量为n,数组存储的元素数量已经到达容量上限,调用addLast再添加一个元素会让数组容量变为2n
此时调用removeLast,数组容量又变回n
再调用addLast,变为2n,调用removeLast,变回n
如果发生了这样的情况,原本复杂度为O(1)的操作,复杂度会变得很大
因为removeLast缩容过快了(eager),将其机制修改为每当数组中的元素数量为容量的1/4时,才将容量缩小一半,
就可以解决短时间内添加元素又要调用时间复杂度为O(n)的resize的问题