线性结构的特点是:在数据元素的非空有限集中,
- (1)存在唯一的一个被称做“第一个”的数据元素
- (2)存在唯一的一个被称做“最后一个”的数据元素
- (3)除第一个之外,集合中的每个数据元素均只有一个前驱
- (4)除最后一个之外,集合中的每个元素均只有一个后继
1.线性表定义
线性表(linear_list)是最常用且最简单的一种数据结构。线性表是n个类型相同数据元素的有限序列,通常记作(a0,a1,…,ai-1,ai,ai+1,…,an-1)。至于每个数据元素的具体含义,在不同的情况下各不相同,它可以是一个数或一个符号,也可以是一页书,甚至其他更复杂的信息。例如,26个英文字母的字母表:(A,B,C,…,Z)。
在线性表的相邻数据元素之间存在着序偶关系,即ai-1是ai的直接前驱,则ai是ai-1的直接后继。唯一没有直接前驱的元素a0一端称为表头,唯一没有后续的元素an-1一端称为表尾。
线性表中元素的个数n(n>=0
)定义为线性表的长度,n = 0
时称为空表。在非空表中的每个数据元素都有一个确定的位置,也即序号,例如a0的序号是0,ai的序号是i。
在这里要特别注意线性表和数组的区别:
(1)从概念上讲,线性表是一种抽象数据类型,而数组是一种具体的数据结构。线性表与数组的逻辑结构是不一样的,线性表是元素之间具有1对1的线性关系的数据元素的集合,而数组是一组数据元素到数组下标的一一映射。
(2)从物理性质上来看,数组中相邻的元素是连续地存储在内存中的;线性表只是一个抽象的数据结构,并不具有具体的物理形式。相反,线性表需要通过其他有具体物理形式的数据结构来实现。
(3)在线性表的具体实现中,表中相邻的元素不一定存储在连续的内存空间中,除非表示用数组来实现的。
线性表是一种非常灵活的数据结构,它的长度可根据需要进行增长或缩短,即对线性表的数据元素不仅可以进行访问,还可进行插入和删除等。
2.线性表的顺序存储与实现
线性表的顺序存储是用一组地址连续的存储单元依次存储线性表的数据元素。它的特点是,以数据元素的存储地址相邻来表示线性表中数据元素之间相邻的逻辑关系。每一个数据元素的存储地址和线性表的起始地址相差一个数据元素在线性表中的序号成正比的常数。因此,只要确定了线性表的起始地址,线性表中的任何一个数据元素都可以随机存取。因此线性表的的顺序存储结构是一种随机的存储结构。
由于高级语言中的数组也具有随机存储的特性,因此我们都是用数组来描述数据结构的顺序存储结构。在使用数组实现线性表的操作中,经常会碰到在数组中进行数据元素的查找、添加、删除等操作。
在数组中添加数据元素,通常是在数组中下标为i(0 <= i <= n)的位置添加数据元素,而将原来下标从i开始的数组中所有后续元素依次后移。整个操作如下图所示:
如果对上述操作的执行时间进行分析,那么对于不同的插入位置,情况是不同的。最坏的情况下,在数组的第0个位置进行插入,那么数组的所有元素都要后移,时间复杂度为O(n)。
同样的,对于删除也是一样的情况,都需要移动元素,时间复杂度也是O(n),如下:
另外,对于查找,我们也需要去遍历数组,进行比较,时间复杂度也是O(n)。
在Java中,ArrayList其实就是一个顺序存储的线性表,它的底层通过数组来实现,可以根据需要动态扩展数组的大小。下面我们通过数组实现自己的线性表MyArrayList,并实现一些常用的方法,实现Java中特有的迭代器方法。如下:
package linearlist;
import java.util.Iterator;
import java.util.NoSuchElementException;
public class MyArrayList<T> implements Iterable<T> {
// 默认容量
private static final int DEFAULT_CAPACITY = 10;
// 实际大小
private int theSize;
// 数组
private T[] theItems;
public MyArrayList() {
doClear();
}
public int size() {
return theSize;
}
public boolean isEmpty() {
return size() == 0;
}
/**
* 清除多余空间
*/
public void trimToSize() {
ensureCapacity(size());
}
/**
* 根据索引获取元素
*
* @param index
* @return
*/
public T get(int index) {
if (index < 0 || index >= size()) {
throw new IndexOutOfBoundsException();
}
return theItems[index];
}
/**
* 设置新值,返回旧值
*
* @param index
* @param newVal
* @return
*/
public T set(int index, T newVal) {
if (index < 0 || index >= size()) {
throw new IndexOutOfBoundsException();
}
T oldVal = theItems[index];
theItems[index] = newVal;
return oldVal;
}
/**
* 根据索引添加元素
*
* @param index
* @param x
*/
public void add(int index, T x) {
// 扩展容量
if (theItems.length == size()) {
ensureCapacity(size() * 2 + 1);
}
// 向后移动
for (int i = theSize; i > index; i--) {
theItems[i] = theItems[i - 1];
}
// 添加
theItems[index] = x;
// 实际容量增加1
theSize++;
}
/**
* 默认向最后添加
*
* @param x
* @return
*/
public boolean add(T x) {
add(size(), x);
return true;
}
/**
* 根据索引删除节点
*
* @param index
* @return
*/
public T remove(int index) {
T removedItem = theItems[index];
// 向前移动
for (int i = index; i < size() - 1; i++) {
theItems[i] = theItems[i + 1];
}
// 实际容量减小1
theSize--;
return removedItem;
}
private void doClear() {
theSize = 0;
ensureCapacity(DEFAULT_CAPACITY);
}
/**
* 数组扩展
*
* @param newCapacity
*/
public void ensureCapacity(int newCapacity) {
if (newCapacity < theSize) {
return;
}
T[] old = theItems;
// 泛型不能实例化,只能这么写
theItems = (T[]) new Object[newCapacity];
for (int i = 0; i < size(); i++) {
theItems[i] = old[i];
}
}
/**
* 获取元素在集合中的索引
*
* @param x
* @return
*/
public int indexOf(T x) {
if (x == null) {
for (int i = 0; i < theSize; i++) {
if (theItems[i] == null) {
return i;
}
}
} else {
for (int i = 0; i < theSize; i++) {
if (x.equals(theItems[i])) {
return i;
}
}
}
return -1;
}
/**
* 判断是否包含某个元素
*
* @param x
* @return
*/
public boolean contains(T x) {
return indexOf(x) >= 0;
}
/**
* 迭代器方法
*/
@Override
public Iterator<T> iterator() {
return new ArrayListIterator();
}
/**
* 迭代器。内部类
*
* @author Gavin
*
*/
private class ArrayListIterator implements Iterator<T> {
private int current = 0;
@Override
public boolean hasNext() {
return current < size();
}
@Override
public T next() {
// 如果已经没有元素了
if (!hasNext()) {
throw new NoSuchElementException();
}
return theItems[current++];
}
public void remove() {
MyArrayList.this.remove(--current);
}
}
@Override
public String toString() {
StringBuilder line = new StringBuilder("[");
for (int i = 0; i < size(); i++) {
line.append(theItems[i]);
if (i != size() - 1) {
line.append(", ");
} else {
line.append("]");
}
}
return line.toString();
}
}
说明:在MyArrayList
类中共有3个成员变量,DEFAULT_CAPACITY
是默认的数组大小,theSize
是数组中实际的元素个数,theItems
就是实际的数组,用来存储线性表中的元素。另外,MyArrayList
类实现了Iterable
接口,可以通过迭代器来遍历元素。
对于查找、删除、和插入操作,时间复杂度都是O(n)。