线性表
线性表:零个或多个数据元素的有限序列。分为线性存储结构(顺序表)和链式存储结构(链表)。
1.线性表的定义
线性表(List)的定义:零个或多个数据元素的有限序列。
有两个值得注意的地方:
- 首先它是一个序列。第一个元素无前驱,最后一个元素无后继,中间元素只能出现存在一个前驱和一个后继的情况,不能出现一对多或多对一的情况。
- 线性表是有限的序列。
例1:12星座都是白羊座拍第一,双鱼座排第二,每个星座都有前驱和后继属于线性表。
例2:公司组织架构,一个总监理管理多个总监,每个总监管理几个经理,每个经理又有自己的下属员工,不符合只有一个前驱和一个后继的情况,所以不属于线性表。
2.线性表的抽象数据类型
线性表应有的操作:
- 重置线性表为空
- 获取表中的元素
- 查找某个元素是否存在线性表中
- 获取线性表的长度
- 在先行表中插入和删除数据
根据上面的操作要求,我们可以对线性表的抽象数据类型定义如下(用伪代码定义)。
ADT 线性表(SqList){
Data//数据
线性表的数据对象集合为{a1,a2,a3,...an},每个元素的类型为DataType。
operation{//操作
InitList(*L); //初始化操作,建立在一个空的线性表L
ListEmpty(L); //若线性表为空,返回true,否则返回false
ClearList(*L); //将线性表清空
GetElem(L,i,*e); //将线性表L中的第i个位置元素值返回给e
LocateElem(L,e); //在线性表中查找与给定值e相等的元素,找到为true
ListInsert(*L,i,e); //在线性表L的第i个位置插入元素e。
ListDelete(*L,i,*e);//删除线性表中L中的第i个未知元素,并用e返回其值
ListLength(L); //返回线性表L的元素个数
}
};
例如:实现集合A和B的并集操作,只需要循环B的元素如果在A中则插入集合A中,代码如下:
//将所有的在线性表Lb中但不在La中的元素插入到La中
void union(List*La,List Lb){
Lena=ListLength(La);
for(int i=0;i<ListLength(Lb);i++){
GetElem(Lb,i,*e);
if(!LocateElem(La,e)){
ListInsert(*La,++Lena,e);
}
}
}
3.线性表的顺序存储结构
3.1顺序存储结构定义
线性表的顺序存储结构指的是用一段地址连续的存储单元依次存储线性表的数据元素。
3.2顺序存储方式
顺序存储结构是先在内存中开辟了一条连续的空间,通过占位的形式将相同类型的数据类型元素以此放到这块内存中。
在C++中通常用数组来实现顺序存储结构,为了建立一个顺序存储结构的线性表,要在内存中先找一块空间,于是这块空间的第一个位置就非常重要,是存储空间的起始位置。
下面是线性表的顺序存储的结构代码:
#define MAXSIZE 20; //存储空间初始分配量
typedef int ElemType; //类型别名,ElemType的类型视情况而言,这里类型int
typedef struct{
ElemType data[MAXSIZE]; //数组
int length; //线性表当前长度
}SqList;
可以总结出来顺序存储结构需要三个属性:
- 存储空间的起始位置: 数组data[MAXSIZE],它的存储空间起始位置就是data
- 线性表的最大存储容量: 数组长度MAXSIZE
- 线性表的当前长度: length
3.3数据长度与线性表长度的区别
“数组的长度” 和 “线性表的长度” 这两个概念需要区分一下:
- 数组的长度:存放线性表空间的长度,存储分配后这个量一般是**不变**的,上面的MAXSIZE就是数组的长度。
- 线性表的长度:线性表的长度是线性表中元素的个数, 这个量是随着线性表的插入和删除而 变化 的。上面的lenght就是线性表的长度。
任意时刻,线性表的长度应该小于等于数组的长度。
3.4地址计算方法
线性表的起始元素为1,但是数组的第一个元素的下标为0。因此,线性表的序号和它的数组下标之间存在对应关系为:线性表第i个元素存储在数组下标为i-1的位置。
存储器中的每个存储单元都有自己的编号,这个编号成为**地址,当我们顺序存储结构的第一个元素地址确定后,由于每个元素类型一样所占的空间是固定的,设为c,那么线性表中任意位置i的元素的存储位置就可以推算得出:(LOC表示或的存储位置的函数)
L
O
C
(
A
i
)
=
L
O
C
(
A
1
)
+
(
i
−
1
)
×
c
LOC(Ai)=LOC(A1)+(i-1)×c
LOC(Ai)=LOC(A1)+(i−1)×c
通过上面公式,我们知道首地址后,可以通过这个公式算出线性表中任意位置的地址,即我们对线性表位置存入或取出数据对计算机来说都是相等的时间,用时间复杂度的概念来说就是O(1)。因此,我们把具有这一特点的存储结构成为随机存取结构(随机访问结构)。**
3.5顺序存储结构的插入与删除
3.5.1获得元素操作(读取操作)
要实现GetElem操作,即将线性表第i个元素返回,只要i的数值在数组下表范围内,就是把数组第i-1下标的值返回即可。
GetElem伪代码代码如下:
#define OK 1
#define ERROR 0
typedef int ElemType
typedef int Status //Status是ERROR或OK的值即0或1
Status GetElem(SqList L,int i,ElemType *e){
if(L.Length==0||i<=0||i>L.Lenght){
//线性表长度为0或者i超出数组下标范围返回ERROR
return ERROR;
}else{
*e=L.data[i-1]; //第i个元素对应数组的下标为i-1
return OK;
}
}
获得元素的操作的时间复杂度为O(1)。
3.5.2插入操作
插入操作将一个元素插入到第i个元素之后,第i个元素后面的元素必须都往后移动。
插入操作时间复杂度为O(n).
插入算法的思路:
- 如果插入位置不合理,抛出异常(插入的元素必须在数组下标范围内)
- 若果线性表长度大于等于数组长度,则抛出异常或动态增加容量。(由于顺序存储结构容量一开始就固定了,如果线性表长度已经大于等于数组长度,则没有空间继续插入新的元素)
- 从最后一个元素开始向前遍历到第i个位置,分别将它们都往后移动一个位置。
- 将要插入的元素填入位置i处
- 表长加一(不要忘了)
代码如下:
//在顺序存储的线性表L的第i个位置插入元素e
Status ListInsert(SqList*L,int i,ElemType e){
if(i<0||i>L->length+1){ //如果插入位置不合理,抛出异常
return ERROR;
}
if(L->length==MAXSIZE){//如果线性表长度大于等于数组长度,抛出异常
return ERROR;
} else{
for(int k=L->length;k>=i-1;k--){
L->data[k]=L->data[k-1];
}
L->data[i-1]=e;
//表长+1
L->length++;
return OK;
}
3.5.3删除操作
删除操作是将从第i个元素开始,后面的每一个元素覆盖前一个元素。
删除操作的时间复杂度为O(n).
删除算法的思路:
- 如果删除位置不合理,抛出异常(删除的元素不在数组下标范围内)
- 取出删除的元素
- 从删除元素位置开始遍历到最后一个元素位置,分别将它们都向前移动一个位置
- 表长减一(不要忘了)
代码如下:
//在顺序存储的线性列表的第i个位置删除元素
Status ListDelete(SqList*L,int i){
if(L->length==0){
return ERROR
}
if(i<1||i>L->length){
return ERROR
}else{
for(int k=i-1;k<L->length-1;k++){
L->data[k]=L->data[k+1];
}
L->length--;
return OK;
}
}
3.5.4线性表顺序存储结构的优缺点
首先我们来分析一下,插入和删除的时间复杂度。
- 最好的情况:元素插入到最后一个位置或者删除最后一个元素时间复杂度为O(1).
- 最差的情况:元素插入或删除第一个位置,此时时间复杂度为O(n).
- 平均情况:平均时间复杂度为O(n-1/2).
综上,不管是最坏情况下还是平均情况下的时间复杂度(主要还是最坏情况下的)为O(n).
接来下我们可以总结出存储结构的优缺点了:
- 优点:无需为表示表中元素之间的逻辑关系而增加额外的存储空间、可以快速地存取表中任一位置的元素
- 缺点:插入和删除操作需要移动大量元素、当线性表长度变化较大时,难以确定存储空间的容量


被折叠的 条评论
为什么被折叠?



