什么是数组
定义
用一片连续内存来保存同一类型数据的线性表数据结构
这个定义中有两个要点:
线性表
什么是线性表?元素之间只有前后关系的一种数据结构,如数组、链表、栈、队列。
而与之相反,非线性表,是指元素之间不是简单前后关系的数据结构,如堆、图、树等。
2. 连续内存和同一数据类型
正是因为连续内存和相同数据类型这个特点,才使得数组的随机下标访问有了O(1)的时间复杂度,那么具体是怎么做到的呢?对于数组的元素,使用下标访问的时候,其所在地址的计算公式为
address = base_address + i * data_type_length
即根据首地址及下标和数据类型长度即可计算出数组中任意元素在内存的地址偏移量,然后根据该地址进行访问。如下图,根据公式,可直接通过下标得到内存地址偏移量,从而做到快速的随机访问。
特点
低效的删除和插入
但是,为了保证内存的连续性,数组的插入和删除操作变得低效,因为插入或删除元素后需要将后面的元素进行搬移,导致效率变低,平均时间复杂度为O(n)。
实现一个动态扩容的数组
package _array;
import java.util.*;
/**
* 支持动态扩容数组
* @version 1.0
* @desc
* @date 2019/3/18
*/
public class DynamicArray<T> {
private Object[] elements;
private int capacity = DEFAULT_CAPACITY;
private static final int DEFAULT_CAPACITY = 2 << 3;
private int size;
public DynamicArray() {
this.elements = new Object[DEFAULT_CAPACITY];
}
public DynamicArray(int capacity) {
if(capacity <= 0){
throw new IllegalArgumentException("Capacity must be greater than zero");
}
this.elements = new Object[capacity];
}
public int size() {
return this.size;
}
public boolean isEmpty() {
return this.size == 0;
}
/**
* 时间复杂度为O(n)
* @param o
* @return
*/
public boolean contains(Object o) {
if(o == null){
for (Object e : elements) {
if(e == null){
return true;
}
}
}else {
for (Object e : elements) {
if (o.equals(e)) {
return true;
}
}
}
return false;
}
public void clear() {
for(int i = 0; i < elements.length; i++){
elements[i] = null;
}
this.size = 0;
}
@SuppressWarnings("unchecked")
public T get(int index) {
ensureIndex(index);
return (T)elements[index];
}
void ensureIndex(int index){
if(index < 0 || index >= elements.length){
throw new IllegalArgumentException("Index out of bound. Required:[0-"+capacity+"], actual: ["+index+"]");
}
}
/**
*
* @param index
* @param element
*/
public void add(int index, T element) {
ensureIndex(index);
ensureCapacity();
if(index >= size) {
elements[index] = element;
}else {
System.arraycopy(elements, index, elements, index+1, size - index);
}
elements[index] = element;
++size;
}
public void add(T element) {
ensureCapacity();
elements[size++] = element;
}
/**
* 当数组中元素数量将要超过数组最大容量的时候,进行扩容,
* 1、将容量增长一倍,如果大于Integer.MAX ,则按Integer.MAX来算
* 2、将原来数组的元素全部移动到新数组
*/
private void ensureCapacity() {
if(size >= capacity){
int doubleCapacity = capacity << 1;
capacity = doubleCapacity >= Integer.MAX_VALUE ? Integer.MAX_VALUE : doubleCapacity ;
}
Object[] newArray = new Object[capacity];
System.arraycopy(elements, 0, newArray, 0, elements.length);
elements = newArray;
}
@SuppressWarnings("unchecked")
public T remove(int index) {
ensureIndex(index);
T t = (T)elements[index];
size--;
elements[index] = null;
return t;
}
@SuppressWarnings("unchecked")
public T remove(T t){
int index = index(t);
if(index > 0){
return (T) elements[index];
}else {
return null;
}
}
public int index(T t){
for(int i = 0; i < size; i++){
if(t == null){
if( elements[i] == null) {
return i;
}
}else {
if(t.equals(elements[i])){
return i;
}
}
}
return -1;
}
@Override
public String toString() {
final StringBuilder sb = new StringBuilder("DynamicArray{");
sb.append("elements=").append(Arrays.toString(elements));
sb.append(", capacity=").append(capacity);
sb.append(", size=").append(size);
sb.append('}');
return sb.toString();
}
}
总结
1、数组是使用连续内存存储相同类型数据的线性表数据结构。
2、数组的特点是随机访问快,但是插入和删除效率慢。
3、参考java容器类arrayList实现了一个可以扩容的数组。