线性表
概念:可以说是像线一样的表,最为典型的就是数组。零个或多个数据元素的有限序列。最主要的几个关键字:序列;所以说这是有序的。有限;
这次要说的是线性表的顺序存储结构。
- 顺序存储结构:一段地址连续的存储单元依次存储线性表的数据单元。因此,我们可以想到数组,数组就有一段连续的内存地址。所以应该想到线性表的先行存储结构是不是基于数组实现的呢?
ArrayList就是基于数组实现的。
/*
* 这就是Java中ArrayList(线性表)底层源码的一部分,可以看出线性表是基于数组实现的
*/
public class ArrayList<E> extends AbstractList<E>
implements List<E>, RandomAccess, Cloneable, java.io.Serializable
{
private static final long serialVersionUID = 8683452581122892189L;
/**
* 默认初始容量。
*/
private static final int DEFAULT_CAPACITY = 10;
/**
* 用于空实例的共享空数组实例。
*/
private static final Object[] EMPTY_ELEMENTDATA = {};
/*
* 用于默认大小的空实例的共享空数组实例。我们将其与空的元素数据区分开来,
* 以知道在添加第一个元素时要充气多少。
*/
private static final Object[] DEFAULTCAPACITY_EMPTY_ELEMENTDATA = {};
/*
* 存储ArrayList元素的数组缓冲区。arrayList的容量是此数组缓冲区的长度。添加第一个元素时,
* elementdata==default capacity\u empty\u elementdata的任何空arraylist都将扩展为default\u capacity。
*/
transient Object[] elementData; // 非私有以简化嵌套类访问
public ArrayList(int initialCapacity) {
if (initialCapacity > 0) {
this.elementData = new Object[initialCapacity];
} else if (initialCapacity == 0) {
this.elementData = EMPTY_ELEMENTDATA;
} else {
throw new IllegalArgumentException("Illegal Capacity: "+ initialCapacity);
}
}
/**
* Constructs an empty list with an initial capacity of ten.
*/
public ArrayList() {
this.elementData = DEFAULTCAPACITY_EMPTY_ELEMENTDATA;
}
/*
* 构造包含指定集合的元素的列表,按集合的迭代器返回这些元素的顺序
* @param c 要将其元素放入此列表中的集合
* @throws NullPointerException 如果指定的集合为空
*/
public ArrayList(Collection<? extends E> c) {
elementData = c.toArray();
if ((size = elementData.length) != 0) {
// c.toarray可能(错误地)不返回对象[(参见6260652)
if (elementData.getClass() != Object[].class)
elementData = Arrays.copyOf(elementData, size, Object[].class);
} else {
//替换为空数组。
this.elementData = EMPTY_ELEMENTDATA;
}
}
- 我们通过底层源码知道线性表是基于数组实现的,但是数组是如何被封装成成线性表的?
- 首先,因为线性表包括顺序存储结构和链式存储结构,但两者都有共性部分,也就是调用的一些方法,所以我们可以先定义一个接口(List),让顺序和链式都实现这个接口,本次我们先讲解顺序。
下面代码只是为了表示接口中的方法。
//这里用到泛型<E>是因为不能确定线性表中存储的数据类型,所以在使用时,可以交给用户去 定义,so我们给个泛型就可以了。
public interface List<E> {
public int getSize();//获取线性表中的元素个数(线性表的长度
public boolean isEmpty();//线性表中指定的index角标处添加元素e
public void add(int index, E e);//在线性表指定位置(index)插入元素
public void addFirst(E e);//在线性表的有位置插入元素
public void addLast(E e);//在线性表的末尾位置插入元素e
public E get(int index); //在线性表中获取指定index角标处的元素
public E getFirst(); //获取线性表中的首位的元素
public E getLast(); //获取线性表中最后一个元素
public void set(int index, E e);//修改线性表中index处的元素
public boolean contains(E e);//判断线性表中是否存在元素e 默认从前往后找
public int find(E e); //在线性表中获取指定元素e的角标 默认从前往后找
public E remove(int index); //在线性表中删除指定角标index处的元素并返回
public E removeFirst();//删除线性表的第一个元素
public E removeLast();//删除线性表中的最后一个元素
public void removeElement(E e); //删除元素e
public void clear(); //清空线性表
}
- 之后我们就可以实现这个接口了,在类中我们势必要定义一个数组来接我们用户传进来的数据,因为我们不确定即将存储的数据类型,所以我们可以交由用户去处理,so这里我们还是将数组类型定义为泛型,然后去实现就口中的方法。
/*
* 这里我们看到所有的访问类型都是private,因为用户使用是没必要使用到数组data的,
* 而直接使用封装之后的ArrayList就可以了
*
* 注意:这里值得注意的是,我们重新定义了一个新的size,这里的size是线性表的长度,
* 而容易混淆的是data.length也就是数组的长度,**数组的长度代表的是数组的容量**,不可与size混淆。
*/
private static int DEFAULT_SIZE = 10;
private E[] data;// 存储线性表的容器
private int size;// 线性表的个数size
- 然后就是完成接口中方法的功能,这是最重要的一步。
- 添加元素。
如果在空的线性表或者非空非满的线性表中中添加数据时是最为简单的,只需要在首地址添加数据就可以了。此时时间复杂度为O(1)。
但是如果在非空线性表中的中间位置添加元素,这时候就比较麻烦了,需要将待插位置的元素全部向后移动一位。然后将元素插入待插位置。时间复杂度为O(n).
!!!!!之后还有一个新的问题:如果元素满了之后怎么办?
此时,这是我们就可以有一个机制——数组扩容,扩容之后的容量为原数组的两倍。再将原数组中元素,移向新数组。
- 添加元素。
//给线性表添加元素
public void add(int index, E e) {
if (index < 0 || index > size) {
throw new ArrayIndexOutOfBoundsException("添加角标越界!");
}
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 i) {
E[] newData = (E[]) new Object[i];
for (int j = 0; j < size; j++) {
newData[j] = data[j];
}
data = newData;
}
-
删除元素
这时,删除最后一个元素时,只需size–即可。时间复杂度为O(1)但是删除中间元素时,或者第一个元素时,时间复杂度就比较高了。
此时时间复杂度为O(n).还有最后一种情况,如果数组缩小到一定空间我们也应该有和扩容相对的一个机制——缩容。
缩容:就是数组中的数据减少到原来的1/4时,如果此时容量还大于默认容量,就将其缩小为原来有效元素长度的2倍
//删除元素,这个方法也是比较那么易理解的。
public E remove(int index) {
// TODO Auto-generated method stub
if (index < 0 || index > size - 1) {
throw new ArrayIndexOutOfBoundsException("删除角标越界!");
}
E e = data[index];
for (int i = index + 1; i <= size - 1; i++) {
data[i - 1] = data[i];
}
size--;
// 判断是否缩容
// 1.最短不能缩过默认容量
// 2.有效元素的个数小于等于data.length/4
if (data.length > DEFAULT_SIZE && size <= data.length / 4) {
resize(2 * size);
}
return e;
}