线性结构
● 定义: 线性表是一个线性结构,它是一个含有n≥0个结点的有限序列,对于其中的结点,有且仅有一个开始结点没有前驱但有一个后继结点,有且仅有一个终端结点没有后继但有一个前驱结点,其它的结点都有且仅有一个前驱和一个后继结点。
线性表是最常用且简单的一种数据结构。
一个线性表是n个数据元素的有限序列。
线性表中的数据元素可以是各种各样的,但同一线性表中的元素必定具有相同特性。
在复杂的线性表中,一个数据元素可以由若干个数据项组成。这种情况下,常把数据元素称为记录。
而含大量记录的线性表称为文件。
在数据元素的非空有限集中,线性结构特点:
a. 存在唯一的一个被称作“第一个”的数据元素
b. 存在唯一的一个被称作“最后一个”的数据元素
c. 除第一个之外,集合中的每个数据元素均只有一个前驱
d. 除最后一个之外,集合中每个数据元素均只有一个后继
在上图中,a1是a2的前驱,ai+1 是ai的后继,a1没有前驱,an没有后继 ,n为线性表的长度 ,若n==0时,线性表为空表 ,下图就是一个标准的线性表
线性表分为如下几种:
顺序表
● 线性表顺序存储的概念:指的是在内存中用一段地址连续的存储单元依次存储线性表中的数据元素。
● 顺序存储方式线性表
顺序存储方式线性表中,存储位置连续,可以很方便计算各个元素的地址
如每个元素占C个存储单元,那么有: Loc(An) = Loc(An-1) + C,于是有: Loc(An) = Loc(A1)+(i-1)*C;
优点:
查询很快 ,时间复杂度O(1)
可以快速地取出表中任意位置的元素,时间复杂度O(1)
开始存数据的时候很快,不需要为表中元素之间的逻辑关系而增加额外的内存空间,时间复杂度O(1)
缺点:插入和删除效率慢,需要移动大量元素,时间复杂度O(n)。
当顺序表长度变化较大时,难以确定存储空间的内容。
造成存储空间的碎片
另外一点:顺序存储要求明确数据的规模用来分配给数据空间。
● 顺序表中还有一个最好的情况和最坏的情况, 最好的情况是:如果要插入或者删除的位置是最后一个,那么时间复杂度O(1),不需要移动任何元素。
最坏的情况是,如果要删除或者插入的元素是第一个呢,时间复杂度O(n)。
这就说明顺序表更适合元素个数不太变化,而更多是存取数据的应用。
● 采用的实现方式:一段地址连续的存储单元可以用固定数组或者动态存储结构来实现,这里采用动态分配存储结构。
单链表
● 线性表的链式存储结构的特点:是用一组任意的存储单元存储线性表的数据元素,这组存储单元可以是连续的,也可以是不连续的 。
我们把存储数据元素信息的域称为数据域,把存储直接后继位置的域称为指针域。 指针域中存储的信息称做指针或链。 这两部分组成数据元素ai 的存储映像,称为结点。
头指针 :就是链表中的第一个结点的存储位置叫做头指针,那么整个链表的存取就必须是从头指针开始进行的。
为了更方便地对链表进行操作,如删除第一个结点的特殊情况(第一个结点没有前驱,而要摘除一个结点需要首先找到它的前驱才能做摘除操作),经常在单链表的第一个结点前附设一个结点,称为头节点,这样头指针就指向了头结点。
头指针和头结点的异同
● 头指针是指链表指向第一个结点的指针,若链表有头结点, 则是指向头结点的指针。
● 头指针具有标识作用,所以常用头指针冠以链表的名字
● 无论链表是否为空,头指针均不为空。 头指针是链表的必要元素。
●头结点是为了操作的统一和方便而设立的,放在第一个元素的结点之前,其数据域一般无意义(也可存放链表的长度)。
● 有了头结点,对第一个元素结点前插入结点和删除第一结点,其操作与其它结点的操作就统一了。
● 指针变量和结点变量
● 指针变量p和结点变量*p的关系
指针变量P | 结点变量*P | |
定义 | 在变量说明部分显示定义 | 在指针变量P使用时,通过标准的new生成 |
取值 | 结点的地址 | 结点各个域的内容 |
操作方式 | 指针变量名访问 | 通过*取值运算符访问 |
● 结点域的访问
- (*p).data和(*p).next
- p->data 和 p->next
● 指针变量p和结点变量*p的关系
- 指针变量p的值是结点的地址
- 结点变量*p的值是结点的内容
- *((*p).next)是*p后继结点的内容
优点:相对于数组,删除和插入效率高
缺点:相对于数组,查询元素和获取元素效率低
● 要执行插入操作,只需要如下的代码:
s->next = p->next
p-next = s ;
执行删除操作,只需要如下的代码:
p->next = p->next->next
或者
q = p->next;
p->next = q->next;
● 单链表结构和顺序表存储结构的优缺点
结论:
若线性表需要频繁查找,很少进行插入和删除操作,采用顺序表,若需要频繁插入和删除时,采用单链表。
当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构,这样可以不需要考虑存储空间的大小问题。 而如果事先知道线性表的大致长度,顺序表效率更好
单循环链表
● 单向循环链表是单链表的另一种形式,其结构特点是链表中最后一个结点的指针不再是结束标记,而是指向整个链表的第一个结点,从而使单链表形成一个环。
和单链表相比,循环单链表的长处是从链尾到链头比较方便。当要处理的数据元素序列具有环型结构特点时,适合于采用循环单链表。
● 单循环链表的初始化操作:示意图
非空表示意图:
● 假如终端结点用尾指针 rear 指示, 则查找终端结点是 O(1), 而开始结点,其实就是 rear->next->next, 时间复杂度也是 O(1)。
双向列表
● 双向链表是每个结点除后继指针外还有一个前驱指针。和单链表类同,双向链表也有带头结点结构和不带头结点结构两种,带头结点的双向链表更为常用;另外,双向链表也可以有循环和非循环两种结构,循环结构的双向链表更为常用。
● 双向链表相当于两个单向循环链表,双链表节点结构如下图示:
双链表节点比单链表多一个前驱指针域。
如下图是带头结点的双向循环链表的图示结构。双向循环链表的next和prior各自构成自己的单向循环链表:
在双向链表中,有如下关系:设对象引用p表示双向链表中的第i个结点,则p->next表示第i+1个结点,p->next->prior仍表示第i个结点,即p->next->prior == p;同样地,p->prior表示第i-1个结点,p->prior->next仍表示第i个结点,即p->prior->next == p。下图是双向链表上述关系的图示:
双向循环链表的插入过程:
下图中的指针p表示要插入结点的位置,s表示要插入的结点,①、②、③、④表示实现插入过程的步骤:
s->prior = p;
s->next = p->next;
p->next->prior = s;
p->next = s;
循环双向链表的删除过程:
下图中的指针p表示要插入结点的位置,①、②表示实现删除过程的步骤:
p->prior->next = p->next;
p->next->prior = p->prior;