数据结构与算法分析~笔记2线性表

线性表是一种典型的线性结构。

2.1 线性表的基本概念

线性表(Linear List)是由n(n≥0)个具有相同类型的数据元素a1,a2,…,an组成的有限序列。其中数据元素的个数n定义为表的长度。当n=0时,称为空表,常将非空的线性表(n>0)记作:
L=(a1,a2,…,ai-1,ai,ai+1,…,an-1,an)
其中,ai(1≤i≤n)为线性表的第i个数据元素,称i为数据元素ai在线性表中的位序。表中所有数据元素具有相同特性,属于同一数据对象,即ai(1≤i≤n)具有相同的数据类型,相邻数据元素之间存在着序偶关系。
在稍微复杂的线性表中,一个数据元素可以由若干数据项(Item)组成,在这种情况下,通常把数据元素称为记录(Record),含有大量记录的线性表又称为文件(File)。
非空的线性表的逻辑特征如下。
(1)有且仅有一个开始结点a1,该结点没有前趋,仅有一个后继a2。
(2)有且仅有一个终端结点an,该结点没有后继,仅有一个前趋an-1。
(3)其余的内部结点ai(2≤i≤n-1)都有且仅有一个前趋ai-1和一个后继ai+1。
数据的运算是定义在逻辑结构上的,而运算的具体实现则是在存储结构上进行的。
线性表的抽象数据类型定义如下:
ADT List{
数据对象:D={ai|ai∈ElemSet,i=1,2,…,n,n≥0}
数据关系:R={<ai-1,ai>|ai-1,ai∈D,i=2,3,…,n}
基本操作:
InitList(&L)  //1初始条件:无。操作结果:构造一个空的线性表L,也称为初始化线性表。
DestroyList(&L)   //2初始条件:线性表L已存在。操作结果:销毁线性表L,即释放L所占用的存储空间。
ClearList(&L)  //3初始条件:线性表L已存在。操作结果:将L重置为空表。
ListEmpty(L)  //4初始条件:线性表L已存在。操作结果:若L为空表,则返回TRUE,否则返回FALSE。
ListLength(L)  //5初始条件:线性表L已存在。操作结果:返回L中数据元素的个数,即表的长度。
GetElem(L,i,&e)  //6初始条件:线性表L已存在,且1≤i≤ListLength(L)。操作结果:用e返回L中第i个数据元素的值。
LocateElem(L,e,compare())  //7初始条件:线性表L已存在,e为给定值,compare()是数据元素判定函数。操作结果:返回L中第1个与e满足关系compare()的数据元素的位序。若这样的数据元素不存在,则返回值为0。
PriorElem(L,cur_e,&pre_e)  //8初始条件:线性表L已存在。操作结果:若cur_e是L的数据元素,且有前驱,则用pre_e返回它的前驱,如果没有前驱,则操作失败,pre_e无定义。
NextElem(L,cur_e,&next_e)  //9初始条件:线性表L已存在。操作结果:若cur_e是L的数据元素,且有后继,则用next_e返回它的后继,如果没有后继,则操作失败,next_e无定义。
ListInsert(&L,i,e)   //10初始条件:线性表L已存在,1≤i≤ListLength(L)+1。操作结果:在L的第i个位置插入新的数据元素e,L的长度加1。
ListDelete(&L,i,&e)  //11初始条件:线性表L已存在,1≤i≤ListLength(L)。操作结果:删除L的第i个数据元素,并用e返回其值,L的长度减1。
ListTraverse(L,visit())  //12初始条件:线性表L已存在,visit()为对数据元素的访问函数。操作结果:依次对L中的每个数据元素调用函数visit()。一旦visit()失败,则操作失败。
}ADT List
对于ADT List中的基本操作,有如下两点说明。(1)类型的基本操作可以不同。在某些应用中,基本操作可能只是上述12个操作的一部分,但有些可能还需补充其他操作。(2)在具体应用中,即使某个基本操作的功能相同,但具体实现也可以不同。

2.2 线性表的顺序存储结构

线性表有两种常用的存储表示方法:顺序存储表示和链式存储表示。
线性表的顺序存储指的是把线性表的数据元素按逻辑顺序依次存放在一组地址连续的存储单元里,用这种方法存储的线性表简称为顺序表(Sequential List)。顺序表的特点是,表中逻辑上相邻的数据元素,存储时在物理位置上也一定相邻。
假设顺序表的每个数据元素占用m个存储单元,且数据元素的存储位置定义为其所占的存储空间中第一个单元的存储地址。若顺序表的起始位置或基地址是LOC(a1)(即表中第一个数据元素a1的存储位置),那么顺序表的第i个数据元素ai的存储位置为:LOC(ai)=LOC(a1)+(i-1)×m
由于计算任一数据元素存储地址的时间都是相等的,因此顺序表是一种随机存取(Random Access)结构。
线性表的顺序存储结构示意图:
在这里插入图片描述其中,b=LOC(a1),即线性表中第一个数据元素的存储地址,length为线性表的长度,MaxSize则表示数组的容量,即数组中最多可存储的数据元素的个数。
在高级语言中,一维数据也具有和顺序表相同的如下3个特性:
(1)一维数组的存储对象也是一组相同类型的数据;
(2)一维数组也是用一组地址连续的存储单元存放数据;
(3)一维数组中的数据元素也可以通过数组下标随机存取。
因此,可以用数组类型来描述顺序表。
C++语言中,一维数组的定义有两种方式。
方式一:<数据类型><数组名> [<常量表达式>];例如:int a[5]; float x[100];采用方式一定义的数组,数组的容量是确定的(不能扩充)。
方式二:<数据类型> *<指针变量> = new <数据类型>[<常量表达式>];例如:int *p = new int[3];其中,int *p = new int[3]; 也可以分两步实现:(1)int *p;(2)p = new int[3];采用方式二定义的数组,由于其采用指针来表示数组,因此存储空间可以动态分配,即指针所表示的数组空间容量可以变化,这样更符合线性表长度可变的实际情况。
顺序表的特点是:逻辑关系上相邻的两个数据元素在物理位置上也相邻。
顺序表的优点如下:
(1)节省存储空间。
(2)随机存取。
顺序表的缺点如下:
(1)插入和删除操作需移动大量数据元素,平均需要移动表中一半的数据元素。
(2)表容量。由于逻辑关系上相邻的两个数据元素在物理位置上也相邻,因此顺序表要求占用连续的空间。

2.3 线性表的链式存储结构

线性表的链式存储结构是指用一组地址任意的存储单元来依次存放线性表中的数据元素,这组存储单元既可以是连续的,也可以是不连续的,甚至可以零散分布在内存中的任意位置上。因此,链式存储结构中的数据元素的逻辑次序和物理次序不一定相同。
在线性表的链式存储结构中,为了表示数据元素之间的逻辑关系,对于每个数据元素ai来说,除了存储其结点本身的信息外,还需存储指示其前驱或后继结点的信息。因此,链式存储结构中的每个数据结点都需要保存以下两部分信息:
(1)存储数据元素自身信息的部分,称为数据域;
(2)存储与前驱或后继结点的逻辑关系,称为指针域。
链式存储结构中数据元素间的逻辑顺序便可以通过链表中的指针链接次序实现。
根据结点中指针域存储的指针个数和类型的不同,线性表的链式存储还可细分为单链表(只有一个指针)、静态链表、双向链表(两个指针)以及循环链表。
单链表
在链式存储结构中,如果结点只包含一个指针域,则称该线性表为单链表(Singly Linked List)。单链表的结点结构如下:
在这里插入图片描述
其中,data为数据域,用来存储数据元素自身的信息;next为指针域(也称链域),用来存放结点的后继结点的地址。
单链表中每个结点的存储地址是存放在其前驱结点的next域中,而表中的第一个结点a1无前驱,故应设置一个头指针(Head Pointer)head指向a1。此外,由于最后一个结点an无后继,故an的指针域为空,即NULL(在图示中常用符号“ʌ”表示)。单链表的结构示意图如图所示。
在这里插入图片描述
在链式存储结构中,逻辑上相邻的两个数据元素其存储的物理位置不一定相邻,因此,这种存储结构称为非顺序映像链式映像
为了操作方便,通常在单链表的开始结点之前附设一个类型相同的结点,称为头结点(Head Node)。头结点的数据域可以不存储任何信息,也可以存储表长等信息。具有头结点的单链表如图所示。
在这里插入图片描述
加上头结点之后的单链表具有如下特点:
(1)在进行插入删除等操作时,表中第一个结点和在表的其他位置上的结点操作一致,无需进行特殊处理;
(2)无论单链表是否为空,头指针始终指向头结点。因此,空表和非空表的处理统一,无需进行区别处理。
下面讨论单链表基本操作的实现。
(1)创建一个单链表
链表的创建是指在初始化后创建具有n个数据元素的单链表。由于链表是一个动态的结构,它不需要预分配空间,因此创建链表的过程是一个结点“逐个插入”的过程。创建链表的常用方法有两种:头插入法和尾插入法。
① 头插入法该方法从一个空表开始,重复读入数据,生成新结点,将读入数据存放到新结点的数据域中,然后将新结点插入到当前链表的表头上,直到读入结束标志(比如$)为止。
② 尾插入法为了实现创建链表过程中结点的输入顺序与结点实际的逻辑次序相同,可采用尾插入法。尾插入法每次将新生成的结点插入到当前链表的表尾上。为此需增加一个尾指针r,始终指向当前链表的尾结点。
(2)查找操作
① 按位序查找
链式存储的线性表,位序上相邻数据元素在存储位置上不一定相邻,因此,即使知道被访问结点的序号i,也不能像顺序表那样按照位序进行直接访问。在单链表中,只能从链表的头指针出发,沿next域逐个结点往下搜索,直到搜索到第i个结点为止。因此,链表不是随机存取结构。等概率情况下,平均时间复杂度为O(n)。
② 按值查找
按值查找是在链表中查找是否有结点值等于给定值key的结点,若有,则返回首次找到的值为key的结点的存储位置;否则返回NULL。查找过程从开始结点出发,顺着链表逐个将结点的值和给定值key作比较。等概率情况下,平均时间复杂度为O(n)。
(3)插入操作
单链表中插入操作是将值为e的新结点s插入到单链表的第i个结点的位置上,即插入到ai-1和ai之间。算法的时间主要耗费在查找正确的插入位置,故时间复杂度为O(n)。
(4)删除操作
单链表的删除操作是将单链表的第i个结点删去,即改变ai-1、ai与ai+1之间的链接关系。删除算法的时间主要耗费在查找正确的删除位置上,故时间复杂度为O(n)。
静态链表
静态链表(Static Linked List)是指用一维数组表示的单链表。在静态链表中,用数据元素在数组中的下标作为单链表的指针。静态链表的特点如下。
(1)静态的含义是指静态链表采用一维数组表示,表的容量是一定的,因此称为静态。
(2)静态链表中结点的指针域next存放的是其后继结点在数组中的位置(即数组下标)。
静态链表的每个数组数据元素由两个域构成:data:数据域,用来存储数据元素自身的信息;next:游标域,用来存储数据元素的后继结点的在数组中的位置。
静态链表与单链表表示的比较示意图:
在这里插入图片描述
静态链表需要预先分配一个较大的存储空间,但在进行线性表的插入和删除操作时不需要移动数据元素,仅需修改指针,故仍具有链式存储结构的主要优点。
虽然静态链表采用一维数组实现,但它的插入和删除操作与前述单链表的操作方法相同。
为了在插入时快速找到可用空闲单元,通常把链表中的所有空闲单元也组成一个链,称为空闲链,并设一指针avail指向其头位置。通常称h是静态链表的头指针,avail是空闲链的头指针。
静态链表虽然用数组来存储线性表,但因增加了空闲链,使得数据元素无需顺序存放,在插入和删除操作时,只需修改下标,不需要移动表中的数据元素,从而改进了顺序表中插入和删除操作需要大量移动数据元素的缺点,但它没有解决连续存储分配带来的表长度难以确定的问题。
循环链表
循环链表(Circular Linked List)是一种头尾相接的链表。其特点是无需增加存储量,仅对表的链接方式稍作改变,即可使得表处理更加方便灵活。
单循环链表(Single Circular Linked List)是指在单链表中,将终端结点的指针域NULL改为指向表头结点或开始结点,得到的单链形式的循环链表,并简称为单循环链表。从单循环链表中任意结点出发均可找到表中其他结点。类似的,还可以有多重链的循环链表(Multiple Circular Linked List)。为了使空表和非空表的处理一致,循环链表中也可设置一个头结点。这样,空单循环链表仅有一个自成循环的头结点表示,空单循环链表、非空单循环链表则如图所示。
在这里插入图片描述
在用头指针表示的单循环链表中,找到开始结点a1的时间复杂度是O(1),然而,要找到尾结点an,则需从头指针开始遍历整个链表,其时间复杂度是O(n)。
在很多实际问题中,表的操作常常是在表的尾位置上进行,此时头指针表示的单循环链表就显得不太方便。为提高此类应用的效率,可改用尾指针rear来表示单循环链表,则查找开始结点a1和尾结点an都将很方便。用尾指针表示的单循环链表如图所示。此时,头结点的地址是rear→next→next,尾结点的地址是rear,显然,查找头结点和尾结点的时间复杂度都是O(1)。
在这里插入图片描述
双向链表
在单链表的每个结点中再设置一个指向其前驱结点的指针域,这样形成的链表中有两个方向不同的链,故称为双向链表(Double Linked List),简称双链表,其结点结构如图所示。
在这里插入图片描述
其中:data:数据域,用来存储数据元素自身的信息。prior:前驱指针域,存放该结点的前驱结点的地址。next:后继指针域,存放该结点的后继结点的地址。
和单链表类似,双链表一般也是由头指针惟一确定,增加头结点也能使双链表的某些操作变得方便,将头结点和尾结点链接起来也能构成双循环链表,这样,无论是插入还是删除操作,对链表中的开始结点、尾结点和中间任意结点的操作过程相同。实际应用中常采用带头结点的双循环链表,如图所示。
在这里插入图片描述
设指针p指向双循环链表中的某一结点,则双循环链表具有如下的对称性:p→prior→next=p=p→next→prior
即结点p的存储地址既存放在其前驱结点的后继指针域中,也存放在其后继结点的前驱指针域中。
在双循环链表中求表长、按序号查找等操作的实现与单链表基本相同,不同的只是插入和删除操作的实现。由于双循环链表是一种对称结构,因此对其进行插入和删除操作都很容易实现。
(1)插入操作在结点p前插入一个新结点s,需要修改4个指针:
① s→prior=p→prior;
② p→prior→next=s;
③ s→next=p;
④ p→prior=s;
注意指针修改的相对顺序。在修改第①和第②步的指针时,要用到p→prior以找到p的前驱结点,所以第④步指针的修改要在第①和第②步的指针修改完成后才能进行,如图所示:
在这里插入图片描述(2)删除操作设指针p指向待删除结点,删除操作可通过下述3条语句完成:
①p→prior→next=p→next;
②p→next→prior=p→prior;
③delete p;
①、②语句的顺序可以颠倒。另外,虽然执行上述语句后结点p的两个指针域仍指向其前驱结点和后继结点,但在双链表中已经找不到结点p。而且,执行完删除操作后,还要将结点p所占的存储空间释放,如图所示。
在这里插入图片描述

  • 13
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值