《Java 数据结构与算法》第1章:链表
持续坚持原创输出,点击蓝字关注我吧
作者:小傅哥
博客:https://bugstack.cn
❝沉淀、分享、成长,让自己和他人都能有所收获!😜
❞
一、前言
二、链表数据结构
三、链表分类类型
1. 单向链表
2. 双向链表
3. 循环链表
四、实现一个链表
1. 链表节点
2. 头插节点
3. 尾插节点
4. 拆链操作
5. 删除节点
五、链表使用测试
六、常见面试问题
一、前言
链表的历史
于1955-1956年,由兰德公司的Allen Newell、Cliff Shaw和Herbert A. Simon开发了链表,作为他们的信息处理语言的主要数据结构。链表的另一个早期出现是由 Hans Peter Luhn 在 1953 年 1 月编写的IBM内部备忘录建议在链式哈希表中使用链表。
到 1960 年代初,链表和使用这些结构作为主要数据表示的语言的实用性已经很好地建立起来。MIT 林肯实验室的 Bert Green于 1961 年 3 月在 IRE Transactions on Human Factors in Electronics 上发表了一篇题为“用于符号操作的计算机语言”的评论文章,总结了链表方法的优点。1964 年 4 月,Bobrow 和 Raphael 的一篇评论文章“列表处理计算机语言的比较”发表在 ACM 通讯中。
二、链表数据结构
在计算机科学中,链表是数据元素的线性集合,元素的线性顺序不是由它们在内存中的物理地址给出的。它是由一组节点组成的数据结构,每个元素指向下一个元素,这些节点一起,表示线性序列。
在最简单的链表结构下,每个节点由数据和指针(存放指向下一个节点的指针)两部分组成,这种数据结构允许在迭代时有效地从序列中的任何位置插入或删除元素。
链表的数据结构通过链的连接方式,提供了可以不需要扩容空间就更高效的插入和删除元素的操作,在适合的场景下它是一种非常方便的数据结构。但在一些需要遍历、指定位置操作、或者访问任意元素下,是需要循环遍历的,这将导致时间复杂度的提升。
三、链表分类类型
链表的主要表现形式分为;单向链表、双向链表、循环链表,接下来我们分别介绍下。
1. 单向链表
单链表包含具有数据字段的节点以及指向节点行中的下一个节点的“下一个”字段。可以对单链表执行的操作包括插入、删除和遍历。
2. 双向链表
在“双向链表”中,除了下一个节点链接之外,每个节点还包含指向序列中“前一个”节点的第二个链接字段。这两个链接可以称为'forward('s')和'backwards',或'next'和'prev'('previous')。
3. 循环链表
在列表的最后一个节点中,链接字段通常包含一个空引用,一个特殊的值用于指示缺少进一步的节点。一个不太常见的约定是让它指向列表的第一个节点。在这种情况下,列表被称为“循环”或“循环链接”;否则,它被称为“开放”或“线性”。它是一个列表,其中最后一个指针指向第一个节点。
四、实现一个链表
像 Java 的源码中本身就提供了非常多的数据结构,包括我们所学习的链表 LinkedList 在日常的开发使用中也是非常频繁。
所以我们在学习的过程中,以使用 Java 程序员本身常用的语言来分析学习,并通过简化结构的方式把 LinkedList 手写实现,让读者更能方便的理解链表。
-
源码地址:https://github.com/fuzhengwei/java-algorithms - Java 算法与数据结构
-
源码详见:https://github.com/fuzhengwei/java-algorithms/data-structures/LinkedList.java
1. 链表节点
private static class Node<E> {
E item;
Node<E> next;
Node<E> prev;
public Node(Node<E> prev, E element, Node<E> next) {
this.item = element;
this.next = next;
this.prev = prev;
}
}
-
链表的数据结构核心根基就在于节点对象的使用,并在节点对象中关联当前节点的上一个和下一个节点。通过这样的方式构建出链表结构。 -
但也因为在链表上添加每个元素的时候,都需要创建新的 Node 节点,所以这也是一部分耗时的操作。
2. 头插节点
void linkFirst(E e) {
final Node<E> f = first;
final Node<E> newNode = new Node<>(null, e, f);
first = newNode;
if (f == null)
last = newNode;
else
f.prev = newNode;
size++;
}
-
头插的操作流程,先把头节点记录下来。之后创建一个新的节点,新的节点构造函数的头节点入参为null,通过这样的方式构建出一个新的头节点。 -
原来的头结点,设置 f.prev 连接到新的头节点,这样的就可以完成头插的操作了。另外如果原来就没有头节点,设置设置为新的节点即可。最后记录当前链表中节点的数量,也就是你使用 LinkedList 获取 size 时候就是从这个值获取的。
3. 尾插节点
void linkLast(E e) {
final Node<E> l = last;
final Node<E> newNode = new Node<>(l, e, null);
last = newNode;
if (l == null) {
first = newNode;
} else {
l.next = newNode;
}
size++;
}
-
尾差节点与头插节点正好相反,通过记录当前的结尾节点,创建新的节点,并把当前的结尾节点,通过 l.next 关联到新创建的节点上。同时记录 size 节点数量值。
4. 拆链操作
E unlink(Node<E> x) {
final E element = x.item;
final Node<E> next = x.next;
final Node<E> prev = x.prev;
if (prev == null) {
first = next;
} else {
prev.next = next;
x.prev = null;
}
if (next == null) {
last = prev;
} else {
next.prev = prev;
x.next = null;
}
x.item = null;
size--;
return element;
}
-
unlink 是一种拆链操作,只要你给定一个元素,它就可以把当前这个元素的上一个节点和一个节点进行相连,之后把自己拆除。 -
这个方法常用语 remove 移除元素操作,因为整个操作过程不需要遍历,拆除元素后也不需要复制新的空间,所以时间复杂读为 O(1)
5. 删除节点
public boolean remove(Object o) {
if (o == null) {
for (Node<E> x = first; x != null; x = x.next) {
if (x.item == null) {
unlink(x);
return true;
}
}
} else {
for (Node<E> x = first; x != null; x = x.next) {
if (o.equals(x.item)) {
unlink(x);
return true;
}
}
}
return false;
}
-
删除元素的过程需要 for 循环判断比删除元素的值,找到对应的元素,进行删除。 -
循环比对的过程是一个 O(n) 的操作,删除的过程是一个 O(1) 的操作。所以如果这个链表较大,删除的元素又都是贴近结尾,那么这个循环比对的过程也是比较耗时的。
五、链表使用测试
public static void main(String[] args) {
List<String> list = new LinkedList<>();
// 添加元素
list.add("a");
list.addFirst("b");
list.addLast("c");
// 打印列表
list.printLinkList();
// 头插元素
list.addFirst("d");
// 删除元素
list.remove("b");
// 打印列表
list.printLinkList();
}
测试结果
目前的列表,头节点:b 尾节点:c 整体:b,a,c,
目前的列表,头节点:d 尾节点:c 整体:d,a,c,
Process finished with exit code 0
-
按照我们的测试链表对数据的操作过程,从测试结果可以看到,已经满足了链表数据结构的使用。
六、常见面试问题
-
描述一下链表的数据结构? -
Java 中 LinkedList 使用的是单向链表、双向链表还是循环链表? -
链表中数据的插入、删除、获取元素,时间复杂度是多少? -
什么场景下使用链表更合适?
- END -
下方扫码关注 bugstack虫洞栈,与小傅哥一起学习成长、共同进步,做一个码场最贵Coder!
-
回复【设计模式】,获取《重学Java设计模式》,这是一本互联网真实案例的实践书籍,从实际业务中抽离出,交易、营销、秒杀、中间件、源码等众多场景进行学习代码设计。 -
回复【Spring专栏】, 获取《手撸Spring》,这是一本通过带着读者手写简化版 Spring 框架,了解 Spring IOC、AOP、循环依赖等核心原理和设计实现的技术资料。 -
回复【面经手册】,获取《面经手册 • 拿大厂Offer》,这是一本有深度的Java核心内容,从数据结构、算法、并发编程以及JVM系8不断深入讲解,让懂了就是真的懂。
java
工程师、架构师,开发过交易&营销、写过运营&活动、设计过中间件也倒腾过中继器、IO板卡。不只是写Java语言,也搞过C#、PHP,是一个技术活跃的折腾者。 本文分享自微信公众号 - bugstack虫洞栈(bugstack)。
如有侵权,请联系 support@oschina.cn 删除。
本文参与“OSC源创计划”,欢迎正在阅读的你也加入,一起分享。

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
Python图像处理丨两种实现图像形态学转化运算
摘要:本篇文章主要讲解Python调用OpenCV实现图像形态学转化,包括图像顶帽运算和图像黑帽运算。 本文分享自华为云社区《[Python图像处理] 十.形态学之图像顶帽运算和黑帽运算》,作者: eastmount 。 一. 图像顶帽运算 1.基本原理 图像顶帽(或图像礼帽)运算是原始图像减去图像开运算的结果,得到图像的噪声。如下图所示: 顶帽运算(img) = 原始图像(img) - 开运算(img) 2.函数原型 图像开运算主要使用的函数morphologyEx,它是形态学扩展的一组函数,其参数cv2.MORPH_TOPHAT对应开运算。其原型如下: dst = cv2.morphologyEx(src, cv2.MORPH_TOPHAT, kernel) 参数dst表示处理的结果,src表示原图像,cv2.MORPH_TOPHAT表示顶帽运算,kernel表示卷积核。下图表示5*5的卷积核,可以采用函数 np.ones((5,5), np.uint8) 构建。 卷积如下图所示: 3.代码实现 完整代码如下所示: #encoding:utf-8 import cv2 imp...
- 下一篇
一文讲透hdfs的delegation token
【背景】 前一段时间总结了hadoop中的token认证、yarn任务运行中的token,其中也都提到了delegation token。而最近也遇到了一个问题,问题现象是:flink任务运行超过七天后,由于宿主机异常导致任务失败,继而触发任务的重试,但接连重试几次都是失败的,并且任务的日志也没有聚合,导致无法分析问题失败的原因。最后发现是和delegation token有关,本文就来总结下相关的原理。 【原理】 1. 什么是delegation token 先简单描述下为什么需要delegation token。在开启kerberos之后,服务之间交互前,都需要先向KDC认证获取对应的票据。而在一个yarn任务运行过程中可能会产生很多任务container,每个这样的任务container都可能会访问hdfs,由于访问前需要先获取票据来进行认证,那么这个时候KDC就很容易成为性能瓶颈。delegation token(委派token)就是为了减少不必要的认证工作而出现的。 2.delegation token在任务提交运行过程中的使用 任务提交运行过程中,delegation to...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,CentOS7官方镜像安装Oracle11G
- 设置Eclipse缩进为4个空格,增强代码规范
- Docker使用Oracle官方镜像安装(12C,18C,19C)
- SpringBoot2整合Thymeleaf,官方推荐html解决方案
- SpringBoot2更换Tomcat为Jetty,小型站点的福音
- Windows10,CentOS7,CentOS8安装Nodejs环境
- CentOS7编译安装Cmake3.16.3,解决mysql等软件编译问题
- CentOS8安装MyCat,轻松搞定数据库的读写分离、垂直分库、水平分库
- CentOS关闭SELinux安全模块
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程