什么是链表
链表不同于顺序表,但是得记住顺序表是随机排序,链表的空间不是连续的,而顺序表是连续的。它通过指针来连接一个个分散的节点,虽然物理上不相邻,但是在逻辑上是相邻的。

顺序表他不如链表,本质还是用数组来存的
链表本身分为带头结点的链表和不带头结点,戴头结点的链表就是会有一个头指向后续的整个链表,但是头结点不存放数据。

而不带头结点的链表就像上面那样,第一个节点就是用来存放数据的结点,一般设计链表都会采用带头结点的结构,因为操作更加方便。
那么我们先来写一个链表:
1 | typedef int E; |
接着我们来编写初始化函数:
1 | void initList(Node head){ |
链表的操作
插入链表
那么链表的插入该如何做到呢?

我们可以先修改新插入的结点和后继结点指向,指向原本在这个位置的结点


按照这个思路,我们来实现一下,首先设计一下函数:
1 | void insertList(Node head, E element, int index){ |
接着我们需要先找到待插入位置的前驱结点:
1 | _Bool insertList(Node head, E element, int index){ |
在循环操作完成后,如果没问题那么会找到对应插入位置的前驱结点,我们只需要按照上面分析的操作来编写代码即可:
1 | _Bool insertList(Node head, E element, int index){ |
接下来我们就可以使用打印函数来看一下:
1 | void printList(Node head){ |
现在我们来看下结果

删除结点
删除结点的话,我们只需要让该结点的next变为NULL,随之让后继结点指向该删除结点的前继结点即可。



那么我们来根据这样的思路来编写代码,首先是设计删除函数:
1 | void deleteList(Node head, int index){ |
需要找到待删除结点的前驱结点:
1 | _Bool deleteList(Node head, int index){ |
最后就是按照我们上面说的删除结点了:
1 | _Bool deleteList(Node head, int index){ |
这样,我们就成功完成了链表的删除操作
获取链表对应结点的元素
1 | E * getList(Node head, int index){ |
查找对应元素的位置
1 | int findList(Node head, E element){ |
求链表的长度
1 | int sizeList(Node head){ |
链表的性质
效率方面
因为要寻找对应位置的前驱结点,所以链式实现的线性表插入、删除操作的时间复杂度为O(n),但是不需要做任何的移动操作,效率肯定是比顺序表要高的。
但由于必须要挨个向后寻找,才能找到对应的结点,所以时间复杂度为O(n),不支持随机访问,只能顺序访问,比顺序表慢。
什么情况下使用顺序表,什么情况下使用链表呢
通过分析顺序表和链表的特性我们不难发现,链表在随机访问元素时,需要通过遍历来完成,而顺序表则利用数组的特性直接访问得到,所以,当我们读取数据多于插入或是删除数据的情况下时,使用顺序表会更好。
而顺序表在插入元素时就显得有些鸡肋了,因为需要移动后续元素,整个移动操作会浪费时间,而链表则不需要,只需要修改结点 指向即可完成插入,所以在频繁出现插入或删除的情况下,使用链表会更好。
说些什么吧!