2.数据结构-链表

news2024/9/24 23:32:14

概述

目标

  1. 链表的存储结构和特点
  2. 链表的几种分类及各自的存储结构
  3. 链表和数组的差异
  4. 刷题(反转链表)

概念及存储结构

先来看一下动态数组 ArrayList 存在哪些弊端

  • 插入,删除时间复杂度高
  • 需要一块连续的存储空间,对内存要求比较高,比如要申请1000M 的数组,如果内存没有连续且足够大的存储空间,则会申请失败,即使内存的剩余空间大于 1000M ,仍然会申请失败

链表 (Linked list) 是一种物理存储单元上非连续非顺序的存储结构 ,链表中的每一个元素称之为节点(Node),节点之间用指针(引用) 连接起来,指针的指向顺序代表了节点的逻辑顺序,节点可以在运行时动态生成;每个节点包括两部分:一个是存储数据元素的数据,另一个是存储下一个节点地址的指针
在这里插入图片描述
链表解决了下面两个问题

  1. 链表天生具备动态扩容的特点,不需要像动态数组那样先申请一个更大的空间,能够避免内存空间的大量浪费
  2. 链表不需要一块连续的内存空间,它通过指针将一组零散的内存块串联起来使用,所以如果申请一个1000M大小的链表,只要内存空间大于这个值,就可以申请,不会出现问题
  3. 但链表也会占更多的空间

链表分类

链表根据其节点之间的连接形式可以分为:单链表双向链表循环链表双向循环链表

单链表

单链表就是链表的最基本的结构,链表通过指针将一组零散的内存块串联在一起,如下图所示,将这个记录下一个节点地址的指针叫作后继指针 next,如果链表中的某个节点为pp的一下节点为q,可以表示为: p.next = q
在这里插入图片描述

单向链表中有两个节点是比较特殊的,它们分别是第一个节点和最后一个节点,习惯性地将第一个节点称为头节点,最后一个节点叫尾节点,其中,头节点用来记录链表的基地址,有了它,就可以遍历得到整条链表,而尾节点特殊的地方是:指针不是指向下一个节点,而是指向一个空地址NULL,表示这是单链表上最后一个节点

与数组一样,链表也支持 数据的查找,插入及删除操作
在进行数组的插入,删除操作时,为了保持内存数据的连续性,需要做大量的数据搬移操作,所以时间复杂度为O(n);链表中插入或者删除一个数据时,并不需要为了保持内存的连续性而搬移节点,因为链表的存储空间本来就是不连续的,所以在链表中插入和删除一个数据,是非常快的
如下图所示,针对链表的插入和删除操作,只要考虑相邻节点的指针改变,插入删除的时间复杂度是O(1),查询到指定到数据仍是O(n)
在这里插入图片描述
在这里插入图片描述
有利有弊,链表要想随机访问第k个元互,就没有数组那么高效了,因为链表中的数据并非连续存储的,所以无法像数组那样,根据首地址和下标,通过寻址公式就能直接计算出对应的内存地址,需要根据一个节点一个节点的依次遍历,直到找到相应的节点,所以,查询的时间复杂得是O(n)

双向链表

单向链表只有一个方向,节点只有一个后继指针 next ,而双向链表,它支持两个方向,每个节点有一个后继指针next指向后面的节点,还有一个前驱指针prev指向前面的节点,如下图所示
在这里插入图片描述
由图可知,双向链表对比单向链表,在存储同样多的数据,需要更多的内存存储空间,但可以支持双向遍历,这样也带来了双向链表操作的灵活性,比如:

  1. 可以在O(1)时间内找到给定结点的前驱节点,而对于单向链表需要O(n)

在很多场景下双向链表都比单向链表更加高效,这就是为什么实际的软件开发中,双向链表尽管比较费内存,但还是比较单链表的应用更加广泛的原因,在java语言中,LinkedHashMap就是用到了双向链表这种数据结构
实际上,这里有一个重要的思想是:用空间换时间的设计思想,根据机器内存空间是否充足,来判断是时间换空间,还是空间换时间

循环链表

循环链表 是一种特殊的单链表,实际上,它跟单链表唯一的区别就在尾节点上,单链表的尾节点指针指向NULL,表示这就是最后的节点了,而循环链表的尾节点指向链表的头节点,循环链表结构如下图中所示
在这里插入图片描述
循环链表的优点是从链尾链头比较方便,当要处理的数据具有环型特点时,特别适合用循环链表

双向循环链表

了解了循环链表双向链表,如果把这两种链表整合在一起就是一个双向循环链表
在这里插入图片描述

链表和数组的差异

链表数组对比

数组和链表是两种截然不同的内存组织方式,因为内存存储的区别,它们插入,删除,随机访问操作的时间复杂度正好相反,看下表

时间复杂度数组链表
插入删除O(n)O(1)
随机访问O(1)O(n)

刷题(反转链表)

反转链表

迭代解法

在这里插入图片描述
代码如下

public class Demo {
	public static void main(String[] args) {
		ListNode last = new ListNode(4);
		ListNode node3 = new ListNode(3, last);
		ListNode node2 = new ListNode(2, node3);
		ListNode head = new ListNode(1, node2);

		ListNode cur = head;
		while (cur != null) {
			System.out.println(cur.val);
			cur = cur.next;
		}
		System.out.println("--------");
		ListNode node = reverseList(head);
		ListNode cur2 = node;
		while (cur2 != null) {
			System.out.println(cur2.val);
			cur2 = cur2.next;
		}
	}
	
	public static ListNode reverseList(ListNode head) {
		ListNode pre = null;
		ListNode cur = head;
		while (cur != null) {
			// 获取 head 下一个节点,缓存
			ListNode tmp = cur.next;
			// 当前节点指向前一个节点
			cur.next = pre;
			// 指向移动
			pre = cur;
			cur = tmp;
		}
		return pre;
	}

	public static class ListNode {
		int val;
		ListNode next;

		ListNode() {
		}

		ListNode(int val) {
			this.val = val;
		}

		ListNode(int val, ListNode next) {
			this.val = val;
			this.next = next;
		}

		@Override
		public String toString() {
			return "ListNode{" +
					"val=" + val +
					", next=" + next +
					'}';
		}
	}
}

在这里插入图片描述

递归解法

递归解法,理解之后,会觉得很巧妙

  1. 终止条件是当前节点或者一个节点 == null
  2. 在函数内部,改变节点的指向,也就head 的下一个节点指向 head head.next.next = head
    在这里插入图片描述

代码如下

public class Demo {
	public static void main(String[] args) {
		ListNode last = new ListNode(4);
		ListNode node3 = new ListNode(3, last);
		ListNode node2 = new ListNode(2, node3);
		ListNode head = new ListNode(1, node2);

		ListNode cur = head;
		while (cur != null) {
			System.out.println(cur.val);
			cur = cur.next;
		}
		System.out.println("--------");
		ListNode node = reverseList2(head);
		ListNode cur2 = node;
		while (cur2 != null) {
			System.out.println(cur2.val);
			cur2 = cur2.next;
		}
	}

	public static ListNode reverseList2(ListNode head) {
		// 递归终止条件是当前为空,或者下一个节点为空
		if (head == null || head.next == null) {
			return head;
		}
		// 最后一次递归,返回节点数据值为4的节点
		ListNode cur = reverseList2(head.next);
		// 此时是在节点数值为3的节点执行方法中
		// head.next 代表节点4,head 代表节点 3
		head.next.next = head;
		// 清除原来 节点3批向节点4的指针,防止节点3,4之间成循环链表
		head.next = null;
		return cur;
	}
	public static class ListNode {
		int val;
		ListNode next;

		ListNode() {
		}

		ListNode(int val) {
			this.val = val;
		}

		ListNode(int val, ListNode next) {
			this.val = val;
			this.next = next;
		}

		@Override
		public String toString() {
			return "ListNode{" +
					"val=" + val +
					", next=" + next +
					'}';
		}
	}
}

调试这个递归函数
在这里插入图片描述
由上图可以看出,4节点在执行完函数后,返回至3节点所在执行函数,所以 head.next.next 意思是 3节点的下一个节点(4节点)的指针指向3节点,即完成了3,4节点指向调转,这个地方需要理解

在这里插入图片描述
最终结果如下图
在这里插入图片描述

结束

至此 链表 就分析完了,如有问题,欢迎评论留言

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

SSM培训报名管理系统开发mysql数据库web结构java编程计算机网页源码eclipse项目

一、源码特点 SSM 培训报名管理系统是一套完善的信息系统,结合SSM框架完成本系统,对理解JSP java编程开发语言有帮助系统采用SSM框架(MVC模式开发),系统具有完整的源代码和数据库,系统主 要采用B/S模式开…

vim

简介 vim是一款多模式的文本编辑器,vim里面还有很多子命令,来进行代码的编写操作 常用模式图 命令模式 光标移动 shif $ 光标定义到当前行的最右侧结尾 shift ^ 光标定义到当前行的最左侧开头 shift g 光标定位到文本最末尾…

激光雷达点云基础-点云滤波算法与NDT匹配算法

激光雷达点云处理在五年前就做了较多的工作,最近有一些新的接触发现激光雷达代码原理五年前未见重大更新,或许C与激光雷达结合本身就是比较高的技术门槛。深度学习调包侠在硬核激光雷达技术面前可以说是完全的自愧不如啊。 1、点云滤波 在获取点云数据…

利用远程IO模块,轻松驾驭食品包装生产的自动化

常见的自动化包装系统,它的核心部分通常由一系列高端设备组成,包括自动开箱机、自动封箱机、自动捆扎机、装箱机器人、码垛机器人等。这些设备协同工作,形成一条高效运转的生产线,从开箱到装箱,再到码垛,每…

SpringCloud(二) Eureka注册中心的使用

在SpringCloud(一)中,我们学会了使用RestTemplate进行远程调用,但是在调用user-service时候需要在order-service中发送http请求,请求中需要书写对应微服务的ip和端口号,十分不方便,如果此时有多个user-service实例的话,就不知道调用哪个了(除非每次调用的时候都对ip和端口号进行…

SpringCloud 微服务全栈体系(九)

第九章 Docker 三、Dockerfile 自定义镜像 常见的镜像在 DockerHub 就能找到,但是我们自己写的项目就必须自己构建镜像了。 而要自定义镜像,就必须先了解镜像的结构才行。 1. 镜像结构 镜像是将应用程序及其需要的系统函数库、环境、配置、依赖打包而…

uniapp保存网络图片

先执行下载uni.downloadFile接口,再执行保存图片uni.saveImageToPhotosAlbum接口。 // 保存二维码 saveQrcode() {var _this this;uni.downloadFile({url: _this.qrcodeUrl, //二维码网络图片的地址success(res) {console.log(res);uni.saveImageToPhotosAlbum({fi…

MQTT 保留消息

一、保留消息简介 官方文档:https://www.emqx.io/docs/zh/v5.1/messaging/mqtt-retained-message.html 1、为什么需要保留消息 不考虑持久会话的因素,MQTT订阅只有在客户端连接之后才能创建主题。所以当消息到达服务端之后,服务端只会把消息…

Pycharm安装jupyter和d2l

安装 jupyter: jupyter是d2l的依赖库,没有它就用不了d2l pycharm中端输入pip install jupyter安装若失败则: 若网速过慢,则更改镜像源再下载: pip config set global.index-url https://mirrors.aliyun.com/pypi/simple/ pip …

梯度下降|笔记

1.梯度下降法的原理 1.1确定一个小目标:预测函数 机器学习中一个常见的任务是通过学习算法,自动发现数据背后的规律,不断改进模型,做出预测。 上图的坐标系,横轴表示房子面积,纵轴表示房价,图…

注册资金认缴和实缴的区别

公司注册资本金实缴和认缴的区别有性质不同、意义不同、类型不同、缴纳时限不同、缴纳方式不同、包含范围不同等方面。 具体分析如下: 公司注册资本实缴制是营业执照注册资金有多少,该公司的银行验资账户上就得有相应金额的资金。实缴制需要占用公司的资…

__attribute__中的constructor和destructor--如何让程序退出时调用指定函数

背景 假设你在开发一个基础组件x,然后你设计了一个x_init接口用来初始化这个组件,相应地你设计了一个x_deinit来去初始化。这样其它模块要用到这个组件时,先调一下x_init, 用完了再调一下x_deinit。init和deinit这是一对很常见的接口&#x…

NB-IOT的粮库挡粮门异动监测装置

一种基于NBIOT的粮库挡粮门异动监测装置,包括若干个NBIOT开门监测装置,物联网后台管理系统,NBIOT低功耗广域网络和用户访问终端;各个NBIOT开门监测装置通过NBIOT低功耗广域网络与物联网后台管理系统连接,物联网后台管理系统与用户访问终端连接.NBIOT开门监测装置能够对粮库挡粮…

网络新闻发稿为何经久不衰?

有的老板可能看不到新闻营销的直接回报,一直不乐意在此方面投入,但是却看到竞争对手一直在搞新闻营销,也就安排个PR做做新闻公关。小马识途营销顾问观察,自互联网诞生以来,新闻营销一直是网络营销工作中的一个重点。 如…

JS中的元编程

ES6(ECMAScript 2015)新增了对 Reflect 和 Proxy 对象的支持,使得我们能够便捷地进行元编程。让我们通过示例来学习它们的用法。 1、什么是元编程 元编程 无异于 编程中的魔法!如果编写一个“能够读取、修改、分析、甚至生成新程…

提高小程序SEO 排名,9招优化技巧!

在当今移动互联网时代,小程序已经成为企业必不可少的一种营销手段,而如何让用户能够更容易地找到自己的小程序,就需要进行SEO优化,提升小程序的排名,本文将 为大家介绍9个小程序SEO优化技巧,帮助您的小程序…

黑客技术(网络安全)——如何高效学习

前言 前几天发布了一篇 网络安全(黑客)自学 没想到收到了许多人的私信想要学习网安黑客技术!却不知道从哪里开始学起!怎么学 今天给大家分享一下,很多人上来就说想学习黑客,但是连方向都没搞清楚就开始学习…

OPENCV 闭运算实验示例代码morphologyEx()函数

void CrelaxMyFriendDlg::OnBnClickedOk() {hdc this->GetDC()->GetSafeHdc();// TODO: 在此添加控件通知处理程序代码string imAddr "c:/Users/actorsun/Pictures/";string imAddr1 imAddr"rice.png";Mat relax, positive;relax imread(imAddr1…

Linux的基础常用指令

常用指令汇及其功能 ls 列出当前文件夹有哪些文件 ls -a显示所有文件,包含隐藏的文件和文件夹pwd显示当前是在哪个文件夹下mkdirmkdir名字→创建文件夹cdcd名字→进入某个指定文件夹cd .. 退回上层文件夹(cd后有空格) Tab键自动补全:文件或文件名太长&a…

信息系统项目管理师教程 第四版【第8章-项目整合管理-思维导图】

信息系统项目管理师教程 第四版【第8章-项目整合管理-思维导图】 课本里章节里所有蓝色字体的思维导图