链表通常指的是单向链表,他包含多个节点,每个节点都有一个纸箱下一个几点的next指针。表中最后一个节点指向NULL,表示链表的结束。
image.png
下面是一个基于Java 声明的链表节点

  1. public class Node<T extends Serializable> {
  2. // 节点数据
  3. private T data;
  4. // 指向的Next 节点
  5. private Node<T> next;
  6. }
  • 遍历链表
  • 在链表中插入一个元素
  • 在链表中删除一个元素

1.1 链表的遍历

假设表头节点是指向链表的第一个节点,完成链表的遍历只需要执行以下步骤。很简单可以在链表中查找的时间复杂度为 单向链表 - 图2

  1. 沿指针遍历
  2. 显示当前指针所指向的内容
  3. 将当前节点指向 Next 节点
  4. 当 Next 节点指向NULL的时候遍历结束
    1. public static <T extends Serializable> void linkedListForeach(Node<T> headNode) {
    2. Node<T> currentNode = headNode;
    3. // 4. 遍历终止条件,Next指向NULL
    4. while (currentNode != null) {
    5. // 2.输出当前节点的数据
    6. System.out.println(currentNode.getData() + "->");
    7. // 3. 将当前节点指向Next 节点
    8. currentNode = currentNode.getNext();
    9. }
    10. }

根据同样的逻辑,也可以根据此实现链表长度的计算。

  1. private static <T extends Serializable> int length(Node<T> headNode) {
  2. Node<T> currentNode = headNode;
  3. int count = 0;
  4. while (currentNode != null) {
  5. count++;
  6. currentNode = currentNode.getNext();
  7. }
  8. return count;
  9. }

1.2 链表插入元素

链表中插入元素分为三种,分别是在链表头部插入元素,在链表中间插入元素以及在链表尾部插入元素。需要注意的这里的链表的位置指的在p位置插入元素,在插入完成之后,链表的p位置指向的是新的节点。 针对插入操作其时间复杂度同查找一致,在头部插入元素的时间复杂度为 单向链表 - 图3 ,在中间以及尾部节点插入的时间复杂度为 单向链表 - 图4 主要的体现在对节点位置的查找上。

1.2.1 在链表的头部插入新的节点

image.png

其逻辑步骤是:

  • 首先将新的节点的指针指向表头节点
  • 将表头节点指向新的节点

1.2.2 在链表尾部插入节点

image.png

在链表尾部插入新的节点也非常简单,需要将新的节点的NEXT 指针指向NULL,然后将原链表的尾指针执行新节点,即可。

1.2.3 在链表中间插入节点

image.png

  • 首先定位到需要插入的节点位置,断开其 Next 节点,注意需要保存 Next 节点的引用
  • 将当前节点的 Next 指针执行新的节点
  • 将新节点的 Next 指针指向 Next 节点

1.2.4 源码示例

  1. // 时间复杂度 O(n) 空间复杂度 O(1)
  2. private static <T extends Serializable> Node<T> insert(
  3. Node<T> headNode, Node<T> insertNode, int position) {
  4. // 判断长度,插入的位置是否合法
  5. int length = length(headNode);
  6. if (position <= 0 || position > length) {
  7. throw new IllegalArgumentException();
  8. }
  9. // 如果是链表头部,直接指向Next节点
  10. if (position == 1) {
  11. insertNode.setNext(headNode);
  12. return insertNode;
  13. }
  14. // 如果不是头部,那么获取到当前插入位置的节点
  15. Node<T> currentNode = findByPosition(headNode, position - 1);
  16. if (currentNode == null) {
  17. return headNode;
  18. }
  19. // 保存当前插入位置节点的Next引用
  20. Node<T> next = currentNode.getNext();
  21. // 当前节点的Next 指向新的节点
  22. currentNode.setNext(insertNode);
  23. // 新的节点的Next 指向Next节点
  24. insertNode.setNext(next);
  25. return headNode;
  26. }

1.3 链表删除元素


同插入类似,链表删除也分为三种情况,分别是在删除链表头部元素,删除链表中间元素以及在删除链表尾部元素。 针对删除操作其时间复杂度同查找一致,删除头部元素的时间复杂度为 单向链表 - 图8 ,删除中间以及尾部节点的时间复杂度为 单向链表 - 图9 主要的体现在对节点的查找

1.3.1 删除链表头部节点

image.png
删除头部节点元素非常简单,只需要将原头部节点的NEXT指向NULL,然后将表头指针指向原头结点的Next节点即可。

1.3.2 删除链表尾部节点

image.png

删除尾部节点也非常简单,只要将尾部节点的前驱的Next节点设置为NULL,即可

1.3.3 删除链表中间的节点

image.png

删除链表的中间节点的步骤是:

  • 定位当前节点为需要删除节点的前驱位置,当前节点的后驱就是即将删除的节点
  • 将删除节点的Next 指向NULL
  • 将当前节点的Next指针执行将要删除的节点的Next节点。

1.3.4 代码示例

  1. // 时间复杂度 O(n) 空间复杂度 O(1)
  2. private static <T extends Serializable> Node<T> remove(Node<T> headNode, int position) {
  3. int length = length(headNode);
  4. if (position <= 0 || position > length) {
  5. throw new IllegalArgumentException();
  6. }
  7. // 如果是删除头部节点,直接返回头部节点的Next
  8. if (position == 1) {
  9. headNode.setNext(null);
  10. return headNode.getNext();
  11. }
  12. // 定位到删除节点的前驱节点,以及即将删除的节点
  13. Node<T> preNode = findByPosition(headNode, position - 1);
  14. Node<T> currentNode = findByPosition(headNode, position);
  15. // 如果前驱节点存在,则将前驱节点的Next设置为删除节点的Next
  16. if (preNode != null) {
  17. Node<T> next = Objects.isNull(currentNode) ? null : currentNode.getNext();
  18. preNode.setNext(next);
  19. }
  20. return headNode;
  21. }