目录
书接上回,针对顺序表中存才的问题,如空间不够时会进行扩容造成效率低下,还可能浪费空间;在头插和中间插入数据时会挪动数据造成效率低下。
链表可以对以上问题进行优化,因为可以满足按需开辟空间,且不需要挪动数据。
虽然链表与顺序表相比有这些优点,但并不能替代顺序表,犹如跑车与卡车的区别,使用目的不同,选择不同的数据结构。
1. 链表的概念及结构
链表是一种物理存储结构上非连续、非顺序的存储结构。
数据元素的逻辑顺序是通过链表中的指针链接次序实现的。
可以看出在物理上存储的地址不同,是因为每一个单独的节点都是在堆区动态开辟的,相应的位置是不确定的,所以物理上不一定是连续的。
但是在逻辑结构中,因为每个节点中的next都存储了下一个节点的指针,对指针解引用可以找到下一个节点,所以在逻辑上是一定连续的。
2. 链表的分类
链表 分类主要有三种不同的条件区别
1:是否带哨兵位的头结点
2:是否是双向链表
3:是否是循环链表
因此可以看到,链表的种类总共有八种。
其中最常用的是无头单向不循环链表和带头双向循环链表。
无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结
构的子结构,如哈希桶、图的邻接表等等。另外这种结构在OJ中出现很多。
带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都
是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单。
3. 链表的实现
3.1 单向非循环无头链表的实现
先对无头单向非循环链表进行实现。
首先在编译器中建立SLlist.h,对链表中的节点和函数以及一些常量标识符的声明;再建立SLlist.c,对声明的函数进行实现;建立test.c,对链表各个函数进行测试。
首先,对链表的节点完成声明
对生成一个节点的声明及实现,因为创建一个节点后需要得到节点的地址,所以返回类型为结构体的指针,参数为需要存入节点的值;
对创建一个链表的声明及实现,同样链表的需要返回链表第一个节点的指针,才能找到这个链表;参数为节点的个数。
对链表的打印的声明及实现,参数为链表的头节点,不需要返回值。
尾插的声明与实现,首先将返回值为空,这样对链表的使用者来说比较方便。所以对于参数:因为尾插存在链表为空,即头节点的指针的指向为NULL,当插入新节点时,会改变头节点的指针,从指向NULL变为指向新节点,所以需要传头节点指针的地址,即SLN的二级指针,才能对指针的值进行改变,否则只是改变的函数内形参的值,并不能改变实参。若想使用就只使用SLN的一级指针,则需要对将形参的结果进行返回,这与设计这个函数接口的初衷不符;以及尾插的数据域的值。
尾删的声明与实现,对于参数:可能会将链表删空,所以最后会改变头节点的指针指向NULL,需要SLN的二级指针,与上面同理,返回值为空。
头插的声明与实现,同上需要二级指针作为形参,以及需要插入的数据值。因为会改变链表头节点的指针。返回值为空。
头删的声明与实现,同上,需要二级指针作为形参。因为链表会需要改变头节点的指针,使其指向空。