链表Java

目录

实现链表

实现思路

链表简介

头指针和伪头

实现单链表所需的基本功能

代码展示

链表的应用:反转链表

实现思路

代码展示

代码讲解

总结


实现链表

实现思路

链表简介

说起链表来大家可能都听过,因为他的名气还不小。链表里面的数据在存储空间的分布是不连续的。这就给了它一个优势,即链表允许节点在链表的任意位置插入。虽然数组用来搜查数据很方便,但是只能把数据添加到数组的末尾,没有选择插入的余地。

不过不连续的分布也意味着我们需要在每个数据节点上做一个标识才能找到数据,同时我们需要一个链表头来确定该链表的开始位置。我们将这个标识称为指针,每一个节点都有一个指针,指向下一个节点。而为了操作方便,我们给链表头配置了一个头指针,并且还有一个临时指针,我们会通过它对目标节点并进行增删查操作。

单链表(无伪头)

节点里面有什么呢?节点包含数据项以及指向下一个节点。补充一下:由于在链表中,最后一个节点后面再没有节点,因此指向下一个节点的指针就是空指针null。在单链表的删除方法中我们还会见到它的。

头指针和伪头

有些读者可能会有一个想法:我们能不能让头指针成为临时指针,让他来遍历整个链表呢?这个想法是很好的,但是有些不现实。虽然每个节点都配有指针,但是我们实际上只知道头指针的位置——指向第一个节点。如果头指针自身变化了,如何才能找到第一个节点的位置呢?链表的数据在存储空间是分布的,一旦失去了我们能控制的指针,这数据就很难找到了。因此我们要重新创建一个临时指针,让他去遍历,而头指针尽量不动。

单链表(有伪头)

我们简单讲一下实现单链表的两种思路。一种是带有伪头和链表头的实现,一种只有链表头,我们这次代码实现的是第一种。什么是伪头呢?伪头是一个位于链表头前面的节点,内置头指针且不存储数据。奇怪,明明已经有链表头了,为什么要伪头呢?由于头指针确定了一个链表,因此我们如果想删掉链表头,或在其前面插入节点时,就需要先将头指针转移到其他满足条件的节点,再进行操作,十分麻烦。

因为伪头只有头指针没有值,所以它自身是不参与链表的基本操作的,换句话说,所有的操作都在它的后面进行。因此,使用了伪头,我们在增删操作中就不需要移动头指针,而且链表头可以永久的确定为伪头的下一个节点。另外,基于伪头永久不变的特点,我们可以创建一个起始于伪头节点的临时指针,通过.next操作,轻而易举地到达单链表内所有节点。需要注意的是,伪头的索引值是-1,链表头的索引值才是0,也就是说,伪头在隐藏空间里。

前面讲到,由于伪头已经起到链表头的作用了,因此我们在程序中会把伪头 (pseudo-head) 直接写成头 (head),这样不仅省了不少精力去写"pseudo"这生僻的词,同时真正的链表头也可以用head.next表示,十分方便。

实现单链表所需的基本功能

我们看看实现单链表所需的所有基本功能。

首先我们要在代码中定义一个节点。之前说过,一个节点包含下一个节点的指针和这一个节点的值。对于值的话我们可能需要重写构造方法,毕竟链表头的值是需要在链表初始化的时候传进去的。对于下一个节点的指针,我们这里有一个很有趣的语法。我们把下一个指针的类型设成当前节点的类型,使其既拥有自己的值,也拥有自己的“下一个指针”。我们把下一个指针用英文next表示,而值则用val表示,节点类型则是ListNode。

然后是查询功能query()。查询方法的参数是节点对应的索引index。这个时候就要求我们要通过索引找到对应节点,怎么办呢?我们可以先创建一个临时指针,令它等于伪头。然后用一个for循环,循环的的范围是 [0, index+1) ,并在循环中每次让临时指针指向下一个节点,这样循环结束后临时指针指向的就是该索引对应节点。我们返回该节点的值,查询任务完成!

到这里大家不免有点小疑惑,为什么循环变量不是小于index而是小于index+1呢?别忘了,伪头在隐藏空间里,索引是-1(链表头的索引才是0)。又因为临时指针被赋值为伪头,所以需要多循环一次才能查询到正确的节点值。

插入功能(addAtHead(), addAtTail(), addAtIndex() ):插入功能有三个,一个是将节点插入到链表头前,一个是将节点插入到链表尾,最后一个是将节点插入到链表的任意位置。有了伪头的帮助,这三种功能是统一的,也就是说前两个添加功能就是最后面那个方法的衍生,因此在写代码的时候不用额外费什么功夫,十分方便。我们现在介绍一下插入节点的方法。

插入节点

如图,我们创建了一个临时指针p,且想要节点newnode插入索引1。我们先使用for循环使得p到达索引0的节点,即newnode目标插入索引-1,然后开始操作——首先要让newnode.next=p.next,然后再让p.next=newnode。

这个操作顺序一定不能变,因为在第一步前,p根本找不到newnode这个节点,只有之后newnode自己连接p.next之后,p才知道newnode的位置。

删功能(deleteAtIndex()):删功能的参数是节点的索引。那如何删除呢?

删除节点

看上图我们不难理解,只要使被删的节点的前一个连接它的下一个节点,越过需要被删除的节点即可。deleteAtIndex()参数是索引,所以我们在删除之前还要有一步,就是让临时指针指向要删除的节点的上一个节点。那么我们需要使用到for循环,模式和插入方法的一样。

接下来是两个小功能,printhead()和print(),一个是打印链表头,一个是打印链表所有节点的值。


代码展示

package advance.dataStructure;

class ListNode {//special grammar
	public int val;
	public ListNode next;//pointer

	public ListNode() {
	}
	public ListNode(int val) {//overload
		this.val = val;
	}
}

public class MyListNode {
	int size;//链表长度
	ListNode head;//伪头
    //构造器
	public MyListNode() {
		size = 0;
		head = new ListNode(0);//设置伪头
	}
    //查询功能
	public int query(int index) {
		if (index < 0 || index >= size) {// 确定边界
			return -1;
		}
		ListNode cur = head;//设置临时指针
		for (int i = 0; i < index + 1; i++) {
			cur = cur.next;
		}
		return cur.val;
	}
    //添加功能:将节点添加至链表头前
	public void addAtHead(int val) {
		addAtIndex(0, val);
	}
    //添加功能:节点添加至链表末尾
	public void addAtTail(int val) {
		addAtIndex(size, val);
	}
    //添加功能:插入节点至任意位置
	public void addAtIndex(int index, int val) {
		if (index > size)// 确定边界
			return;
		else if (index < 0)//确定边界
			index = 0;
		size++;
		ListNode pre = head;
		for (int i = 0; i < index; i++) {
			pre = pre.next;
		}
		ListNode newnode = new ListNode(val);//创建新点
		newnode.next = pre.next;
		pre.next = newnode;
	}
    //删除值(参数为节点对应索引)
	public void deleteAtIndex(int index) {
		if (index < 0 || index >= size)//设置边界
			return;
		size--;
		ListNode pre = head;//设置临时指针
		for (int i = 0; i < index; i++) {//或者: while(pre.next!=null)
			pre = pre.next;
		}
		pre.next = pre.next.next;
	}
    //打印链表头的值
	public void printhead() {
		System.out.println(this.head.next.val);
	}
    //打印链表的所有节点值
	public void print() {
		ListNode temp = head;
		while (temp != null) {
			if(temp.next!=null)
				System.out.print(temp.val+", ");
			else
				System.out.println(temp.val);
			temp = temp.next;
		}
    }
}
package advance.dataStructure;
//测试
public class Main {
	public static void main(String[] args) {
		MyListNode linklist = new MyListNode();
		int[] arr={4,3,5,2,1};
		for(int i=0;i<arr.length;i++)
			linklist.addAtIndex(i, arr[i]);//快速新建多个节点
		linklist.deleteAtIndex(0);//删除链表头
		linklist.print();//打印链表所有节点的值
		linklist.printhead();//打印链表头
	}
}

 

链表的应用:反转链表

实现思路

反转链表有很多种思路,我们今天要讲的这个思路比较直接,不算难。

首先我们调用之前创建的MyListNode()类,找到head属性。根据之前说的,head是个伪头,本身不承载数据。所以我们就可以利用这一点,将head.next作为链表头,然后把后面的节点都陆续移到它前面,直到head.next.next是null——这说明链表头后面没有可操作节点了,于是完成反转链表操作!

这里的细节就在于:要如何才能将链表头后面的节点移动其前面呢?很简单,先使用addAtHead()方法,把链表头的下一个节点复制到链表头之前,然后再删除链表头的下一个节点就大功告成了。我们来看它的代码是怎么实现的。


代码展示

package advance.dataStructure;

class ReverseLinkList {
	public MyListNode linklist = new MyListNode();//创建MyListNode的实例对象linklist
    //反转链表
	public void reverse(int[] arr) {
		for (int e : arr) {
			linklist.addAtTail(e);
		}
		ListNode head2 = linklist.head.next;//创建链表头
		while (head2.next != null) {
			linklist.addAtHead(head2.next.val);
			head2.next=head2.next.next;//删除节点
		}
		head2=linklist.head.next;//更新链表头
	}
    //打印链表所有节点的值
	public void printr() {
		ListNode temp = linklist.head;
		while (temp != null) {
			System.out.println(temp.val);
			temp = temp.next;
		}
	}
};
//测试
public class Main{
	public static void main(String[] args) {
		ReverseLinkList obj = new ReverseLinkList();
		int[] arr = { 2, 1, 5, 3 };
		obj.reverse(arr);
		obj.printr();
	}
}

代码讲解

在Main类中的静态main方法中,我们希望把数组快速转换成链表。这里由于之前在自定义链表的时候没有创建一个像当时创建栈与队列的cvtArr()函数,大家可以自行添加。在外层,我们能看到reverse还自动携带”数组转换器“,并且可以通过调用printr()方法打印反转后的链表,方便我们通过链表的状态进行代码修改。

reverse() 方法中的内容在实现思路中就已经讲的七七八八,加上代码本身也极易理解,这里就不再赘述。


总结

链表优点很多,它插入删除速度快,内存利用率高,大小没固定所以拓展很灵活。像数组,他的长度是固定的,只能事前多预留点,相比之下,虽然链表的原理较数组还是复杂了些,但是我们不能否认它优越的性能。

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


学习编程是顺着互联网的发展潮流,是一件好事。新手如何学习编程?其实不难,不过在学习编程之前你得先了解你的目的是什么?这个很重要,因为目的决定你的发展方向、决定你的发展速度。
IT行业是什么工作做什么?IT行业的工作有:产品策划类、页面设计类、前端与移动、开发与测试、营销推广类、数据运营类、运营维护类、游戏相关类等,根据不同的分类下面有细分了不同的岗位。
女生学Java好就业吗?女生适合学Java编程吗?目前有不少女生学习Java开发,但要结合自身的情况,先了解自己适不适合去学习Java,不要盲目的选择不适合自己的Java培训班进行学习。只要肯下功夫钻研,多看、多想、多练
Can’t connect to local MySQL server through socket \'/var/lib/mysql/mysql.sock问题 1.进入mysql路径
oracle基本命令 一、登录操作 1.管理员登录 # 管理员登录 sqlplus / as sysdba 2.普通用户登录
一、背景 因为项目中需要通北京网络,所以需要连vpn,但是服务器有时候会断掉,所以写个shell脚本每五分钟去判断是否连接,于是就有下面的shell脚本。
BETWEEN 操作符选取介于两个值之间的数据范围内的值。这些值可以是数值、文本或者日期。
假如你已经使用过苹果开发者中心上架app,你肯定知道在苹果开发者中心的web界面,无法直接提交ipa文件,而是需要使用第三方工具,将ipa文件上传到构建版本,开...
下面的 SQL 语句指定了两个别名,一个是 name 列的别名,一个是 country 列的别名。**提示:**如果列名称包含空格,要求使用双引号或方括号:
在使用H5混合开发的app打包后,需要将ipa文件上传到appstore进行发布,就需要去苹果开发者中心进行发布。​
+----+--------------+---------------------------+-------+---------+
数组的声明并不是声明一个个单独的变量,比如 number0、number1、...、number99,而是声明一个数组变量,比如 numbers,然后使用 nu...
第一步:到appuploader官网下载辅助工具和iCloud驱动,使用前面创建的AppID登录。
如需删除表中的列,请使用下面的语法(请注意,某些数据库系统不允许这种在数据库表中删除列的方式):
前不久在制作win11pe,制作了一版,1.26GB,太大了,不满意,想再裁剪下,发现这次dism mount正常,commit或discard巨慢,以前都很快...
赛门铁克各个版本概览:https://knowledge.broadcom.com/external/article?legacyId=tech163829
实测Python 3.6.6用pip 21.3.1,再高就报错了,Python 3.10.7用pip 22.3.1是可以的
Broadcom Corporation (博通公司,股票代号AVGO)是全球领先的有线和无线通信半导体公司。其产品实现向家庭、 办公室和移动环境以及在这些环境...
发现个问题,server2016上安装了c4d这些版本,低版本的正常显示窗格,但红色圈出的高版本c4d打开后不显示窗格,
TAT:https://cloud.tencent.com/document/product/1340