第二章 线性表(2.1-2.4)
第二章 线性表
2.1线性表的定义和特点
1、他们的数据元素虽然不同,但是同意线性表中的元素必定是具有相同的特性,即属于同一数据对象,相邻数据元素之间存在着序偶关系
2、对于非空的线性表或阿这线性结构,其特点:
- 存在唯一的一个被称作"第一个"的数据元素;
- 存在唯一的一个被称作“最后一个”的数据元素;
- 除第一个之外,结构中的每个数据元素均只有一个前驱;
- 除最后一个之外,结构中的每个数据元素均只有一个后驱;
2.2案例引入
案例2.:1:一元多项式的运算
- 实现两个多项式相关运算的前提是如何在计算机中有效的表示一元多项式,进而在此基础上设计相关的运算的算法
可以看出,一元多项式可由n+1个系数唯一确定,因此将一元多项式pn(x)可用线性表R表示:
- 在后面的叙述中将看到,对此类多项式的线性表只需要用数组表示的顺序存储结构便很容易实现上述算法
- 然而,在通常的应用中,多项式的次数可能很高且变化很大,这种所谓的稀疏多项式如果采用上述方法,将使得线性表中出现很多的零元素。
案例2.2:稀疏多项式的运算
- 由于线性表的元素可以包含多个数据项,由此改变元素设定,对多项式的每一项,可用(系数,指数)唯一确定,这样表示将大大节省空间。
案例2.3:图书管理系统
- 要实现上述功能,与以上案例中的多项式一样,我们首先根据图书标的特点将其抽象成一个数据表,每本图书作为线性表中的一个元素,然后可以采用适当的存储结构来表示该线性表,在此基础上设计完成有关的功能算法
2.3线性表的类型定义
1.线性表的抽象数据类型定义:
ADT List{
数据对象:D={ai|ai=ElemSet,i=1,2,..,n,n≥0}
数据关系:R1={<ai-1,ai>|ai-1,ai∈D,i=2,...,n}
基本操作:
IniList(&L)操作结果:构造一个新的线性表L。
DestroyList(&L)操作结果:销毁线性表
ClearList(&L)操作结果:将L重置为空表
ListEmpty(L)操作结果:若L为空表,则返回TURE,否则返回FALSE
ListLength(L)操作结果:返回L中数据元素个数
GetElem(L,i,&e)初始条件:线性表已存在,1≤i≤ListLength(L)
操作结果:用e返回L中第i个数据元素的值
LocateElem(L,e,compare())操作结果:返回L中第一个与e满足关系compare()的数据元素的位序。若这样的数据元素不存在,则返回结果为0.
PriorElem(L,cur_e,&pre_e)若cur-e是L的数据元素,且不是第一个,则用pre_e返回它的前驱,否则操作失败,pre_e无定义
NextElem(L,cur_e,&text_e)若cur_e是L的数据元素,且不是最后一个,则用next_e返回它的后继,否则操作失败,next_e无定义
ListInsert(&L,i,e)初始条件:线性表已存在,1≤i≤ListLength(L)+1
操作结果:在L中第i个位置之前插入新的数据元素e,L的长度加1
ListDelete(&L,i,&e)初始条件: 线性表存在且非空,1≤i≤ListLength(L)
操作结果:删除L的第I个数据元素,并用e返回其值,L的长度减一
ListTravarse(L,visit())操作结果:依次对L的每个数据元素调用函数visit().一旦visit()失败,则操作失败。
}ADT List
2.举例:
实现两个线性集合A和B的并集(union)操作后成为新集合A,使得 A = A ∪ B A=A \cup B A=A∪B
1、思路分析:
遍历集合B中每个数据元素,判断当前数据元素是否出现在集合A中(LocateElem)
如果已存在,不进行操作(不写任何语句)
如果未存在,则将此时集合B中的当前数据元素添加到集合A中(ListInsert)
//集合A需要出现变动,所以使用指针(指向原数据元素,此函数对A中元素的操作是对其A中数据元素本身进行操作)
//集合B不需要出现变动,所以用普通参数(原数据元素的拷贝,此函数对B中元素的操作是对B数据元素拷贝值进行操作)
void union(SqList *La,SqList Lb){
int La_len,Lb_len; //定义线性表的长度
ElemType e; //数据元素的类型,typedef int ElemType; ElemType是int的类型别名
La_len = ListLength(*La); //求线性表A的长度
Lb_len = ListLength(Lb); //求线性表A的长度
for(int i = 1; i <= Lb_len; i++){ //循环遍历表B
GetElem(Lb,i,&e); //获取当前B中数据元素
if(!LocateElem(La,e)) //B的当前数据元素没有在A中出现过
//ListInsert(&La,i,e) //在表La中第i个位置之前插入新数据元素e
ListInsert(&La,++La_len,e); //将新数据元素插入到表A的末尾
}
}———————————————
2、总结
当你传递一个参数给函数时,这个参数会不会在函数内被改动决定了使用参数的类型
如果需要被改动,则需要传递指向此参数的指针
(指针指向原值,函数对参数的操作就是对原值的操作)
如果不需要被改动,则直接传递此参数
(直接传递相当于对原值进行了拷贝,函数中对参数的操作就是对原值拷贝的操作
2.4线性表的顺序表示和实现
2.4.1 线性表的顺序存储表示
1、顺序表的定义
线性表的顺序存储又称为顺序表。它是用一组地址连续的存储单元,依次存储线性表中的数据元素,
从而使得逻辑上相邻的两个元素在物理位置上也相邻。第 1 个元素存储在线性表的起始位置,第 i 个元素
元素的存储位置后面紧接着存储的是第 i + 1 个元素。因此,顺序表的特点是表中元素的逻辑顺序与其物理
顺序相同
2.顺序表的概念
顺序表分为:
1.静态顺序表
2.动态顺序表
顺序结构------将表中元素一个一个的存入一组连续的存储单元中
顺序表--------采用顺序结构的线性表
3、静态顺序表和动态顺序表的区别
相同点:内存连续,数据顺序存储
不同点:所占内存空间位置不同,静态是在函数栈上,随着函数调用的结束自动被系统收回,而 动态顺序表所占内存空间在堆的内存上,不会随着函数的结束而被释放,需要用户主动去释放;
4、顺序表的优缺点
静态顺序表:操作简单,不需要用户主动去释放,所以也就不存在内存泄露;
动态顺序表:可以动态开辟内存空间,操作灵活,避免造成资源浪费;
5.顺序存储
假设线性表 L 存储的起始位置为 LOC(A) ,sizeof(ElemType) 是每个数据元素所占用存储空间的大小,
则表 L 所对应的顺序存储如下图:
注意:线性表中的位序是从 1 开始的,而数组中元素的下标是从 0 开始的。
-----------------------------------------------------------------------------------------------------
假定线性表的元素类型为 ElemType ,线性表的顺序存储类型描述为:
# define MaxSize 50 // 定义线性表的最大长度
typedef struct{
ElemType data[MaxSize] ; // 顺序表的元素
int length ; // 顺序表的当前长度
} SqList ; // 顺序表的类型定义
- 一维数组可以是静态分配的,也可以是动态分配的。在静态分配时,由于数组大小和空间事先已经固定,一旦空间占满,再加入新的数据将产生溢出,就会导致程序崩溃。
- 而动态分配时,存储数据的空间是在程序执行过程中通过动态分配语句分配的,一旦数据空间占满,可以另外开辟一块更大的存储空间,用以替换原来的存储空间,从而达到扩充存 数组空间的目的,而不需要一次性地划分所有所需要空间给线性表。
2.4.2顺序表中基本操作的实现
1.初始化
为顺序表L动态分配一个预定义大小的数组空间,使 data 指向这段空间的基地址。将表的当前长度设为0。
bool InitList(SqList &L){
//本算法用于实现初始化顺序表L
L.data = new ElemType[MaxSize]; //为顺序表分配一个大小为MaxSize的数组空间
if(!L.data)
exit(OVERFLOW); //存储分配失败退出
l.length = 0; //表长度为0
return true;
}
2.取值
取顺序表L的第1 <= i <= L.length 个位置元素值,并用e返回。若i的输入不合法,则返回 false ,表示取值失败。
bool GetElem(SqList L, int i, ElemType e){
//本算法用于实现获取顺序表L中第i个元素的值
if(i <= 1||i >= L.length) //判断i值是否合理
return false;
e = L.data[i-1];
return true;
}
3.插入操作
在顺序表L的第 1 <= i <= L.length+1 个位置插入新元素e。若i的输入不合法,则返回 false ,表示插入失败;否则,将顺序表的第i个元素及其后的所有元素右移一个位置,腾出一个空位置插入新的元素e,顺序表长度加1,插入成功,返回 true 。
bool ListInsert(SqList &L, int i, ElemType e){
//本算法实现将元素e插入到顺序表L中第i个位置
if(i < 1||i > L.length+1) //判断i的范围是否有效
return false;
if(L.length >= MaxSize) //判断是否超出数组的最大范围
return false;
for(int j = L.length; j >= i; j--) //将i个元素及之后的元素后移
L.data[j] = L.data[j-1];
L.data[i-1] = e; //在i位置处插入e
L.length++; //线性表长度加1
return true;
}
最好情况:在表位插入,时间复杂度为O(1).
最坏情况:在表头插入,时间复杂度为O(n).
平均情况:假设是在第i个位置上插入一个结点的概率,则在长度为 n 的线性表中插入一个结点时,所需移动结点的平均次数为O((1+n)/2)
因此,线性表插入算法的时间复杂度为 O(n).
4.删除操作
顺序表L的第 1 <= i <= L.length 个位置元素,若成功则返回 true,并将被删除的元素用引用变量e返回,否则返回 false 。
bool ListDlelete(SqList &L, int i, ElemType &e){
//本算法实现删除顺序表L中第i个位置的元素
if(i < 1||i > L.length) //判断i的范围是否有效
return false;
e = L.data[i-1]; //将被删除的元素赋值给e
for(int j = i; j < L.length; j++) //将第i个位置后的元素前移
L.data[j-1] = L.data[j];
L.length--; //线性表的长度减1
return true;
}
最好情况:删除表尾元素,时间复杂度为O(1).
最坏情况:删除表头元素,时间复杂度为O(n).
平均情况:假设是删除第i个位置上结点的概率,则在长度为 n 的线性表中删除一个结点时,所需的时间复杂度为 O(n).
5、按值查找(顺序查找)
在顺序表L中查找第一个元素值等于e的元素,并返回其次序。
int LocateElem(SqList L, ElemType e){
//本算法实现查找顺序表中值为e的元素,如果查找成功,返回元素位序,否则返回0
int i;
for(i = 0; i<L.length; i++)
if(e == L.data[i])
return i+1; //下标为i的元素值等于e,返回其位序i+1
return 0;
}
最好情况:查找的元素在表头,时间复杂度为O(1).
最坏情况:查找的元素在表尾,时间复杂度为O(n).
平均情况:假设是查找的元素在第 1 <= i <= L.length 个位置上的概率,则在长度为 n 的线性表中查找值为e的元素所需比较的平均次数为(n+1)/2
因此,线性表查找算法的时间复杂度为 O(n).
2.4.3 总结
顺序表可以随机存取表中的任一元素,其存储位置可以用一个简单、直观的公式来表现。然而,从另一方面来看,这个特点也造成了这种存储结构的缺点:在做插入或者删除操作的时候,需要移动大量元素。另外由于数组有长度相对固定的静态特性,当表中的元素个数较多且变化较大时,操作过程相对复杂,必然导致存储空间的浪费。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· winform 绘制太阳,地球,月球 运作规律
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· AI 智能体引爆开源社区「GitHub 热点速览」
· Manus的开源复刻OpenManus初探
· 写一个简单的SQL生成工具