1. 单链表

头节点

一般通过头指针来表示一个链表
在链表的头部增加一个头节点,头节点不存储数据
image.png

  1. /* 链表节点的定义 */
  2. struct Node {
  3. int val = 0;
  4. Node *next = nullptr;
  5. };
  6. /* 使用头指针来表示一个链表 */
  7. typedef Node *LinkList;
  8. LinkList list1 = new Node(); // 这里创建了一个空的头节点

使用头节点的好处

  • 可以统一空链表和非空链表的处理
  • 可以统一头节点和一般节点的处理

在头部插入节点时,使用头节点和不使用头节点的区别
注意到:

  • 不使用头节点时,操作有些怪异
  • 不使用头节点时,需要传头指针的引用进去(我更希望保持头指针不变) ```cpp / 使用头节点 / void insert(LinkList list, int value) { Node *node = new Node(); node->val = value; node->next = list->next; list->next = node; }

/ 不使用头节点 / void insert(LinkList &list, int value) { Node *node = new Node(); node->value = value; node->next = list; // 这两句感觉挺怪的 list = node; } ```

前插操作

已知单链表的一个节点,想要在该节点之前插入另一个节点

方法1: 找到前驱节点
从链表头部开始,找到该节点的前驱节点,然后将元素插入到前驱节点的后面,时间复杂度为:链式实现 - 图2
image.png

方法2: 在后方插入,并交换内容
将元素插入到该节点的后面,然后调换两个节点的值,时间复杂度为:链式实现 - 图4
image.png

删除操作

已知某个节点 *p,想要删除这个节点。
和前插类似,也有两种方法

方法1: 找到这个节点的前驱节点,然后执行删除操作,时间复杂度:链式实现 - 图6

方法2: 将后继节点的值赋值给当前节点,然后删除后继节点,时间复杂度为:链式实现 - 图7

注:如果要删除的节点是最后一个节点,由于需要将前驱节点的 next 置为 NULL,时间复杂度只能是:链式实现 - 图8

2. 双链表

双链表也可以有头节点,同样可以简化操作
image.png

3. 循环单链表

使用头节点的循环单链表情形:
image.png

使用尾指针的好处
由于是单链表,如果使用头指针,那么在尾部插入的时间复杂度就是链式实现 - 图11;使用尾指针可以让头部插入和尾部插入的时间复杂度都是链式实现 - 图12

判断循环单链表是否空:头节点的后继是否是头节点

4. 循环双链表

。。。

5. 静态链表

借助数组来描述链表的静态结构
image.png