实现单链表的基本操作(力扣、牛客刷题的基础笔试题常客)

本节来学习单链表的实现。在链表的刷题中,单链表占主导地位,很多oj题都在在单链表的背景下进行;而且很多链表的面试题都是以单链表为背景命题。所以,学好单链表的基本操作很重要

目录

一.介绍单链表

1.链表及单链表

2.定义一个链表

二.实现单链表的功能

1.插入数据

2.打印链表

3.删除数据

4.查找某个元素

5.检测链表大小

6.完整的链表


一.介绍单链表

1.链表及单链表

(1)什么是链表

链表是一种物理存储结构上非连续存储结构,数据元素的逻辑顺序是通过链表中的引用链接次序实现的。

例如下面的这种数据结构,由一个个的结点组成。每个结点中存储着数据,又存储着其他结点的地址。

(2)什么是单链表

链表有三个特点:单向和双向、带头和不带头、循环和不循环;三三组合起来,一共8种情况(比如单向不带头不循环链表,就是本节的单向链表)。

单向和双向:单向表示每个结点只存后一个结点的地址;双向表示每个结点存放前后结点的地址。

区别:双向链表可以知道某个结点的前面结点,单向链表只能找到它后面的结点

带头和不带头:带头的链表会有一个固定的头结点(也称为哨兵位),所有操作都在头结点后面操作;不带头的链表则会自己定义一个结点用来表示当前链表的头,该结点也称为头结点,但是该头结点的位置是不停的变化的

区别:带头的链表,头结点的位置是固定不变的 

循环和非循环:循环的链表,最后一个结点存放第一个结点的地址,非循环的链表最后一个结点存放的地址为null,也就是不指向任何的结点

区别:循环的链表也可以称为环,链表的遍历不会结束,而非循环会结束

本节介绍的单链表为:单向不带头非循环的链表,如下图的链表

 

2.定义一个链表

(1)定义一个链表(单独一个类)

public class MyList{}

(2)将链表的功能包装称接口

public interface IList {public void addFirst(int data);//头插public void addLast(int data);//尾插public void add(int index,int data);//任意位置插入public boolean contains(int key);//检查key元素是否存在public void remove(int key);//删除第一个keypublic void removeAll(int key);//删除所有keypublic int size();//求链表的长度public void clear();//清空链表public void show();//打印链表}

(3)链表实现该接口并重写方法

public class MyList implements IList{@Overridepublic void addFirst(int data) {//头插}@Overridepublic void addLast(int data) {//尾插    }@Overridepublic void add(int index, int data) {//指定位置插入}@Overridepublic boolean contains(int key) {//查找key元素}@Overridepublic void remove(int key) {//删除第一个key     }@Overridepublic void removeAll(int key) {//删除所有key结点}@Overridepublic int size() {//求链表大小}@Overridepublic void clear() {//清空链表}@Overridepublic void show() {}
}

(4)定义链表的结点

我们将结点定义成一个内部类:包括数据域(data)和next域(存放下一个结点的地址)

public class MyList implements IList{class ListNode {public int data;//数据域public ListNode next;public ListNode(int data) {this.data = data;}}public ListNode head;//定位头的位置@Overridepublic void addFirst(int data) {//头插}@Overridepublic void addLast(int data) {//尾插    }@Overridepublic void add(int index, int data) {//指定位置插入}@Overridepublic boolean contains(int key) {//查找key元素}@Overridepublic void remove(int key) {//删除第一个key     }@Overridepublic void removeAll(int key) {//删除所有key结点}@Overridepublic int size() {//求链表大小}@Overridepublic void clear() {//清空链表}@Overridepublic void show() {}
}

这样一个链表的基本结构就定义完成,接下来就是实现链表的一些功能即可

二.实现单链表的功能

链表的功能大概有以下几种,插入数据(头插,尾插,随机插入),打印链表的数据,删除链表的数据,查找某个元素和监测链表的大小。

接下来我们慢慢了解

1.插入数据

(1)头插法

下面的是头插法的代码

public void addFirst(int data) {//头插ListNode node = new ListNode(data);node.next = head;//head = node;
}

第一步:需要创造一个新的结点出来

第二步:连接链表;分为两种情况:第一种是空链表的时候(一个结点都没有的时候),另一种是非空链表的时候。以上的代码都满足

 

(2)尾插法

尾插法的逻辑稍微复杂一点点,同样需要考虑链表的两种情况;空链表时需要单独讨论,而当链表非空时,则需要找链表的尾巴。

第一步:创造新的结点

ListNode node = new ListNode(data);

第二步:考虑空链表的情况

if(head == null) {head = node;return;
}

 第三步:找链表的尾巴

这个注意,我们不能移动head,head需要保持不动,不然链表的头将不见。

ListNode cur = head;
while(cur.next!=null) {cur = cur.next;
}

第四步:将新的结点连接到尾结点后面即可

cur.next = node;

完整的尾插法代码:

 public void addLast(int data) {//尾插ListNode node = new ListNode(data);if(head == null) {head = node;return;}ListNode cur = head;while(cur.next!=null) {cur = cur.next;}cur.next = node;}

(3)随机位置插入

 public void add(int index, int data)

随机位置插入,需要用户指定插入的位置和值;所以需要讨论以下的情况

第一步:创造新的结点

ListNode node = new ListNode(data);

第二步:检查用户指定插入的位置是否合法

 if(index < 0 || index > size()) {System.out.println("插入位置不合法");return;
}

第三步:检查链表是否为空

 if(head == null) {head = node;return;
}

第四步:检查是否为头插法

如果是头插法就直接调用头插法就可以,不需要再浪费时间去写这个代码。尾插法需要单独考虑,和普通插入当成一种即可。

 if(index == 0) {addFirst(data);return;
}

第五步:找到插入位置的前一个结点

在单链表中,只能找前一个的位置,如果找的是后一个位置,将无法获取前结点的信息

int count = index-1;
ListNode cur = head;
while(count > 0) {cur = cur.next;count--;
}

此时的cur指向插入位置的前一个结点

第六步:插入新的结点

插入新的结点都是要求连接后面,再连接前面

node.next = cur.next;
cur.next = node;

完整代码:

 public void add(int index, int data) {//指定位置插入ListNode node = new ListNode(data);ListNode cur = head;//1.检查位置是否合法if(index < 0 || index > size()) {System.out.println("插入位置不合法");return;}//2.空链表情况if(head == null) {head = node;return;}//3.头插法情况(index = 0)if(index == 0) {addFirst(data);return;}//4.找前位置int count = index-1;while(count > 0) {cur = cur.next;count--;}//5.插入数据node.next = cur.next;cur.next = node;}

2.打印链表

打印链表比较简单,就是需要将链表遍历一遍即可,同样的道理,不能动head

  public void show() {//打印ListNode cur = head;while(cur!=null) {System.out.print(cur.data+" ");cur = cur.next;}}
3.删除数据

删除数据分为两种:一种是删除一个key结点;另一种是删除所以的key结点。删除的参数都是根据结点的值来判断

下面我们一起来查看这两种的代码和思路

(1)删除第一个key结点

第一步:判断是否为空链表

如果是空链表的情况,无论如果都无法删除,直接返回就好

 if(head == null) {System.out.println("链表为空,无法删除");return;}

第二步:单独考虑头结点是否是目标结点

 if(head.data == key) {head = head.next;return;
}

第三步:一边遍历链表一边删除结点

这里只需要遍历一遍就可以完成,不需要再找什么前结点;下面的代码是判断下一个结点是否为删除的结点,如果是,直接断开连接就好,不是则继续往下走

 ListNode cur = head;while(cur.next!=null) {if(cur.next.data==key) {cur.next = cur.next.next;//删除操作return;}else {cur = cur.next;}}

第四步:链表中不存在key结点

System.out.println("没有该结点,删除失败!");

完整代码:

public void remove(int key) {//删除第一个key//1.空表if(head == null) {System.out.println("链表为空,无法删除");return;}//2.头结点就是目标结点if(head.data == key) {head = head.next;return;}//3.正常删除ListNode cur = head;while(cur.next!=null) {if(cur.next.data==key) {cur.next = cur.next.next;//删除操作return;}else {cur = cur.next;}}//4.找不到System.out.println("没有该结点,删除失败!");}

(2)删除所有的key结点

删除所有的结点是在删除一个key结点的前提下改进即可,删除一个key结点时,直接返回了,然后删除所有的key,我们不返回就好。下面分三步

第一步:判断是否为空链表

 if(head == null) {System.out.println("链表为空,无法删除");return;}

第二步:删除头结点后面的所有key结点

下面的代码无法删除头结点,所以头结点的情况单独考虑并且放在最后面 

        ListNode cur = head.next;//需要删除的结点ListNode prev = head;//删除结点的前一个//2.删除中间的结点while(cur != null) {if(cur.data != key) {prev = cur;//让prev走到cur的位置cur = cur.next;//cur往下走}else {prev.next = cur.next;cur = cur.next;}}

第三步:删除头结点

if(head.data == key) {head = head.next;
}

完整代码:

 public void removeAll(int key) {//删除所有key结点//1.检查空链表情况if(head == null) {System.out.println("链表为空,无法删除");return;}ListNode cur = head.next;//需要删除的结点ListNode prev = head;//删除结点的前一个//2.删除中间的结点while(cur != null) {if(cur.data != key) {prev = cur;//让prev走到cur的位置cur = cur.next;//cur往下走}else {prev.next = cur.next;cur = cur.next;}}//3.删除头结点if(head.data == key) {head = head.next;}}
4.查找某个元素

查找某个元素是否存在时,根据结点的值去查找

如果结点存在,返回true;不存在则返回false

 public boolean contains(int key) {//查找key元素ListNode cur = head;while(cur!=null) {if(cur.data == key) {return true;}cur = cur.next;}return false;}
5.检测链表大小

求链表的结点个数只需要遍历一遍链表即可,每走到一个结点的位置,计数器就家加1,最后返回即可

  public int size() {//求链表大小ListNode cur = head;int count = 0;while(cur!=null) {count++;cur = cur.next;}return count;}
6.完整的链表
public class MyList implements IList{class ListNode {public int data;//数据域public ListNode next;public ListNode(int data) {this.data = data;}}public ListNode head;//定位头的位置@Overridepublic void addFirst(int data) {//头插ListNode node = new ListNode(data);node.next = head;//head = node;}@Overridepublic void addLast(int data) {//尾插ListNode node = new ListNode(data);if(head == null) {head = node;return;}ListNode cur = head;while(cur.next!=null) {cur = cur.next;}cur.next = node;}@Overridepublic void add(int index, int data) {//指定位置插入ListNode node = new ListNode(data);//1.检查位置是否合法if(index < 0 || index > size()) {System.out.println("插入位置不合法");return;}//2.空链表情况if(head == null) {head = node;return;}//3.头插法情况(index = 0)if(index == 0) {addFirst(data);return;}//4.找前位置int count = index-1;ListNode cur = head;while(count > 0) {cur = cur.next;count--;}//5.插入数据node.next = cur.next;cur.next = node;}@Overridepublic boolean contains(int key) {//查找key元素ListNode cur = head;while(cur!=null) {if(cur.data == key) {return true;}cur = cur.next;}return false;}@Overridepublic void remove(int key) {//删除第一个key//1.空表if(head == null) {System.out.println("链表为空,无法删除");return;}//2.头结点就是目标结点if(head.data == key) {head = head.next;return;}//3.正常删除ListNode cur = head;while(cur.next!=null) {if(cur.next.data==key) {cur.next = cur.next.next;//删除操作return;}else {cur = cur.next;}}//4.找不到System.out.println("没有该结点,删除失败!");}@Overridepublic void removeAll(int key) {//删除所有key结点//1.检查空链表情况if(head == null) {System.out.println("链表为空,无法删除");return;}ListNode cur = head.next;//需要删除的结点ListNode prev = head;//删除结点的前一个//2.删除中间的结点while(cur != null) {if(cur.data != key) {prev = cur;//让prev走到cur的位置cur = cur.next;//cur往下走}else {prev.next = cur.next;cur = cur.next;}}//3.删除头结点if(head.data == key) {head = head.next;}}@Overridepublic int size() {//求链表大小ListNode cur = head;int count = 0;while(cur!=null) {count++;cur = cur.next;}return count;}@Overridepublic void clear() {//清空链表head = null;}@Overridepublic void show() {//打印ListNode cur = head;while(cur!=null) {System.out.print(cur.data+" ");cur = cur.next;}}}

接口:

public interface IList {public void addFirst(int data);//头插public void addLast(int data);//尾插public void add(int index,int data);//任意位置插入public boolean contains(int key);//检查key元素是否存在public void remove(int key);//删除第一个keypublic void removeAll(int key);//删除所有keypublic int size();//求链表的长度public void clear();//清空链表public void show();//打印链表}

实例化链表对象:

 public static void main(String[] args) {MyList myList = new MyList();//实例化链表对象myList.addLast(8);myList.addLast(1);myList.addLast(4);myList.show();}

本节单链表的实现就到这里了,快去自己模拟实现一下吧!

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

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

相关文章

JVM垃圾收集器三色标记算法

垃圾收集算法 分代收集理论 当前虚拟机的垃圾收集都采用分代收集算法&#xff0c;这种算法没有什么新的思想&#xff0c;只是根据对象存活周期的不同将内存分为几块。一般将java堆分为新生代和老年代&#xff0c;这样我们就可以根据各个年代的特点选择合适的垃圾收集算法。 比…

day44代码训练|动态规划part06

完全背包和01背包问题唯一不同的地方就是&#xff0c;每种物品有无限件。 1. dp数组的含义 dp[i][j] 0-i物品&#xff0c;重量为j的容量时&#xff0c;最大的价值 2. 递推公式 dp[i][j] max(dp[i-1][j],dp[i][j-weight[i]]value[i]); 两种状态&#xff0c;不用物品i的话&…

22.VRRP网关冗余

VRRP网关冗余 STP 是二层冗余技术 VRRP是三层冗余技术 这里如果网关挂掉的话&#xff0c;用户就无法访问外网&#xff0c;还得配置新的网关&#xff0c;VRRP就是用来解决这种问题的 原理&#xff1a;可以把R1 的优先级设置的高一点&#xff0c;先由R1 来掌管192.168.1.254这个…

linux 性能优化-内存优化

CPU 管理一样&#xff0c;内存管理也是操作系统最核心的功能之一。内存主要用来存储系统和应 用程序的指令、数据、缓存等。 1.内存原理 1.1.内存映射 1.1.1.日常生活常说的内存是什么? 我的笔记本电脑内存就是 8GB 的这个内存其实是物理内存物理内存也称为主存&#xff0…

Redis原理之网络模型笔记

目录 1. 阻塞IO 2. 非堵塞IO 3. IO多路复用 ​3.1 select 3.2 poll 3.3 epoll 4. 信号驱动IO 5. 异步IO 6. Redis是单线程还是多线程 Redis采用单线程模型&#xff0c;这意味着一个Redis服务器在任何时刻都只会处理一个请求。Redis的网络模型涉及到阻塞I/O&#xff08;Blo…

【图神经网络】在节点分类任务中无特征节点的特征表示

无特征节点的特征表示 节点度数degree pagerank 以pagerank起源的应用场景为例&#xff0c;不是所有的网站都是同等重要的&#xff0c;所以需要根据结构信息对节点进行排序。 直觉上&#xff0c;如果一个网站它有很多链接&#xff0c;它就很重要&#xff0c;举例来说&#…

Leetcode—77.组合【中等】

2023每日刷题&#xff08;六十五&#xff09; Leetcode—77.组合 算法思想 实现代码 class Solution { public:vector<vector<int>> combine(int n, int k) {vector<vector<int>> ans;vector<int> path;function<void(int)> dfs [&…

tcp vegas 为什么好

我吹捧 bbr 时曾论证过它在和 buffer 拧巴的时候表现如何优秀&#xff0c;但这一次说 vegas 时&#xff0c;我说的是从拥塞控制这个问题本身看来&#xff0c;vegas 为什么好&#xff0c;并且正确。 接着昨天 tcp vegas 鉴赏 继续扯。 假设一群共享带宽的流量中有流量退出或有…

JavaGUI(但期末速成版)之容器和控件

点击返回标题->JavaGUI期末速成版-CSDN博客 前言 依旧先声明&#xff0c;本篇记录的JavaGUI编程都是十分精简的&#xff0c;内容只取常用的、套路的、应付期末考试的。 在学习本篇之前&#xff0c;很有必要先弄清楚Java基于swing包下的图形化编程的层次逻辑。 在前一篇中&a…

在 TensorFlow 中启用 Eager Execution

TensorFlow 是一个端到端的开源机器学习平台&#xff0c;可以更轻松地构建和部署机器学习模型。TensorFlow 应用程序使用一种称为数据流图的结构。默认情况下&#xff0c;在 TensorFlow 1.0 版中&#xff0c;每个图形都必须在 TensorFlow 会话中运行&#xff0c;这只允许一次运…

ansible的脚本---playbook剧本

ansible的脚本---playbook剧本 playbook组成部分 1、tasks任务&#xff1a;包含要在目标主机上执行的操作&#xff0c;使用模块定义这些操作&#xff0c;每个任务都是一个模块的调用 2、varlables变量&#xff1a;存储和传递数据&#xff0c;变量可以自定义&#xff0c;可以…

linux中vim命令修改jar包中的文件内容

文章目录 概述vim命令修改配置文件 概述 首先问问为什么要直接修改jar包中的文件&#xff0c;而不是重新打包&#xff0c;在非必要的情况下&#xff0c;不要直接修改jar包&#xff0c;这样容易出事故&#xff1b; 当然也有一些场景不得不修改jar包&#xff0c;比如&#xff1a…

【Linux系统编程】进程的认识

介绍&#xff1a; 进程是程序执行的实体&#xff0c;可将其理解为程序。比如&#xff1a;当我们使用文本编辑器Notepad应用程序来编写一篇文章时&#xff0c;此时&#xff0c;Notepad应用程序就被加载到了内存中&#xff0c;并且它占用的资源&#xff08;如内存、CPU等&#xf…

Vuex的学习-2

Vuex的核心概念 StateMutationAction 1.State State提供唯一的公共数据源&#xff0c;所有共享的数据都统一放在Store的State中进行存储。 const store new Vuex.Store({state : { count: 0 } }) 这是渲染的页面 组件访问数据的第一种方式 组件访问数据的第二种方式 // 1…

MSPM0L1306例程学习-ADC部分(4)

MSPM0L1306例程学习系列 使用的TI的官方例程&#xff0c;即SDK里边包含的例程代码。 可以到TI官网下载并且安装SDK: https://www.ti.com.cn/tool/cn/download/MSPM0-SDK/ MCU使用的是MSPM0L1306, 对于ADC部分&#xff0c;有10个例程&#xff1a; 今天要讲的例程是adc12_14bit…

mac电脑m1 arm架构安装虚拟机教程

1、准备一台虚拟机&#xff0c;安装CentOS7 常用的虚拟化软件有两种&#xff1a; VirtualBoxVMware 这里我们使用VirtualBox来安装虚拟机&#xff0c;下载地址&#xff1a;Downloads – Oracle VM VirtualBox 001 点击安装 002 报错&#xff1a;he installer has detected an…

用模型预测测试数据

Hi, I’m Shendi 2、用模型预测测试数据 在之前已经训练好了一个模型&#xff0c;可以通过 model.save("path") 来保存模型到硬盘&#xff0c;下次直接使用。 这个模型使用的 mnist 数据集训练&#xff0c;这个数据集包含6万训练样本和一万测试样本&#xff0c;28*28…

linux下的进程组与会话的区别

进程组&#xff08;Process Group&#xff09;和会话&#xff08;Session&#xff09;是Unix/Linux操作系统中的两个概念&#xff0c;它们之间有一些关键区别&#xff1a; 定义和范围&#xff1a;一个进程组是一组相关进程的集合&#xff0c;它们具有相同的进程组ID&#xff08…

【运维面试100问】(十一)淡淡I/O过程

本站以分享各种运维经验和运维所需要的技能为主 《python零基础入门》&#xff1a;python零基础入门学习 《python运维脚本》&#xff1a; python运维脚本实践 《shell》&#xff1a;shell学习 《terraform》持续更新中&#xff1a;terraform_Aws学习零基础入门到最佳实战 《k8…

华为云之ECS云产品快速入门

华为云之ECS云产品快速入门 一、ECS云服务器介绍二、本次实践目标三、创建虚拟私有云VPC1.虚拟私有云VPC介绍2.进入虚拟私有云VPC管理页面3.创建虚拟私有云4.查看创建的VPC 四、创建弹性云服务器ECS——Linux1.进入ECS购买界面2.创建弹性云服务器(Linux)——基础配置步骤3.创建…