第一个动态结构:链表

封面:链表.png

王有志,一个分享硬核Java技术的互金摸鱼侠
加入Java人的提桶跑路群:共同富裕的Java人

今天我们一起学习线性表中的第二种数据结构:链表,也是真正意义上的第一个动态数据结构。
今天的内容分为3个部分:认识链表链表的形式单向链表的实现

什么是链表

在数据结构:线性表入门中,我们知道数组使用连续的内存,可如果程序已经运行了很久,内存中没有足够的连续内存,这时需要一个线性表结构,除了使用360安全卫士清理内存外,该怎么办呢?
早在1955年就有人想到了这个问题,从而诞生了一种影响深远的数据结构-链表。

链表开发于1955-56,由当时所属于兰德公司(英语:RAND Corporation)的艾伦纽维尔(Allen Newell),克里夫肖(Cliff Shaw)和赫伯特西蒙(Herbert Simon)在他们编写的信息处理语言(IPL)中做为原始数据类型所编写。IPL被作者们用来开发几种早期的人工智能程序,包括逻辑推理机,通用问题解算器和一个计算机象棋程序。

以上来源于维基百科的链表。在预备知识:概念和存储结构中,我们讲到链式存储结构,链表正是这样一种结构:
图1:链式分配内存.png
可以看到,链表使用分散在内存各个角落的内存块,通过“一根线”将内存块”串“起来。
得益于链式存储结构,我们可以很容易的为链表动态扩缩容,通常我们会称这种为动态数据结构,而像数组那样顺序存储的数据结构称为静态数据结构

链表的概念

链表中,如果每块内存只存储数据元素的话,内存之间就无法形成线性结构。
为了使链表“串起来”,除了存储数据元素外,还需要存储直接后继节点的内存地址,通过“无形的线”将内存块“串”起来。
图2:链表的节点.png
我们将存储后继节点内存地址的变量称为指针(即图中next);存储着数据元素和指针的组合称为链表的节点;链表中第一个节点称为头节点,最后一个节点称为尾节点
可能很多小伙伴听到指针就头疼,从C语言就开始折磨,好不容易到了Java,Python中没有了指针,可学个数据结构还要遭受指针的折磨。
其实指针的概念很简单,指针实际上是一个变量,存储的是某个对象的内存地址

链表的特点

对于链表来说,插入和删除很简单,因为不需要使用连续的内存,所以也不需要在插入和删除后“整理”内存,得益于这种链式的存储结构,链表具有时间复杂度为 O ( 1 ) O(1) O(1)的插入和删除操作。

插入和删除

图3:链表的插入.png
如图,我们在节点B和D之间插入节点C,只需要2步操作即可:

  • 节点C的指针指向节点D
  • 节点B的指针指向节点C

删除操作也是类似的:
图4:链表的删除.png
如果要删除节点C,需要3步操作:

  • 存储节点D
  • 删除节点C的指针
  • 节点B指向节点D

可以看到,链表的插入和删除操作仅需要有限的操作步骤即可完成,在这个意义上时间复杂度为 O ( 1 ) O(1) O(1)

查询操作

对于链表来说,因为不具备顺序存储结构的特点,无法通过简单的计算查找到指定下标的数据元素,我们只能从链表的头节点出发,依次向后查询,直到查询到需要的数据元素位置。
这种情况下,链表查询操作的时间复杂度为 O ( n ) O(n) O(n)

链表的形式

到目前为止,我们对链表有了一定的认识,接下来我们来看看都有哪些常用链表的实现。

单向链表

单向链表是最简单的链表,也是我们在学习链表的概念中使用到的形式。
图5:链表的节点.png
特点是,每个节点中只存储数据元素和指向直接后继节点的指针。下面会和大家实现一个简单的单向链表。

双向链表

双向链表是单向链表的升级版,除了存储数据元素和指向直接后继节点的指针外,还存储了指向直接前驱节点的指针
图6:双向链表.png
假设我们已知链表中的某个节点,我们希望在链表中删除这个节点。
如果是单向链表,我们需要遍历整个链表,查找到该节点的直接前驱节点后再进行删除,而在双向链表中,我们可以通过该节点获取到直接前驱节点。
实际上,我们经常使用的LinkedList.java就是双向链表,可谓是商业应用中,双向链表的最佳实现。

循环链表

除此之外,循环链表也是链表的一种实现,它的特点是尾节点的指针指向头节点,形成环状
图7:循环链表.png
在循环链表中,我们可以从任意节点开始向同一方向出发,访问到链表中的全部节点,这是单向链表和双向链表无法完成的。
循环链表最著名的应用就是约瑟夫问题。

单向链表的实现

今天的最后一部分,我们就一起来实现单向链表。首先,我们定义单向链表的节点,节点主要包含两部分:数据元素直接后继节点的指针

public class Node<E> {

	private E element;

	private Node<E> next;

	public Node(E data, Node<E> next) {
		this.data = data;
		this.next = next;
	}
}

实际上,仅有Node也已经足够我们构建链表了,比如说:

Node<Integer> node = new Node<>(0, new Node<>(1, new Node<>(2, null)));

但是这样的链表看着有点闹心,并且非常“丑陋”,而且需要手动实现诸如插入删除等操作,因此我们要对它进行简单的包装:

public class SinglyLinkedList<E> {

	// 头节点
	private Node<E> first;

	// 尾节点
	private Node<E> last;

	private int size;

	// 检查下标
	private void checkIndex(int index) {
		if (index >= this.size || index < 0) {
			throw new IndexOutOfBoundsException();
		}
	}

	private static class Node<E> {

		private E element;

		private Node<E> next;

		public Node(E data, Node<E> next) {
			this.data = data;
			this.next = next;
		}
	}
}

将Node封装到单向链表SinglyLinkedList中,同时添加了私有变量first,last和size为了方便后续的操作,同时添加了检查下标的私有方法。
新增操作非常简单,只需要处理好链表为空的情况,其余直接在链表尾部添加即可,代码实现如下:

public void add(E element) {
	Node<E> newestNode = new Node<>(element, null);
	if(this.first == null) {
		this.first = this.last = newestNode;
	} else {
		this.last.next = newestNode;
		this.last = newestNode;
	}
	this.size++;
}

我们再来实现查找指定下标数据元素的方法,实现的思想也非常简单,只需要从链表的头部开始,不断的向后移动,直到到达指定下标为止,代码实现如下:

public E get(int index) {
	checkIndex(index);
	int i = 0;
	Node<E> current = this.first;
	while (i < index) {
		current = current.next;
		i++;
	}  
	return current.element;
}

最后,我们实现删除指定数据元素的方法,实现也并不复杂,不过要处理比较多的情况:

  • 头节点符合,并且链表中全部元素都符合,删除整个链表,并更新头尾节点
  • 头节点不符合,链表中间包含或者尾节点符合,删除指定元素/更新尾节点

代码实现如下:

while (this.size != 0 && this.first.element.equals(element)) {
	Node<E> oldFirst = this.first;
	if(oldFirst == this.last) {
		this.first = this.last = null;
	} else {
		this.first = oldFirst.next;
		oldFirst.next = null;
	}
	this.size--;
}

if (this.size == 0) {
	return;
}

Node<E> prev = this.first;

while (prev.next != null) {
	if (prev.next.element.equals(element)) {
		Node<E> delNode = prev.next;
		if(delNode == this.last) {
			this.last = prev;
		}
		prev.next = delNode.next;
		delNode.next = null;
		this.size--;
	} else {
		prev = prev.next;
	}
}

这样我们就实现了一个单向链表,并为其添加了查找,新增和删除的操作。这样的链表我们拿来学习,“娱乐”是可以的,但是在商业应用中使用,就显得远远不足了。诸如,在指定位置添加,删除指定位置数据元素等,就留给小伙伴们自行实现了。

结语

今天,我们一起学习了第一个真正意义上的动态数据结构-链表,了解了它的3种实现,并一起动手实现了简单的单向链表。
相较于数组而言,链表对内存的要求相当低,这为链表带来优势,同时也使其失去了随机存取的能力。

练习

动手实现下自己的链表吧:

  • 单向链表
  • 双向链表
  • 21.合并两个有序链表
  • 206.反转链表
  • 剑指 Offer 06.从尾到头打印链表
  • 剑指 Offer 22.链表中倒数第k个节点
  • 剑指 Offer 24.反转链表

如果本文对你有帮助的话,还请多多点赞支持。如果文章中出现任何错误,还请批评指正。最后欢迎大家关注分享硬核Java技术的金融摸鱼侠王有志,我们下次再见!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/311136.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IIS+SDK+VS2010+SP1+SQL server2012全套工具包及安装教程

前言 今天花了两个半小时安装这一整套配置&#xff0c;这个文章的目标是将安装时间缩短到1个小时 正文 安装步骤如下&#xff1a; VS2010 —> service pack 1 —>SQL server2012 —> IIS —> SDK 工具包链接如下&#xff1a; https://pan.baidu.com/s/1WQD-KfiUW…

微软开源时空预测Fost的使用和解读

一、引言 时空预测是指对未知系统状态在时间和空间上的预测&#xff0c;它是地球系统科学、交通运输、智慧城市等领域的重要技术和工具。时空预测的目的是利用历史数据和当前信息&#xff0c;通过建立时空依赖关系&#xff0c;来推断未来的变化趋势和可能的情景。时空预测的应…

Hive数据库:嵌入、本地、远程全攻略(上)

Hive分布式数据仓库工具 关系型数据库 建立在关系模型之上的数据库称为关系型数据库(关系模型是由埃德加科德于1970年提出的)&#xff0c;关系型数据库借助集合代数等数学概念处理数据库中的数据。数据查询语言SOL是基于关系型数据库的语言,能够对关系型数据库中的数据进行检…

单摆波运动

1、简介 单摆波运动通常由15个单摆小球完成&#xff0c;每个小球的线长不一致&#xff0c;线长从一端至另一端依次增长。线长不一致会导致运动周期不一致&#xff0c;故而单摆波运动中的每个小球运动都不同&#xff0c;且能在规则与不规则运动间转换。单摆波运动如下所示&…

Qt QComboBox组合框控件

文章目录 1 属性和方法1.1 文本1.2 图标1.3 插入和删除1.4 信号和槽 2 实例2.1 布局2.2 代码实现 Qt中的组合框是集按钮和下拉列表体的控件&#xff0c;&#xff0c;它占用的屏幕空间很小&#xff0c;对应的类是QComboBox 1 属性和方法 QComboBox有很多属性&#xff0c;完整的…

力扣hot100 路径总和Ⅲ dfs 前缀和 一题双解 超全注释

Problem: 437. 路径总和 III 思路 树的遍历 DFS 一个朴素的做法是搜索以每个节点为根的&#xff08;往下的&#xff09;所有路径&#xff0c;并对路径总和为 targetSumtargetSumtargetSum 的路径进行累加统计。 使用 dfs1 来搜索所有节点&#xff0c;复杂度为 O(n)O(n)O(n)&am…

【计算机网络】TCP原理 | 可靠性机制分析(三)

个人主页&#xff1a;兜里有颗棉花糖 欢迎 点赞&#x1f44d; 收藏✨ 留言✉ 加关注&#x1f493;本文由 兜里有颗棉花糖 原创 收录于专栏【网络编程】【Java系列】 本专栏旨在分享学习网络编程、计算机网络的一点学习心得&#xff0c;欢迎大家在评论区交流讨论&#x1f48c; 目…

数据结构第十二弹---堆的应用

堆的应用 1、堆排序2、TopK问题3、堆的相关习题总结 1、堆排序 要学习堆排序&#xff0c;首先要学习堆的向下调整算法&#xff0c;因为要用堆排序&#xff0c;你首先得建堆&#xff0c;而建堆需要执行多次堆的向下调整算法。 但是&#xff0c;使用向下调整算法需要满足一个前提…

全网最细RocketMQ源码一:NameSrv

一、入口 NameServer的启动源码在NameStartup&#xff0c;现在开始debug之旅 二、createNamesrcController public static NamesrvController createNamesrvController(String[] args) throws IOException, JoranException {System.setProperty(RemotingCommand.REMOTING_VER…

Java中多线程二

抢占调度模型 概述&#xff1a;优先让优先级高的线程使用 CPU &#xff0c;如果线程的优先级相同&#xff0c;那么随机会选择一个&#xff0c;优先级高的线程获取的 CPU 时间片相对多一些 Thread 类中一些关于线程的方法 方法简述public final int getPriority()返回此线程的优…

五、Java中SpringBoot组件集成接入【slf4j日志文档】

五、Java中SpringBoot组件集成接入【slf4j日志文档】 1.slf4j简介2.maven依赖3.配置4.使用5.展示6.参考文章 1.slf4j简介 SLF4J&#xff08;Simple Logging Facade for Java&#xff09;是一个为Java应用程序提供统一日志接口的日志门面框架。它旨在解决Java应用程序中日志系统…

居中面试问题

前端常问居中面试问题 css文本居中 文本水平居中 <div class"father"><div class"child"><div> <div>子类元素为行内元素&#xff0c;则给父类元素定义text-align:center 如果子元素是块元素&#xff0c;则给子元素定义margin&…

Vue3快速入门

文章目录 1. Vue3简介1.1. 【性能的提升】1.2. 源码的升级】1.3. 【拥抱TypeScript】1.4. 【新的特性】 2. 创建Vue3工程2.1. 【基于 vue-cli 创建】2.2. 【基于 vite 创建】(推荐)2.3. 【一个简单的效果】 3. Vue3核心语法3.1. 【OptionsAPI 与 CompositionAPI】Options API 的…

Linux系统——测试端口连通性方法

目录 一、TCP端口连通性测试 1、ssh 2、telnet&#xff08;可能需要安装&#xff09; 3、curl 4、tcping&#xff08;需要安装&#xff09; 5、nc&#xff08;需要安装&#xff09; 6、nmap&#xff08;需要安装&#xff09; 二、UDP端口连通性测试 1、nc&#xff08;…

85.乐理基础-记号篇-速度记号

内容来源于&#xff1a;三分钟音乐社 上一个内容&#xff1a;85.乐理基础-记号篇-力度记号-CSDN博客 速度记号在下方两个里面已经写过一部分了&#xff0c;这些标记总体上是属于 不变速度 的标记&#xff0c;比如一首乐谱就记了 每分钟60拍&#xff0c;那整首速度就都是不变的…

org.springframework.web.servlet.HandlerInterceptor

过期 1 配置黑名单 2 启动注册拦截 3 浏览器访问拦截

【Spring Cloud】Sentinel流量限流和熔断降级的讲解

&#x1f389;&#x1f389;欢迎来到我的CSDN主页&#xff01;&#x1f389;&#x1f389; &#x1f3c5;我是Java方文山&#xff0c;一个在CSDN分享笔记的博主。&#x1f4da;&#x1f4da; &#x1f31f;推荐给大家我的专栏《Spring Cloud》。&#x1f3af;&#x1f3af; &am…

【SAP-PP】生产订单导入问题--完成日期向前推了一天

问题描述&#xff1a; 在执行BAPI_PRODORD_CREATE生产订单导入的时候&#xff0c;发现填写入模板中的基本完成日期是12月31日&#xff0c;但是到具体工单时变成了12月30日 截图说明&#xff1a; 感觉很神奇&#xff0c;咋一看&#xff0c;真的是日期提前了一天&#xff0c;de…

线性回归实例

1、线性回归&#xff08;linear Regression&#xff09;和逻辑回归&#xff08;logistic Regression&#xff09;的区别 线性回归主要是用来拟合数据&#xff0c;逻辑回归主要是用来区分数据&#xff0c;找到决策边界。 线性回归的代价函数常用平方误差函数&#xff0c;逻辑回…

函数指针和回调函数

文章目录 一.函数指针1.什么是函数指针2.函数指针的形式3.函数指针的用途。1.调用函数2.作为参数进行传递 二.函数指针数组三.回调函数 一.函数指针 1.什么是函数指针 函数指针是指向函数的指针。在C语言和C中&#xff0c;函数指针可以用来存储函数的地址&#xff0c;并且可以…