最近在学数据结构,接下来一段时间我将用java来实现所学的各种数据结构,以加深自己的印象。
线性表包括顺序表和链表,其实顺序表就是动态数组,下面我将二次封装实现属于自己的动态数组。
-----------------------------------------------------------------------------------------------------------------------------------------------------
数组类:Array
数组名:data
数组容量:capacity
已存放元素个数:size
方法:
①获取数组中元素个数(getSize)
②获取数组容量(getCapacity)
③返回数组是否为空(isEmpty)
④向数组末尾添加元素(addLast)
⑤向指定索引位置添加元素(add)
⑥向所有元素前添加一个新元素(addFirst)
⑦得到指定索引位置的元素(get)
⑧修改指定索引位置的元素(set)
⑨查找数组中是否有指定元素(contains)
⑩查找数组中指定元素的索引(find)
⑪删除指定索引位置的元素(remove)
⑫删除数组中第一个元素(removeFirst)
⑬删除数组最后一个元素(removeLast)
⑭查找指定元素并删除(removeElement)
⑮实现扩容和缩容(resize)
当然感兴趣的可以继续添加自己想要的功能,比如删除全部元素等等。
-----------------------------------------------------------------------------------------------------------------------------------------------
public class Array<E> {
private E[] data;
private int size;// 数组中元素个数
// 构造函数,传入数组容量capacity构造Array
public Array(int capacity) {
data = (E[]) new Object[capacity];
size = 0;
}
// 无参数构造函数,默认数组容量为10
public Array() {
this(10);
}
// 获取数组容量
public int getCapacity() {
return data.length;
}
// 获取数组中元素个数
public int getSize() {
return size;
}
// 返回数组是否为空
public boolean isEmpty() {
return size == 0;
}
// 向数组所有元素前添加元素
public void addFirst(E e) {
add(0, e);
}
// 向数组末尾添加元素
public void addLast(E e) {
add(size, e);
}
// 向数组index位置插入元素e
public void add(int index, E e) {
if (index < 0 || index > size)
throw new IllegalArgumentException("操作失败,需要满足 index<0 || index>size");
if (size == data.length)
resize(data.length << 1);// 实现两倍扩容
// 使元素往后挪一个位置
for (int i = size - 1; i >= index; i--)
data[i + 1] = data[i];
data[index] = e;
size++;
}
// 得到指定索引上的数组元素
public E query(int index) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("操作失败,需要满足 index < 0 || index >= size");
return data[index];
}
// 获得第一个元素
public E getFirst() {
return query(0);
}
// 获得最后一个元素
public E getLast() {
return query(size - 1);
}
// 将指定索引上的元素修改成e
public void modify(int index, E e) {
data[index] = e;
}
// 查找数组中是否有元素e
public boolean contains(int e) {
for (int i = 0; i < size; i++)
if (data[i].equals(e))
return true;
return false;
}
// 查找数组中指定元素的一个索引
public int find(E e) {
for (int i = 0; i < size; i++)
if (data[i].equals(e))
return i;
return -1;
}
// 删除指定索引上的元素,返回删除元素
public E remove(int index) {
if (index < 0 || index >= size)
throw new IllegalArgumentException("操作失败,需要满足 index < 0 || index >= size");
E temp = data[index];
for (int i = index + 1; i < size; i++)
data[i - 1] = data[i];
size--;
data[size] = null;// loitering objects
if (size == data.length >> 2 && data.length >> 1 != 0)// 防止复杂度震荡,当元素个数为容量的1/4时才执行缩容
resize(data.length >> 1);// 实现对半缩容
return temp;
}
// 删除数组第一个元素,返回删除元素
public E removeFirst() {
return remove(0);
}
// 删除数组最后一个元素,返回删除元素
public E removeLast() {
return remove(size - 1);
}
// 查找数组一个元素e并删除
public void removeElement(E e) {
int index = find(e);
if (index != -1)
remove(index);
}
@Override
public String toString() {
StringBuilder res = new StringBuilder();
res.append(String.format("元素个数:%d,容量大小:%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) {
E[] newData = (E[]) new Object[newCapacity];
for (int i = 0; i < size; i++)
newData[i] = data[i];
data = newData;// 将新数组的地址指向旧数组
}
}
-----------------------------------------------------------------------------------------------------------------------------------------------
简单时间复杂度分析:
①添加操作(删除操作等同)
addLast(e) 可能触发resize扩容操作,最坏时间复杂的为O(n)
addFirst(e) O(n)
add(index,e) O(n/2)=O(n)
②修改操作
set(index,e) O(1)
③查找操作
get(index) O(1)
contains(e) O(n)
find(e) O(n)
总结:查询快速,添加删除贼慢
-----------------------------------------------------------------------------------------------------------------------------------------------
均摊时间复杂度分析:
我们知道在调用addLast方法时,有可能会触发resize扩容,因此最坏情况的时间复杂度是O(n),但是这样其实是不合理的,因为每次addLast操作不一定都触发resize;
假设capacity=n,每次都使用addLast作为添加操作,当n+1次addLast操作会触发resize扩容,将前面n个元素复制到新数组中,因此总共执行了2n+1次操作,平均每次addLast约等于进行了两次基本操作;
这样均摊计算,addLast操作的时间复杂度是O(1),在这个例子里均摊计算比计算最坏情况更有意义;
同理,removeLast操作的均摊复杂度也是O(1)。
-----------------------------------------------------------------------------------------------------------------------------------------------
复杂度震荡:
当数组已满时执行addLast时扩容,又马上执行removeLast导致缩容,会造成复杂度震荡,这样来回操作,每一个操作的时间复杂度为O(n)。
解决方法:
只需当size=capacity/4时,才将capacity减半实现缩容。