日撸Java三百行(day13:链表)

news2024/9/25 13:16:48

目录

一、链表的基础知识

二、链表的代码实现

1.链表创建

2.链表遍历

3.链表定位查找

4.链表插入

5.链表删除

6.数据测试

7.完整的程序代码

总结


一、链表的基础知识

在之前顺序表的学习中,我们其实提到过链表。链表它是线性表在不同的物理存储方式下派生出来的,链表不像顺序表,它的数据元素在物理存储结构上并不连续,其数据元素的逻辑顺序是通过链表中的引用链接次序实现的,总结一下就是链表在逻辑上连续,在物理上不连续。

链表中的每一个元素称为结点,因此链表其实是由一系列结点组成的。而每个结点包括两个部分:一个是存储数据元素的数据域data,另一部分则是存储下一个结点地址的指针域next,通过这个指针域我们就可以轻松访问到当前结点的下一个结点,直到某个结点的next域为null。这样,就相当于把所有的结点都串联起来,进行链式访问,所以叫做“链表”。图解如下:

在实际情况中,链表可以分为:

  • 单向or双向

  • 循环or非循环

  • 带头or不带头

组合起来,一共就有8种链表。 

二、链表的代码实现

与顺序表一样,链表也可以进行初始化、查找、插入、删除等操作,下面我们通过java来实现。

1.链表创建

在day11(顺序表一)中,我们学习了类和对象的相关知识,包括如何定义一个类、如何创建对象、如何使用对象等,基于此,我们在定义的链表类中再创建一个结点类,同时定义该结点类的成员变量(data、next)和成员方法,如下:

public class LinkedList {

	/**
	 * An inner class.
	 */
	class Node {
		/**
		 * The data.
		 */
		int data;

		/**
		 * The reference to the next node.
		 */
		Node next;

		/**
		 ******************* 
		 * The constructor
		 * 
		 * @param paraValue
		 *            The data.
		 ******************* 
		 */
		public Node(int paraValue) {
			data = paraValue;
			next = null;
		}// Of the constructor
	}// Of class Node

在上述代码中,我们有两个需要注意的问题:

  • 这里的结点类是一个内部类inner class,根据链表的基本概念,我们知道链表是由若干个结点组成,而结点又由data域和next域组成,所以其符合内部类的定义(如果外部类是由若干完整的类组成,那么这些若干完整的类就可以定义为内部类)
  • 在定义成员变量next时,使用的是Node引用,这是因为next所指向的是下一个结点的地址,而结点始终是Node类型,所以我们用Node来定义next

然后,我们再定义一个头结点,并通过new为其分配内存空间,代码如下:

    /**
	 * The header node. The data is never used.
	 */
	Node header;

	/**
	 *********************
	 * Construct an empty linked list.
	 *********************
	 */
	public LinkedList() {
		header = new Node(0);
		//header.next = null; //Redundant
	}// Of the first constructor

为什么这里要单独把头结点header拿出来定义呢?一方面是因为header是链表的特征,而不是结点的;另一方面是因为头结点的数据域data是无效的,不存放任何数据元素。

2.链表遍历

与顺序表一样,我们同样可以重写toString()方法来遍历输出链表,如下:

    /**
	 *********************
	 * Overrides the method claimed in Object, the superclass of any class.
	 *********************
	 */
	public String toString() {
		String resultString = "";

		if (header.next == null) {
			return "empty";
		} // Of if

		Node tempNode = header.next;
		while (tempNode != null) {
			resultString += tempNode.data + ", ";
			tempNode = tempNode.next;
		} // Of while

		return resultString;
	}// Of toString

根据之前学过的如何使用一个对象,可以知道if语句中的header.next(对象名.成员变量)即表示头结点后的下一个结点的地址,如果其等于null,那么该链表显然为空,所以我们需要提前排除这种情况;排除后,我们将头结点后的下一个结点定义为tempNode(结点始终是Node类型,所以用Node定义);再通过while循环进行迭代,其中tempNode.data(对象名.成员变量)表示该结点所存放的数据,而tempNode = tempNode.next;则实现了对象的迭代,相当于由指向前一个结点移动为指向下一个结点,即可访问下一个结点的相关数据,这其实与数组中索引值递增的作用有点类似;最后通过循环条件tempNode != null来控制,当tempNode = null即到达最后一个结点时结束。

3.链表定位查找

其实链表定位查找方法创建的大体思路和顺序表没有什么差别,都是通过循环遍历,将现有的数据元素与目标元素一一比较,最大的不同就是遍历方式发生了改变,代码如下:

    /**
	 *********************
	 * Locate the given value. If it appears in multiple positions, simply
	 * return the first one.
	 * 
	 * @param paraValue
	 *            The given value.
	 * @return The position. -1 for not found.
	 *********************
	 */
	public int locate(int paraValue) {
		int tempPosition = -1;

		Node tempNode = header.next;
		int tempCurrentPosition = 0;
		while (tempNode != null) {
			if (tempNode.data == paraValue) {
				tempPosition = tempCurrentPosition;
				break;
			} // Of if

			tempNode = tempNode.next;
			tempCurrentPosition++;
		} // Of while

		return tempPosition;
	}// Of locate
  • 第一步,我们同样设置一个非法值-1,它的作用就是当在链表中没有找到目标元素时(包括链表为空、链表不为空但遍历完后没有找到目标元素这两种情况),返回非法值-1
  • 第二步,定义头结点后的下一个结点,并定义此结点的下标为0(因为前面说过头结点的data域不存放数据,所以头结点后的下一个结点相当于是第一个有效的结点,所以将其下标定为0)
  • 第三步,利用while循环进行遍历,将链表中的数据元素tempNode.data与目标元素paraValue一一比对:如果相等就将此时数据元素的下标赋给tempPosition,然后通过break语句跳出循环,再返回tempPosition的值;如果不等就通过tempNode = tempNode.next进行迭代,继续遍历

4.链表插入

由于链表在物理存储结构上不一定连续,所以它不用像顺序表那样通过大段元素移动覆盖的方式间接完成插入、删除等操作。链表的插入只需要创建一个新的结点,然后修改该新结点插入位置前后的引用链接次序即可,图解如下(图源网络):

根据图示,要完成链表插入,大体上可以分为三步:

  • 第一步,创建一个新的结点
  • 第二步,找到指定插入位置的前一个结点
  • 第三步,修改该指定插入位置前后的指针指向

代码如下: 

    /**
	 *********************
	 * Insert a value to a position. If the list is already full, do nothing.
	 * 
	 * @param paraPosition
	 *            The given position.
	 * @param paraValue
	 *            The given value.
	 * @return Success or not.
	 *********************
	 */
	public boolean insert(int paraPosition, int paraValue) {
		Node tempNode = header;
		Node tempNewNode;

		for (int i = 0; i < paraPosition; i++) {
			if (tempNode.next == null) {
				System.out.println("The position " + paraPosition + " is illegal.");
				return false;
			} // Of if

			tempNode = tempNode.next;
		} // Of for i

		// Construct a new node.
		tempNewNode = new Node(paraValue);

		// Now link them.
		tempNewNode.next = tempNode.next;
		tempNode.next = tempNewNode;

		return true;
	}// Of insert

第一步没什么好说的,直接创建即可;第二步中,我们需要提前考虑指定插入位置是否合法,这里将tempNode.next == null是否成立作为判断条件;确定插入位置合法之后,我们通过tempNode = tempNode.next;进行迭代,再利用i < paraPosition控制循环,使得到达指定插入位置的前一个结点;而第三步的修改链接次序,参考上图的解释就好。

5.链表删除

和链表插入同样的思想,链表删除也是通过修改链接次序来实现,不过链表删除还要更简单一点,只需要删掉目标位置前后的链接,再增加目标位置前一个结点与目标位置后一个结点之间的链接即可,图解如下(图源网络):

由于链表删除不需要创建新的结点,所以只需要两步:

  • 第一步,找到目标位置的前一个结点
  • 第二步,修改目标位置前后的链接次序

代码如下:

    /**
	 *********************
	 * Delete a value at a position.
	 * 
	 * @param paraPosition
	 *            The given position.
	 * @return Success or not.
	 *********************
	 */
	public boolean delete(int paraPosition) {
		if (header.next == null) {
			System.out.println("Cannot delete element from an empty list.");
			return false;
		} // Of if

		Node tempNode = header;

		for (int i = 0; i < paraPosition; i++) {
			if (tempNode.next.next == null) {
				System.out.println("The position " + paraPosition + " is illegal.");
				return false;
			} // Of if

			tempNode = tempNode.next;
		} // Of for i

		tempNode.next = tempNode.next.next;

		return true;
	}// Of delete

在执行第一步之前,我们需要考虑两个问题,第一个问题是链表是否为空,第二个问题是目标位置是否合法。对于第一个问题,直接用一个if语句进行判断即可,当header.next = null时,头节点的后一个节点的地址为null,也就是第一个有效结点的地址为null,此时链表显然为空,所以返回false;而第二个问题,需要注意与链表插入不一样,此时的判断条件不再是tempNode.next == null,而是tempNode.next.next == null。解决以上两个问题后,就可以利用tempNode = tempNode.next;进行遍历,直到找到目标位置的前一个结点。

对于第二步的修改链接次序,只需要令tempNode.next = tempNode.next.next;即可,这里的temp.next.next其实就是目标位置后一个结点的地址,将该地址赋给tempNode.next(即目标位置前一个结点的next域),这样就把目标位置前一个结点与目标位置后一个结点链接起来了,也就实现了链表删除操作。

6.数据测试

方法创建完成后,我们来进行一些数据测试,如下:

    /**
	 *********************
	 * The entrance of the program.
	 * 
	 * @param args Not used now.
	 *********************
	 */
	public static void main(String args[]) {
		LinkedList tempFirstList = new LinkedList();
		System.out.println("Initialized, the list is: " + tempFirstList.toString());

		for (int i = 0; i < 5; i++) {
			tempFirstList.insert(0, i);
		} // Of for i
		System.out.println("Inserted, the list is: " + tempFirstList.toString());

		tempFirstList.insert(6, 9);

		tempFirstList.delete(4);

		tempFirstList.delete(2);
		System.out.println("Deleted, the list is: " + tempFirstList.toString());

		tempFirstList.delete(0);
		System.out.println("Deleted, the list is: " + tempFirstList.toString());

		for (int i = 0; i < 5; i++) {
			tempFirstList.delete(0);
			System.out.println("Looped delete, the list is: " + tempFirstList.toString());
		} // Of for i
	}// Of main

7.完整的程序代码

package datastructure.list;

/**
 * Linked list.
 * 
 * @author Xin Lin 3101540094@qq.com.
 */

public class LinkedList {

	/**
	 * An inner class.
	 */
	class Node {
		/**
		 * The data.
		 */
		int data;

		/**
		 * The reference to the next node.
		 */
		Node next;

		/**
		 ******************* 
		 * The constructor
		 * 
		 * @param paraValue
		 *            The data.
		 ******************* 
		 */
		public Node(int paraValue) {
			data = paraValue;
			next = null;
		}// Of the constructor
	}// Of class Node

	/**
	 * The header node. The data is never used.
	 */
	Node header;

	/**
	 *********************
	 * Construct an empty linked list.
	 *********************
	 */
	public LinkedList() {
		header = new Node(0);
		//header.next = null; //Redundant
	}// Of the first constructor

	/**
	 *********************
	 * Overrides the method claimed in Object, the superclass of any class.
	 *********************
	 */
	public String toString() {
		String resultString = "";

		if (header.next == null) {
			return "empty";
		} // Of if

		Node tempNode = header.next;
		while (tempNode != null) {
			resultString += tempNode.data + ", ";
			tempNode = tempNode.next;
		} // Of while

		return resultString;
	}// Of toString

	/**
	 *********************
	 * Reset to empty. Free the space through garbage collection.
	 *********************
	 */
	public void reset() {
		header.next = null;
	}// Of reset

	/**
	 *********************
	 * Locate the given value. If it appears in multiple positions, simply
	 * return the first one.
	 * 
	 * @param paraValue
	 *            The given value.
	 * @return The position. -1 for not found.
	 *********************
	 */
	public int locate(int paraValue) {
		int tempPosition = -1;

		Node tempNode = header.next;
		int tempCurrentPosition = 0;
		while (tempNode != null) {
			if (tempNode.data == paraValue) {
				tempPosition = tempCurrentPosition;
				break;
			} // Of if

			tempNode = tempNode.next;
			tempCurrentPosition++;
		} // Of while

		return tempPosition;
	}// Of locate

	/**
	 *********************
	 * Insert a value to a position. If the list is already full, do nothing.
	 * 
	 * @param paraPosition
	 *            The given position.
	 * @param paraValue
	 *            The given value.
	 * @return Success or not.
	 *********************
	 */
	public boolean insert(int paraPosition, int paraValue) {
		Node tempNode = header;
		Node tempNewNode;

		for (int i = 0; i < paraPosition; i++) {
			if (tempNode.next == null) {
				System.out.println("The position " + paraPosition + " is illegal.");
				return false;
			} // Of if

			tempNode = tempNode.next;
		} // Of for i

		// Construct a new node.
		tempNewNode = new Node(paraValue);

		// Now link them.
		tempNewNode.next = tempNode.next;
		tempNode.next = tempNewNode;

		return true;
	}// Of insert

	/**
	 *********************
	 * Delete a value at a position.
	 * 
	 * @param paraPosition
	 *            The given position.
	 * @return Success or not.
	 *********************
	 */
	public boolean delete(int paraPosition) {
		if (header.next == null) {
			System.out.println("Cannot delete element from an empty list.");
			return false;
		} // Of if

		Node tempNode = header;

		for (int i = 0; i < paraPosition; i++) {
			if (tempNode.next.next == null) {
				System.out.println("The position " + paraPosition + " is illegal.");
				return false;
			} // Of if

			tempNode = tempNode.next;
		} // Of for i

		tempNode.next = tempNode.next.next;

		return true;
	}// Of delete

	/**
	 *********************
	 * The entrance of the program.
	 * 
	 * @param args Not used now.
	 *********************
	 */
	public static void main(String args[]) {
		LinkedList tempFirstList = new LinkedList();
		System.out.println("Initialized, the list is: " + tempFirstList.toString());

		for (int i = 0; i < 5; i++) {
			tempFirstList.insert(0, i);
		} // Of for i
		System.out.println("Inserted, the list is: " + tempFirstList.toString());

		tempFirstList.insert(6, 9);

		tempFirstList.delete(4);

		tempFirstList.delete(2);
		System.out.println("Deleted, the list is: " + tempFirstList.toString());

		tempFirstList.delete(0);
		System.out.println("Deleted, the list is: " + tempFirstList.toString());

		for (int i = 0; i < 5; i++) {
			tempFirstList.delete(0);
			System.out.println("Looped delete, the list is: " + tempFirstList.toString());
		} // Of for i
	}// Of main
}// Of class LinkedList

运行结果:

总结

昨天我们讨论了顺序表,由于其底层是一段连续的物理空间,所以当想要在顺序表中的任意位置插入或删除元素时,必须通过大段元素整体后移或前移才能完成,所以效率很低,时间复杂度为O(n),针对这一问题,java引入了链表结构。链表有一个非常好的性质就是在插入、删除元素时不用移动元素,只改变引用即可,所以效率比较高。但是链表也有不足的地方,就是当需要定位查找元素时,链表只能按链接顺序依次访问数据元素,时间复杂度为O(n)。

综上所述,顺序表的优势是支持随机访问,并且查找元素比较快,而当需要频繁进行插入、删除元素操作时,链表会更为适合。

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

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

相关文章

HarmonyOS 音视频之音频采集实战

HarmonyOS 音视频之音频采集实战 背景 应用开发过程中很多场景都有音频采集需求&#xff0c;比如聊天功能的发送语音功能&#xff0c;实时语音转文本功能&#xff0c;实时语音通话&#xff0c;实时视频通话等。在Android和iOS端&#xff0c;系统提供了两种形式&#xff1a; …

图+代码 | Bloom Filter实现及应用

什么是布隆过滤器&#xff08;Bloom Filter&#xff09;&#xff1f; 布隆过滤器是一种空间复杂度很低的概率型数据结构&#xff0c;用于判断一个元素是否在一个集合中。它有两种可能的返回结果&#xff1a; 元素可能在集合中&#xff1a;这可能是一个真阳性&#xff08;确实…

使用Variadic Templates(可变参数模板)实现printf

最近学习了C2.0版本的一些新的特性&#xff0c;利用Variadic Templates&#xff08;可变参数模板&#xff09;实现printf函数。 语言环境 Dev-C 5.11 并需要自己的环境是支持C11的&#xff0c;例如:Dev-C 5.11可以通过以下步骤进行修改&#xff1a; 源码 #include <io…

编程与AI:保持竞争力的策略

人工智能时代&#xff0c;程序员如何保持核心竞争力&#xff1f; 随着AIGC&#xff08;如chatgpt、midjourney、claude等&#xff09;大语言模型接二连三的涌现&#xff0c;AI辅助编程工具日益普及&#xff0c;程序员的工作方式正在发生深刻变革。有人担心AI可能取代部分编程工…

顶刊、顶会、水刊的论文读哪个,如何做一个称职的学术裁缝_来自B站水论文的程序猿

系列文章目录 文章目录 系列文章目录一、顶刊、顶会要看二、水刊&#xff1a;SCI4区&#xff0c;部分3区 看水刊&#xff0c;发现新大陆1、数据集2、性能3、复现 三、如何做一个称职的学术裁缝1、缝合2、编故事 一、顶刊、顶会要看 通过顶刊、顶会知道好的东西长什么样子即可&…

【从零开始一步步学习VSOA开发】创建VSOA的client端

创建VSOA的client端 创建工程 参考 hellovsoa 工程&#xff0c;创建 client 工程&#xff0c;工程源码修改如下&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/select.h> #include "vsoa_client.h&q…

全球情绪检测与识别市场规划预测:2030年市场规模将接近3166亿元,未来六年CAGR为22.6%

一、引言 随着人工智能和机器学习技术的飞速发展&#xff0c;情绪检测与识别在多个领域的应用日益广泛&#xff0c;其市场潜力日益凸显。本文旨在探索情绪检测与识别行业的发展趋势、潜在商机及其未来展望。 二、市场趋势 全球情绪检测与识别市场的增长主要受人工智能和机器学…

【mathtype】word中如何输入4×4的矩阵,甚至阶数更多

在写论文或者使用word操作的时候&#xff0c;我们可能会使用矩阵插入我们所写的word中&#xff0c;今天小编就分享一下如何在word中输入矩阵。首先&#xff0c;我们word中需要安装mathtype的插件。 ①打开word&#xff0c;鼠标点击mathtype&#xff0c;再点击内联 ② 出现以下…

C++学习记录

C学习 1.const,mutable,初始化列表,this指针2.static使用类友元作用友元全局函数友元类友元成员函数 运算符重载左移运算符 <<下标运算符赋值运算符括号运算符一元运算符 自动类型转换隐式类型转换 转换函数 继承继承方式构造函数继承名字遮蔽和类作用域名字覆盖类作用域…

大气能见度测量仪的功能优势:安心守护

大气能见度是衡量空气质量与交通安全的重要指标之一&#xff0c;它直接关系到人们的出行安全、航空航行的顺畅以及环境监测的准确性。为此&#xff0c;大气能见度测量仪应运而生&#xff0c;以其独特的功能优势&#xff0c;成为现代气象观测与交通管理中不可或缺的高科技设备。…

数据资源:机遇与挑战并存的新时代

数据资源&#xff1a;机遇与挑战并存的新时代 2023年全国数据生产总量达到32.85泽字节&#xff08;ZB&#xff09;&#xff0c;同比增长22.44%&#xff0c;数据生产规模持续扩大&#xff0c;其中非结构数据增长尤为突出&#xff0c;这得益于5G、AI技术的发展和智能设备的应用。…

ME31K-创建合同

ME31K创建合同 合同分为&#xff1a;数量合同和价值合同 CMK代表数量合同。CWK代表价值合同。 输入对应数量或价值后&#xff0c;点击保存。 点击保存后。系统会带出一个合同号。 可以引用这个合同号&#xff0c;去创建采购订单或者计划协议。

【从零开始一步步学习VSOA开发】创建VSOA的server端

创建VSOA的server端 创建工程 参考 hellovsoa 工程&#xff0c;创建 server 工程&#xff0c;工程源码修改如下&#xff1a; #include <stdio.h> #include <stdlib.h> #include <string.h> #include <netinet/in.h> #include <arpa/inet.h> #…

zabbix7.0TLS-02-客户端zabbix-agent部署和配置

文章目录 1 介绍2 工作模式2 部署2.1 RPM 包方式部署 3 配置3.1 配置文件位置3.2 主配置文件布局3.2.1 常规参数3.2.2 高级参数 3.3 部分配置参数详解3.3.1 通用参数3.3.2 被动模式参数3.3.3 主动模式参数 4 测试4.1 Agent 本机测试4.2 在服务端 Zabbix-server 测试和 Agent 的…

Opengl 安装

安装Cmake&#xff1a;Download CMake 安装GLFW&#xff08;source package&#xff09;:An OpenGL library | GLFW glad.dav1d.de 下载glad&#xff0c;选择version3.3 Core --Generate生成。然后点击glad.zip文件下载。 在cmake配置glfw&#xff1a;config-generate 构建GLF…

场外个股期权也可以随时平仓?场外期权行权是什么时候?

今天带你了解场外个股期权也可以随时平仓&#xff1f;场外期权行权是什么时候&#xff1f;A股场外个股有期权&#xff0c;主要对应的标的是沪深交易所上市的个股&#xff0c;除了st、次新股等受到限制&#xff0c;90%的股票都可以购买。 场外个股期权是否可以随时平仓 场外个…

MySQL数据库管理系统在Linux上安装部署

1. MySQL 5.7版本安装 1.1 安装 &#xff08;1&#xff09; 配置yum仓库 # 更新密钥 rpm --import https://repo.mysql.com/RPM-GPG-KEY-mysql-2022 # 安装Mysql yum库 rpm -Uvh http://repo.mysql.com/mysql57-community-release-el7-7.noarch.rpm 注意&#xff1a; 由于M…

OpenCV及rembg去除图像背景

OpenCV去除图像背景 去除图像背景&#xff0c;需要综合使用二值化&#xff08;thresholding&#xff09;、腐蚀&#xff08;erosion&#xff09;、膨胀&#xff08;dilation&#xff09;以及位运算&#xff08;bitwise operations&#xff09;&#xff0c;代码如下&#xff1a…

【独家原创RIME-CNN-LSSVM】基于霜冰优化算法优化卷积神经网络(CNN)结合最小二乘向量机(LSSVM)的数据回归预测

【独家原创RIME-CNN-LSSVM】基于霜冰优化算法优化卷积神经网络(CNN)结合最小二乘向量机(LSSVM)的数据回归预测 目录 【独家原创RIME-CNN-LSSVM】基于霜冰优化算法优化卷积神经网络(CNN)结合最小二乘向量机(LSSVM)的数据回归预测效果一览基本介绍程序设计参考资料 效果一览 基本…

成品油综合监管云平台:系统功能全方位解析

成品油综合监管云平台&#xff0c;作为集数据收集、统计分析、监管、预警应急安全等功能于一体的信息化监管系统&#xff0c;正逐步成为政府监管部门提升管理效率、保障市场秩序的重要工具。 本文将详细解析成品油综合监管云平台的多项核心功能&#xff0c;展现其在现代成品油…