文章目录
前言
路漫漫其修远兮,吾将上下而求索;
一、顺序表的缺陷
1、当空间不够的时候需要增容,增容是需要付出代价的;
增容的代价,因为增容是依靠库函数realloc 进行工作的,而realloc 的工作原理有两点:
- 一是,原地扩容,旧空间后面的空间足够(代价低)
- 二是,异地扩容,当旧空间后面的空间不够时,便会在堆上寻找一块足够大的空间,然后将旧空间中的数据拷贝放到新空间中,释放旧空间,并返回新空间的地址;(代价高)
2、顺序表要求数据从开始的位置连续存储;当我们在顺序表的头部或者中间位置插入或者删除数据的时候,需要挪动数据,在数组中一个一个地挪动数据,消耗是非常大的,故而其效率不高;
3、为了避免重复地扩容以打断操作系统地执行,所以在顺序表中扩容是以二倍地形式去扩的容,但是存在浪费空间的可能性,这也是一种消耗;
注:实际上还有一个内存池的存在;即该程序首先会向操作系统申请一块足够大地空间来动态开辟,这样少量空间地开辟并不会去频繁地打断操作系统的执行,这也是链表一个结点一个结点地动态开辟空间并不会打扰操作系统的原因
显然,利用顺序表来存放数据是非常不方便的,针对顺序表的这些缺陷,我们可以了解一下链表;
二、链表是如何设计的?
针对顺序表扩容以及数据插入删除存在的问题对应地设计出了链表,链表是由一个一个结点构成的,而一个个结点之中,存在着数值域与指针域;
当要存放一个数据地时候便开辟一块空间,此时便解决了空间的浪费问题;链表中的结点与结点之间依靠指针进行链接,像链条一样故而称之为链表;

三、链表的分类
链表有八种(下述三种特性任意组合):
- 单向或者双向
- 带头或者不带头
- 循环或者非循环
1、其中,在单结构中,链表分为单向链表、双向链表;
- 单向链表:上一个结点存放了下一个结点的地址;
- 双向链表:前一个结点存了后一个结点的地址,后一个结点也存放了前一个结点的地址;
注:如若想在单链表中找倒数第k个结点,是非常麻烦的,因为单向链表只记录了数据与下一结点的地址,也就是说单链表中的结点不能找到自己的上一结点;但是对于双向链表来说,就完全不是事;并且在双向链表中,删除结点无需保存上一个结点的地址(相较于单链表);
双向链表总体上优于单向链表,但是它也存在一个小缺点,会比单链表的每个结点多存一个指针变量;(实际上也没有特别大的负担);
2、带头或者不带头
其中的“头”也就是常说的“哨兵位头节点”,哨兵位头节点并不会被用来存放有效数据.
其意义在于,当尾插、头插时会很方便,因为不用考虑链表是否为空这一情况,并且也无需传二级指针,因为此处靠哨兵位头节点中的next 去链接结点.
在操作链表的时候 plist 指向哨兵位头节点,并不会更改指针plist 中存放的数据,故而不用传其地址,也就避免了函数中形参采用二级指针来接收;
3、循环或者非循环
循环链表是一种特殊的带环链表,即该链表的尾结点中的next 指向头节点;
非循环链表:该链表中尾结点中的next 指向NULL
综上,八种链表包括:带头单向循环、带头单向不循环、不带头单向循环、不带头单向不循环、带头双向循环、带头双向不循环、不带头双向循环、不带头双向不循环。
实际中较为常用得两种链表:
不带头单向不循环链表以及带头双向循环链表

不带头单向不循环链表:结构简单,一般不会拿来单独存放数据。实际中会作为哈希桶、图的邻接表等(作为其他数据结构的子结构);
带头双向循环链表:结构最复杂,一般会用来单独存放数据。在实际中使用的链表的数据结构基本上都是使用带头双向循环。即使这个结构很复杂,但是这个结构存在的优势非常多,(注:此结构可以看作是一个无死角结构,因为其中的两个指针prev 和next 分别记录了上一结点的地址与其下一结点的地址,删除任意位置的结点、在任意位置进行插入,它的应用场景都是一样的即无需分情况讨论链表的情况)。会使得实现这个结构的代码相较于不带头单向不循环链表简单很多;
四、链表的概念及其结构
1、链表的概念:
链表是一种物理结构上非连续、非顺序的结构,数据元素的逻辑结构时通过链表中的指针链接次序实现的.
链表的逻辑结构上呈现线性,但是其物理结构为非线性;
2、链表的结构
如下图所示:

从上图(其地址为假设的)中(以不带头单向不循环链表为例)我们可以得知
- 链式结构在逻辑结构上是线性、连续的,但是在物理上不一定是连续的(取决于动态开辟空间的分配);
- 结点需要malloc ,即结点的空间来自于堆区
- 从堆上申请的空间是按照一定给的策略进行分配的,即多次在堆上利用
链表的分类、概念及两种链表的实现

最低0.47元/天 解锁文章
269

被折叠的 条评论
为什么被折叠?



