微信搜索【NO编程】,关注这个与众不同的公众号。
个人网站:www.newobject.cc
版权声明:本文为原创文章,转载请注明出处。

链表简介

链表是很常见的数据结构,由一个个节点组成,每个节点中储存着数据和指针(地址引用),指针负责节点间的连接。

它是一种线性表,线性表有两种存储方式:顺序存储和链式存储。链表属于链式存储,顺序由元素间的指针决定,元素在内存中非连续存放,且链表长度可以改变。数组是顺序存储的线性表,元素在内存中连续存放的,且数组创建时大小已固定。

链表可以用来实现栈和队列数据结构(栈和队列可理解为逻辑类数据结构,链表属于存储类数据结构),实现缓存 LRU 算法,Java 类库也使用了链表(如,LinkedList,LinkedHashMap)等。链表的形式有很多,常用的有单向链表、双向链表、循环链表 …

单向链表

单链表中的节点分两部分,分别是数据(data)和指向下一个节点的地址(next),尾节点(tail)的 next 指向 null。单向链表只能从头到尾一个方向遍历,查找节点时需要从头节点(head)开始向下查找。
插入节点首先遍历查找到插入的位置,然后将当前插入节点的 next 指向下一节点,上一节点的 next 指向当前插入节点。删除节点同样从头遍历找到要删除的节点,然后将当前删除节点的上一个节点 next 指向当前删除节点的下一个节点。

节点的伪代码:

class Node<E>{private E item;private Node<E> next; // 如果是尾节点,next 指向 nullNode(E data, Node<E> next) {this.item = data;this.next = next;}// ...
}

单向循环链表

循环链表和非循环链表基本一样,区别是首尾节点连在了一起,最后一个节点的 next 指向头节点,形成了一个闭环。

节点的伪代码:

class Node<E>{private E item;// 如果是末尾节点,指向首节点的引用地址private Node<E> next;Node(E data, Node<E> next) {this.item = data;this.next = next;}// ...
}

双向链表

顾名思义,与单向链表相比较,双向链表可以从头到尾或从尾到头两个方向来遍历数据。双向链表中的节点分三个部分,分别是指向上一个节点的地址(prev)和数据(data)以及指向下一个节点的地址(next),尾节点(tail)节点的 next 指向 null,头节点(head)的 prev 指向 null。
增加和删除节点和单向链表同理,只是增加了修改 prev 地址的操作。

节点的伪代码:

class Node<E>{private E item;private Node<E> prev; // 头节点 prev 指向 nullprivate Node<E> next; // 尾节点 next 指向 nullNode(Node<E> prev, E data, Node<E> next) {this.item = data;this.prev = prev;this.next = next;}// ...
}

双向循环链表

尾节点的next指向头节点,头节点的 prev 指向尾节点,首尾节点连在一起形成闭环。

节点的伪代码:

class Node<E> { private E item;// 如果是第一个节点,其一用指向末尾节点private Node<E> prev;// 如果是末尾节点,指向第一个节点的引用地址,形成一个环形private Node<E> next;Node(Node<E> prev, E data, Node<E> next) {this.item = data;this.prev = prev;this.next = next;}// ...
}

链表操作

链表的增删改查操作。链表查找节点需要从头或者尾部(单向链表只能从头开始)开始查找,删除或插入节点先查找到节点,然后改变相关节点的指针指向即可。

以双向链表为例:

添加节点

  1. 头部添加节点

伪代码:

Node<E> head;
Node<E> tail;
int size;// 头部添加节点
void addHead(E e) {Node<E> h = head;Node<E> newNode = new Node<>(null, e, h); // (Node<E> prev, E element, Node<E> next)head = newNode;if(h == null) { // 空链表tail = newNode;} else {h.prev = newNode;}   size++; // 记录长度
}
  1. 尾部添加节点

伪代码:

void addTail(E e) {Node<E> t = tail;Node<E> newNode = new Node<>(t, e, null);tail = newNode;if(t == null) {head = newNode;} else {t.next = newNode;      }       size++;
}
  1. 按位置插入节点

伪代码:

void add(int index, E element) {if (index == size) {// 直接在尾部添加节点} else {// 查找的节点Node<E> temp = null;if (index < (size >> 1)) {//由于双向链表,选择从离 index 位置最近端查找Node<E> x = head;for (int i = 0; i < index; i++) {x = x.next;}temp = x;    } else {Node<E> x = tail;for (int i = size - 1; i > index; i--) {x = x.prev;}temp = x;   }// 插入节点Node<E> pred = temp.prev;Node<E> newNode = new Node<>(pred, element, temp);temp.prev = newNode;if (pred == null) { // 查找到的节点为 Head 节点head = newNode;} else {pred.next = newNode;}   } size++;}

删除节点

  1. 删除头部节点

伪代码:

E removeHead() {Node<E> h = head;    if (h != null){E element = h.item;Node<E> next = h.next;head = next;if (next == null) {tail = null;} else {next.prev = null;}   size--; // 减少长度return element; // 返回删除元素}return null;
}
  1. 删除尾部节点

伪代码:

E removeTail() {Node<E> t = tail;if (t != null) {E element = t.item;Node<E> prev = t.prev;tail = prev;if (prev == null) {head = null;} else {prev.next = null;}size--;return element;}return null;
}
  1. 按节点位置或值删除

伪代码-按位置删除:

E remove(int index) {// 根据index查找节点Node<E> temp = null;if (index < (size >> 1)) {Node<E> x = head;for (int i = 0; i < index; i++) {x = x.next;}temp = x;    } else {Node<E> x = tail;for (int i = size - 1; i > index; i--) {x = x.prev;}temp = x;   }// 删除节点E element = temp.item;Node<E> next = temp.next;Node<E> prev = temp.prev;if (prev == null) {head = next;} else {prev.next = next;temp.prev = null;}if (next == null) {tail= prev;} else {next.prev = prev;temp.next = null;}temp.item = null;size--; return element;
}

查找节点

  1. 按位置或值查找节点

伪代码-按位置索引查找:

E get(int index) {Node<E> temp = null;if (index < (size >> 1)) { // 从近的一端开始查找Node<E> x = first;for (int i = 0; i < index; i++) {x = x.next;} temp = x;} else {Node<E> x = last;for (int i = size - 1; i > index; i--) {x = x.prev;} temp = x;}return temp.item;
}

如果是单向链表只能从头部开始向后查找。

更新节点

更新节点首先查找到节点,然后修改节点 data 的指针。

具体可参考 LinkedList 源码

链表实现栈和队列

栈和队列是一种对数据存取有严格顺序要求的线性数据结构,使用链表和数组都能实现。下面使用链表来实现栈和队列。

栈只能从一端存取数据,遵循 后进先出(LIFO) 原则。进出栈的一端称为栈顶,另一封闭端称为栈底,数据进入栈称为入栈或压栈,取出数据称为出栈或弹栈。

伪代码 - 基于双向链表实现简单的“栈”:

class Stack<E> {// 返回栈顶元素值public E peek() {Node<E> h = head;return (h == null) ? null : h.item;}// 入栈public void push(E e) {addHead(e); // 在头部添加节点}// 出栈public E pop() {// 移除头部节点并返回值return removeHead(); }// ...private static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}}
}

队列

队列是从两端存取数据,并且从一端进,从另一端出,遵循 先进先出(FIFO) 原则。队列进数据一端称为队尾,出数据端称为队头,数据进队列称为入队,取出队列称为出队。

伪代码 - 基于链表实现“队列”:

class Queue {// 入队public boolean offer(E e) {return addTail(e); }// 出队public E poll() {return removeHead();}// 返回头元素值public E peek() {Node<E> h = head;return (h == null) ? null : h.item;}private static class Node<E> {E item;Node<E> next;Node<E> prev;Node(Node<E> prev, E element, Node<E> next) {this.item = element;this.next = next;this.prev = prev;}}
}

快慢指针

快慢指针是解决链表某些问题的常用方法,利用两个不同步频的指针 fast 指针和 slow 指针算法来解决很多问题,例如:

查找未知长度的单向链表倒数第N个值

由于链表长度未知,首先循环链表得到 length,然后再次循环链表到 length-(N-1) 处得到元素。但是利用快慢指针来保持固定位置间隔,只需要循环一次链表即可查找到元素。

伪代码:

public E getLastN(int n) {Node<E> h = head;if (h == null || n < 1) {return null;}Node<E> fast = h; // 快Node<E> slow = h; // 慢int count = 1;while ((fast = fast.next) != null) {// 倒数第 k 个节点与倒数第 1 个节点相隔 n-1 个位置,因此 fast 先走 n-1 个位置if (count++ > n - 1) {slow = slow.next;}}// 链表中的元素个数小于 nif (count < n) {return null;}return slow.item;
}

找到链表中间节点值

使快指针移动步频是慢指针二倍,一次遍历即可快速找到中间节点。

伪代码:

public E getMiddle() {Node<E> h = head;if (h == null) {return null;}Node<E> fast = h; // 快Node<E> slow = h; // 慢while (fast != null && fast.next != null) {fast = fast.next.next;// 链表长度为偶数会两个中间节点,返回第一个if (fast != null) {slow = slow.next;}}return slow.item;
}

源码:https://github.com/newobjectcc/code-example/blob/master/basic/datastructure/Linked.java

除此之外,还可以判断链表中是否有环等等问题,快慢指针在面试时可能会被问到,有兴趣朋友可以到网上找些链表的算法题。


原创不易,如果本文对你有帮助,烦请朋友们,点赞、在看或转发支持一下, 这会让我更有创作的动力。
微信搜索「NO编程」或扫描下方二维码关注我的公众号,该公众号致力于通过文字和动图来讲解 Java 相关知识,一起学习交流,共同进步成长,欢迎关注。

数据结构之链表 - 动图演示相关推荐

  1. 数据结构与算法--经典10大排序算法(动图演示)【建议收藏】

    十大经典排序算法总结(动图演示) 算法分类 十大常见排序算法可分为两大类: 比较排序算法:通过比较来决定元素的位置,由于时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序 非比较类型排 ...

  2. c 语言从大到小排序算法,10 大经典排序算法(动图演示+ C 语言代码)

    原标题:10 大经典排序算法(动图演示+ C 语言代码) 来源:C语言与CPP编程 以前也零零碎碎发过一些排序算法,但排版都不太好,又重新整理一次,排序算法是数据结构的重要部分,系统地学习很有必要. ...

  3. 一文总结十大经典排序算法(思维导图 + 动图演示 + 代码实现 C/C++/Python + 致命吐槽)

    声明 1)该文章整理自网上的大牛和专家无私奉献的资料,具体引用的资料请看参考文献. 2)本文仅供学术交流,非商用.如果某部分不小心侵犯了大家的利益,还望海涵,并联系博主删除. 3)博主才疏学浅,文中如 ...

  4. 算法 - 十大经典排序算法(动图演示)

    [TOC] 算法 - 十大经典排序算法(动图演示) ​ 在计算机科学与数学中,一个排序算法(英语:Sorting algorithm)是一种能将一串资料依照特定排序方式进行排列的一种算法.最常用到的排 ...

  5. 十大经典排序算法Python版实现(附动图演示)

    来源:大数据DT 本文约5200字,建议阅读10分钟 排序算法是<数据结构与算法>中最基本的算法之一.本文介绍10种常见的内部排序算法,及如何用Python实现. 排序算法可以分为内部排序 ...

  6. 一文读懂Python版的十大经典排序算法(附动图演示)

    来源:大数据DT 本文约5200字,建议阅读10分钟 排序算法是<数据结构与算法>中最基本的算法之一.本文介绍10种常见的内部排序算法,及如何用Python实现. 排序算法可以分为内部排序 ...

  7. 十大经典排序算法(动图演示,收藏好文)

     0.算法概述  0.1 算法分类 十种常见排序算法可以分为两大类: 非线性时间比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此称为非线性时间比较类排序. 线 ...

  8. 十大经典排序C++实现及动图演示

    0.算法概述 0.1 算法分类 十种常见排序算法可以分为两大类: 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序. 非比较类排序: ...

  9. 程序员面试必备:动图演示十大经典排序算法及代码实现

    0.算法概述 0.1 算法分类 十种常见排序算法可以分为两大类: 比较类排序:通过比较来决定元素间的相对次序,由于其时间复杂度不能突破O(nlogn),因此也称为非线性时间比较类排序. 非比较类排序: ...

最新文章

  1. mvc的宿舍管理系统源码 基于jsp_[源码和文档分享]基于JSP的MVC框架实现的图书推荐系统展示平台网站...
  2. 阿里开源混沌工程工具 ChaosBlade
  3. jquery 对 Json 的各种遍历
  4. chk mysql.sh_zabbix监控mysql_MySQL
  5. Java程序优化的一些最佳实践
  6. 喵哈哈村与哗啦啦村的大战(四)(树形DP)
  7. R学习-小白笔记08
  8. stella forum 知识库---一些错误的修补
  9. VS2017编写C++多文件时,出现LNK2005、LNK1169报错的解决方法
  10. ant design pro模板_ant design pro超详细入门教程
  11. JDK1.8中英文官方文档
  12. MySQL8.0密码找回与权限刷新
  13. css3中边框的4种样式
  14. 全国计算机考试分几个等级?怎么报考呢?
  15. 2022 最值得学习的编程语言:Python 高人气,Ruby 薪水最优渥
  16. Intel8086处理器使用NASM汇编语言实现操作系统15-段的定义section/vstart和align语法
  17. 自定义带取景框的camera
  18. python 使用 openpyxl 批量调整字体和样式
  19. ADATE320介绍
  20. Hive分析函数之SUM,AVG,MIN和MAX OVER(PARTITION BY xxx order by xxx,用于求一段时间内截至到每天的累计访问次数、平均访问次数、最小访问次数、最大访问次

热门文章

  1. 方向余弦矩阵DCM刚体的矢量—矩阵描述
  2. f49.in index.php,国家语言,语言代码,locale id对应表
  3. EasyPoi的简介
  4. Win10家庭中文版用批处理打开本地组策略
  5. 关于在linux测试启动盘命令(qemu的使用)
  6. 跨部门的高效沟通与协作
  7. vgg19.npy下载
  8. keil手把手创建文件
  9. 如何实现伸缩 /折叠报表
  10. python列表list元素降序排列两种方法