本文系在学习数据结构过程中做的笔记记录,主要参考书籍为《大话数据结构》《算法导论》,文中如有纰漏欢迎指正。
线性表(List):零个或者多个数据元素的有限序列
用数学语言可以表述如下:
若线性表记为 ( a 1 , ⋅ ⋅ ⋅ , a i − 1 , a i , a i + 1 , ⋅ ⋅ ⋅ , a n ) (a_1, ···, a_{i-1}, a_i, a_{i+1}, ··· , a_n) (a1,⋅⋅⋅,ai−1,ai,ai+1,⋅⋅⋅,an),则表中 a i − 1 a_{i-1} ai−1领先于 a i a_i ai, a i a_i ai领先于 a i + 1 a_{i+1} ai+1,称 a i − 1 a_{i-1} ai−1是 a i a_i ai的直接前驱元素, a i + 1 a_{i+1} ai+1是 a i a_i ai的直接后继元素。当 i = 1 , 2 , ⋅ ⋅ ⋅ , n − 1 i = 1 , 2 ,··· ,n-1 i=1,2,⋅⋅⋅,n−1时, a i a_i ai有且仅有一个直接后继,当 i = 2 , 3 , ⋅ ⋅ ⋅ , n i = 2 , 3 ,··· ,n i=2,3,⋅⋅⋅,n时, a i a_i ai有且仅有一个直接前驱。 如下图所示:
所以线性表元素的个数 n ( n ≥ 0 ) n(n≥0) n(n≥0)定义为线性表的长度,当 n = 0 n=0 n=0时,称为空表。
线性表的抽象数据类型可以定义如下:
ADT 线性表(List)
Data
operation:
InitList(*L) ://初始化操作,建立一个空的线性表L
ListEmpty(L) ://若线性表为空,返回true,否则返回false
ClearList(*L) ://将线性表清空
GetElem(L, i, *e) : //将线性表L中的第i个元素返回给e
LocateElem(L, e) : //在线性表中查找与指定元素e相等的元素,如果查找成功,返回元素在表中的序号;否则返回0表示失败
ListInsert(*L, i, e) : //在线性表L中的第i个位置插入新元素e
ListDelete(*L, i, e) : //删除线性表中第i个位置的元素,并用e返回其值
ListLeng(L) : //返回线性表L的元素个数
endADT
对于不同的应用,线性表的基本操作是不同的,上述操作是最基本的,对于实际问题中涉及的关于线性表的更复杂的操作,可以利用这些基本操作的组合来实现。
1-顺序存储的定义
线性表 ( a 1 , ⋅ ⋅ ⋅ , a i − 1 , a i , a i + 1 , ⋅ ⋅ ⋅ , a n ) (a_1, ···, a_{i-1}, a_i, a_{i+1}, ··· , a_n) (a1,⋅⋅⋅,ai−1,ai,ai+1,⋅⋅⋅,an)的顺序存储示意图如下:
2-顺序存储方式
因为线性表的每个数据元素的类型都相同,所以可以用一维数组来实现顺序存储结构,即把第一个数据元素存储到数组下标为0的位置中,接着把线性表相邻的元素存储在数组中的相邻的位置。
下面是线性表顺序存储结构代码:
#define MAXSIZE 20 //存储空间初始分配量
typedef int ElemType; //ElemType类型根据实际情况而定,这里假设为int
typedef struct
{
ElemType data[MAXSIZE];//数组存储数据元素,最大值为MAXSIZE
int length;//线性表当前长度
}Sqlist;
这里需要注意顺序存储结构的3个属性:
MAXSIZE
length
线性表的长度时线性表中数据元素的个数,随着线性表插入和删除操作的进行,这个量是可以变化的,在任何时刻,线性表的长度应该小于等于数组的长度
3-地址计算方法
用数组存储顺序表意味着要分配固定的长度的数组空间,由于线性表中可以进行插入和删除操作,因此分配的数组空间要大于当前线性表的长度。
假设一个元素占用 c c c个存储单元,那么线性表中第 i + 1 i+1 i+1个数据元素的存储位置和第 i i i个元素的存储位置满足如下关系( L O C LOC LOC表示获得存储位置的函数): L O C ( a i + 1 ) = L O C ( a i ) + c LOC(a_{i+1}) = LOC(a_i) + c LOC(ai+1)=LOC(ai)+c,所以对第 i i i个数据元素的存储位置可以用 a 1 a_1 a1推算出来: L O C ( a i + 1 ) = L O C ( a 1 ) + ( i − 1 ) ∗ c LOC(a_{i+1}) = LOC(a_1) + (i-1) * c LOC(ai+1)=LOC(a1)+(i−1)∗c
1-获得元素操作
对于线性存储结构来说,如果我们要实现GetElem
操作,即将线性表中第i
个位置的元素值返回,下面是实现代码:
#define OK 1
#define ERROE 0
typedef int Status;
//Status是函数的类型,其值是函数结果状态代码,如OK等
//初始条件:顺序表 L 已经存在,1 ≤ i ≤ListLength(L)
//操作结果: 用 e 返回L中的第 i 个元素的值
Status GetElem(Sqlist L, int i, ElemType *e)
{
if(L.length == 0 || i < 1 || i > L.length) return ERROR;
*e = L.data[i-1];
return OK;
}
2-插入操作
插入算法的思路:
实现代码如下:
//初始条件:顺序表 L 已经存在,1 ≤ i ≤ListLength(L)
//操作结果: 在L中第 i 个位置之前插入新的数据元素e, L的长度加 1
Status ListInsert(Sqlist *L, int i, ElemType e)
{
if(L->length == MAXSIZE) return ERROR;//顺序表已填满
if(i < 1 || i > L->length + 1) return ERROR;//i 不在范围内
if(i <= L->length){//插入数据不在表尾
for(int k = L->length - 1; k >= i-1; k--){//将要插入位置后的元素向后移动一位
L->data[k+1] = L->data[k];
}
}
L->data[i] = e;//新元素插入
L->length ++;
return OK;
}
3-删除操作
删除算法的思路:
实现代码如下:
//初始条件:顺序表 L 已经存在,1 ≤ i ≤ListLength(L)
//操作结果: 删除L中的第 i 个元素, 并用e返回其值,L的长度减 1
Status ListDelete(Sqlist *L, int i, ElemType *e)
{
if(L->length == 0) return ERROR;//顺序表为空
if(i < 1 || i > L->length) return ERROR;//删除位置不正确
*e = L->data[i - 1];
if(i < L->length){//如果删除不是最后位置
for(int k = i; k < L->length; k++){//将删除位置后继元素前移
L->data[k-1] = L->data[k];
}
}
L->length --;
return OK;
}
优点:
缺点:
PS:公众号上线啦,技术干货分享,欢迎关注。