(大话数据结构)第三章 线性表

3.1 线性表的定义

零个或多个数据元素的有限序列。
关键点:
1.它是一个序列,元素之间是有个先来后到的顺序。
2.若元素存在多个,则第一个元素无前驱,而最后一个元素无后继,其他元素都有且只有一个前驱和后继。
3.线性表强调是有限的。

3.2 线性表的抽象数据类型

数据类型:例如说整型,浮点型,字符型这些就是数据类型。性质相同的,集合在一块。
例如在c语言中,数据类型可以分为两类:
原子类型:不可再分解的基本类型。如整型,浮点型,字符型。
结构类型:由若干个类型组合而成,可以再分解的。如整型数组是由若干个整型数据组成。

抽象数据类型(ADT):对已有的数据类型进行抽象。抽象出事物具有的普遍性。ADT是一个数学模型及定义在该模型上的一组操作。例如在3D游戏中,要定位角色的位置,那么总会出现x,y,z三个整形数据组合在一起的坐标。我们就可以定义一个point的抽象数据类型,它拥有x,y,z三个整形变量,这样就方便对一个角色的位置进行操作。

抽象数据类型的标准格式:
ADT 抽象数据类型名
Data
        数据元素之间逻辑关系的定义
Operation
        操作
endADT
简言之,抽象数据类型=数据类型+相关操作

线性表的抽象数据类型:
ADT 线性表(List)
Data
        每个元素的类型均为DataType,除了第一个元素之外,每个元素有且只有一个前驱元素,除了最后一个元素外,每个元素有且只有一个后继元素。数据元素之间的关系是一对一的关系。
Operation
        初始化操作,建立一个空的线性表L;
        判断线性表是否为空表,若空表返回true,否则返回false;
        线性表清空;
        将线性表中的第i个位置元素返回;
        在线性表中查找与给定值相等的元素,如果查找成功,返回该元素在表中序号表示成功,否则返回0表示失败;
        在线性表中第i个位置插入新元素;
        删除线性表中第i个位置元素,并返回其值;
        返回线性表的元素个数 ;
endADT

对于不同的应用,线性表的基本操作是不同的,上述操作是最基本的,对于实际问题中涉及的线性表更复杂操作,完全可以用这些基本操作的组合来实现。

3.3 线性表的顺序存储结构

线性表有两种物理存储结构:顺序存储结构和链式存储结构。

顺序存储结构:用一段地址连续的存储单元依次存储线性表的数据元素。比如数组。
物理上的存储方式就是在内存中找个初始地址,然后通过占位的形式,把一定的内存空间给占了,然后把相同数据类型的数据元素依次放在这块空地中。

顺序存储结构封装的三个属性:
1.存储空间的起始位置。
2.线性表的最大存储容量。一般初始化后不变
3.线性表的当前长度。动态变化的。

顺序存储结构的优缺点:
在存、读数据时,不管是哪个位置,时间复杂度都是O(1)。而插入或删除时,时间复杂度都是O(n)。这说明,它比较适合元素个数比较稳定,步经常插入和删除元素,更多的操作是存取数据的应用。

优点:
1.无须为表中元素之间的逻辑关系而增加额外的存储空间(和链式存储比较而言);
2.可以快速地存取表中任意位置的元素
缺点:
1.插入和删除操作需要移动大量元素。
2.当线性表长度变化较大时,难以确定存储空间的容量。
3.容易造成存储空间的碎片。

3.4 线性表的链式存储结构

顺序存储结构最大的缺点就是插入和删除时需要移动大量元素,这显然就需要耗费时间。原因就在于相邻两元素的存储位置具有邻居关系,他们在内存中的位置是紧挨着的,中间没有间隙,当然就无法快速插入和删除。
解决:每个元素多用一个位置来存放指向下一个元素位置的指针,这样子从第一个元素就可以找到第二个元素,以此类推。所有元素都可以通过遍历找到。

链式存储结构:用一组任意的存储单元存储线性表的数据元素,这组存储单元可以存在内存中未被占用的任意位置。

比起顺序存储结构每个数据元素只需要存储一个位置就可以了,现在的链式存储结构除了要存储数据元素信息外,还要存储它的后继元素的存储地址(指针)。

存储数据元素信息的域(域=地方)称为数据域,存储后继位置的域称为指针域。指针域中存储的信息称为指针或链。这两部分信息组成数据元素称为存储映像,称为结点(Node)

n个阶段链接成一个链表,记为线性表的链式存储结构。此链表的每个结点中只包含一个指针域,所以叫做单链表
这里写图片描述

第一个结点的存储位置叫头指针,最后一个结点指针为空(NULL)。

头指针与头结点的异同:
头结点的数据域一般不存储任何信息。它是第一个,有特权。
头指针:
1.是链表指向第一个结点的指针,若链表有头结点,则是指向头结点的指针。
2.具有标识作用,常用头指针冠以链表的名字。
3.无论链表是否为空,头指针均不为空。
4.头指针是链表的必要元素。
头结点:
1.是为了操作的统一和方便而设立的,放在第一个元素的节点之前,其数据域一般无意义。
2.有了头结点,对在第一元素结点前插入结点和删除第一结点操作与其他结点的操作就统一了。
3.头结点不一定是链表的必须要素。

这里写图片描述

3.5 单链表

单链表如图3-6-7:
这里写图片描述

C语言中可以用结构指针来描述单链表:

typedef struct Node
{
    ElemType data; //数据域
    struct Node *Next; //指针域
}Node;
typedef struct Node *LinkList;

假设 p 是指向线性表第 i 个元素的指针,则该结点 a i 的数据域我们可以用p->data来表示,它的值是一个数据元素。结点 a i 的指针域可以用p->next来表示,它的值是一个指针,指向第 i + 1 个元素。

如果p->data = a i ,那么p->next->data = a i + 1
这里写图片描述

单链表的读取:在线性表的顺序存储结构中,要计算任意一个元素的存储位置是很容易的,通过下标索引。在单链表中,必须从第一个结点开始挨个找。说白了就是从头开始找,直到第 i 个元素为止。由于这个算法的时间复杂度取决于 i 的位置,最坏情况的时间复杂度为O(n)。核心思想叫做:工作指针后移。

单链表的插入:
这里写图片描述
只需要两句话:

    s->next = p->next;
    p->next = s;

这里写图片描述

单链表的删除:
这里写图片描述

假设元素 a 2 的结点为q,要实现结点q删除单链表的操作,其实就是将它的前继结点的指针绕过,指向后继结点即可。

p->next = p->next->next

单链表插入与删除效率pk:都是由两部分组成:第一部分是遍历查找第 i 个元素,第二部分是实现插入和删除元素。时间复杂度O(n)。如果不知道第 i 个元素的指针位置,单链表数据结构在插入和删除操作上,与线性表的顺序存储结构是没有太大优势的。
但是如果我们希望从第 i 个位置开始,插入连续10个元素,对于顺序存储结构意味着每一次插入都需要移动 n i 个位置,所以每次都是O(n);而单链表,只需要在第一次时,找到第 i 个位置的指针,此时为O(n),接下来只是简单地通过赋值移动指针而已,时间复杂度都是O(1)。
对于插入和删除数据越频繁的操作,单链表的效率优势就越是明显。

单链表的整表创建:对于顺序存储结构的线性表的整表创建,我们可以用数组的初始化来直观理解。而单链表和顺序存储结构就不一样了,它不像顺序存储结构数据这么集中,它的数据可以是分散在内存各个角落的,它的增长也是动态的。

对于每个链表来说,它所占用空间的大小和位置是不需要预先分配划定的,可以根据系统的情况和实际的需求即时生成。创建单链表的过程是一个动态生成链表的过程,从空表的初始状态起,依次建立各元素节点并逐个插入链表。

创建的算法思路如下:
1.声明一结点p和计数器变量i;
2.初始化一空链表L;
3.让L的头结点的指针指向NULL,即建立一个带头结点的单链表;
4.循环实现后继节点的赋值和插入;

头插法建立单链表:
把新加进的元素放在表头后的第一个位置,先让新结点的next指向头结点之后,然后让标头的next指向新结点。就是现实中的插队,始终让新节点插在第一的位置。

尾插法建立单链表:
头插法建立链表简单,但是生成的链表中结点的次序和输入顺序相反。尾插法把新结点都插入到最后。

单链表整表删除:在内存中将它释放掉。思路如下:
1.声明结点p和q;
2.将第一个结点赋值给p,第二个结点赋值给q;
3.循环执行释放p和将q赋值给p的操作;

单链表结构与顺序存储结构优缺点:
这里写图片描述

经验性结论:
1.若线性表需要频繁查找,很少进行插入和删除操作时,宜采用循序存储结构;若需要频繁插入和删除时,宜采用单链表结构。比如用户注册的个人信息,除了注册时插入数据外,绝大多数都是读取,所以应该考虑用顺序存储结构。
2.当线性表中的元素个数变化较大或者根本不知道有多大时,最好用单链表结构,这样可以不需要考虑存储空间的大小问题。

猜你喜欢

转载自blog.csdn.net/weixin_37904412/article/details/80595685