本文内容,参考自《大话数据结构》(程杰著) ,一部分自己修改,如:把C语言换成了Java语言。写作目的,意在加强记忆。
本文写作工具,使用 Typora ,Markdown 大法好。不过,和掘金提供的,有一些不兼容。
1.线性表的定义
线性表(List):零个或多个数据元素的有限序列。
这里需要强调几个关键的地方。
首先它是一个序列。也就是说,元素之间是有顺序的,若元素存在多个,则第一个元素无前驱,最后一个元素无后续,其他每个元素都有且只有一个前驱或后继。
然后,线性表强调是有限的,元素个数是有限的。事实上,在计算机中处理的对象都是有限的,那种无限的数列,只存在于数学的概念中。
若将线性表记为 (a1, … , ai-1,ai,ai+1, … ,an),则表中 ai-1 领先于 ai ,ai 领先于 ai+1 ,称 ai-1 是 ai 的直接前驱元素, ai+1 是 ai 的直接后继元素。当 i=1,2,…,n-1时,ai 有且仅有一个直接后继,当 i=2,3,…,n时,有且仅有一个直接前驱。
所以线性表元素的个数n(n ≧ 0)定义为线性表的长度,当 n=0 时,称为空表。
在非空表中的每个数据元素都有一个确定的位置,如 a1 是第一个数据元素,an 是最后一个数据元素, ai 是第 i 个数据元素,称 i 为数据元素 ai 在线性表中的位序。
比如,一年里的十二个月,是不是线性表呢?
当然是,1月开始,12月结束,当中的月份都有前驱和后继,而且一共只有十二个,完全符合线性表的定义。
2.线性表的顺序存储结构
我们来看看线性表的两种物理结构的第一种——顺序存储结构。
线性表的顺序存储结构,指的是用一段地址连续的存储单元依次存储线性表的数据元素。
说白了,就是在内存中找了块地,通过占位的形式,把一定内存空间给占了,然后把相同数据类型的数据元素依次存放。线性表的每个数据元素,类型都相同,所以可以用数组来实现顺序存储结构,把第一个数据元素存到数组下标为 0 的位置,接着把线性表相邻的元素存储在数组中相邻的位置。
下面我们以数组实现的顺序存储结构,来说明一下,线性表顺序存储结构的读取、插入和删除。
public class Demo {
private final int maxSize = 20;//存储空间初始化分配量
private int length;//当前长度
private int[] arr;//数组
public void Demo() {
this.arr = new int[maxSize];
}
//获取线性表的长度
public int length() {
return length;
}
//读取线性表第 index 个元素
public int get(int index) {
if(length==0 || index<0 || index>length-1) {
throw new IndexOutOfBoundsException();
}
int i = arr[index];
return i;
}
//存储元素
public void add(int item) {
if(length==maxSize) {
throw new IndexOutOfBoundsException();
//线性表已经满了
}
arr[length] = item;
length++;
}
//插入元素思路:
//如果插入的位置不合理,抛出异常
//如果线性表长度大于等于数组长度,则抛出异常或动态增加容量(可参考ArrayList源码)
//从最后一个元素开始向前遍历到第i个位置,分别将它们都向后移动一个位置
//将要插入元素填入位置i处
//表长加1
public boolean insert(int index,int key) {
if(length==maxSize) {
throw new IndexOutOfBoundsException();
//线性表已经满了
}
if(index<0 || index>length) {
throw new IndexOutOfBoundsException();
//index 不在范围内,这里是因为顺序存储结构,元素之间不能留空
}
if(index<=length-1) {
//插入的元素,不在表最后
//需要把插入位置之后的元素,向后移动一位
for(int k=length-1;k>=index;k--) {
arr[k+1] = arr[k];
}
}
arr[index] = key;
length++;
return true;
}
//删除元素思路:
//如果删除位置不合理,抛出异常
//从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
//表长减1
public boolean delete(int index) {
if(length==0) {
throw new IndexOutOfBoundsException();//线性表为空
}
if(index<0 || index>length) {
throw new IndexOutOfBoundsException();
//index 不在范围内
}
if(index < length-1) {//不是删除最后的位置
for(int k=index;k < length-1;k++) {
arr[k-1] = arr[k];
}
}
length--;
return true;
}
}复制代码
根据上面的代码,可以得出:
线性表的顺序存储结构,在存、读数据时,不管是哪个位置,时间复杂度都是 O(1)。
而插入或删除时,如果元素要插入到最后一个位置,或者删除最后一个元素,此时时间复杂度为 O(1),因为不需要移动元素。如果元素要插入到第一个位置或删除第一个元素,那此时的时间复杂度为 O(n)。根据概率原理,每个位置插入或删除元素的可能性是相同的,也就说位置靠前,移动次数多,位置靠后,移动次数少。最终平均移动次数和最中间的那个元素的移动次数相等,为 (n-1)/2。根据大O阶推导方法,可以得出,平均时间复杂度为O(n)。
线性表顺序存储结构的优缺点
优点 | 缺点 |
---|---|
无须为表示表中元素之间的逻辑关系而增加额外的存储空间 | 插入和删除操作需要移动大量元素 |
可以快速地存取表中任一位置的元素 | 当线性表长度变化较大时,难以确定存储空间的容量 |
造成存储空间的碎片 |