本文内容,参考自《大话数据结构》(程杰著) ,一部分自己修改,如:把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)。
线性表顺序存储结构的优缺点
优点 | 缺点 |
---|---|
无须为表示表中元素之间的逻辑关系而增加额外的存储空间 | 插入和删除操作需要移动大量元素 |
可以快速地存取表中任一位置的元素 | 当线性表长度变化较大时,难以确定存储空间的容量 |
造成存储空间的碎片 |