Java数据结构与算法04——单向链表

news/2024/11/28 10:58:36/

标签 : Java 数据结构 算法

作者 : Maxchen

版本 : V1.0.1

日期 : 2020/4/2


目录

  • 1.单向链表——原理
  • 2.单向链表——代码实现
    • 2.1单向链表——新增与查询
    • 2.2单向链表——修改
    • 2.3单向链表——删除
  • 3.单向链表——整体代码

1.单向链表——原理

假如我们现在要存放一些物品,但是没有足够大的空间将所有的物品一次性放下(电脑中使用链式存储不是因为内存不够先事先说明一下…,具体原因后续会说到),同时设定我们因为脑容量很小,为了节省空间,只能记住一件物品位置。此时我们很机智的找到了解决方案:存放物品时每放置一件物品就在物品上贴一个小纸条,标明下一件物品放在那里,只记住第一件物品的位置,寻找的时候从第一件物品开始寻找,通过小纸条我们可以找到所有的物品,这就是链式存储。链表实现的时候不再像线性表一样只存储数据即可,还有下一个数据元素的地址,因此先定义一个节点类(Node),记录物品信息和下一件物品的位置,我们把物品本身叫做数据域,存储下一件物品地址信息的小纸条称为引用域。链表结构示意图如下:1

image.png-26.5kB

提示: 寻找物品的时候发现了一个问题,我们从一件物品找下一件物品的时候很容易,但是如果要找上一件物品就得从头开始找,真的很麻烦。为了解决这个问题我们又机智了一把,模仿之前的做法,在存放物品的时候多放置一个小纸条记录上一件物品的位置,这样就可以很快的找到上一件物品了。我们把这种方式我们称为双向链表,前面只放置一张小纸条的方式称为单向链表

2.单向链表——代码实现

代码整体实现如下图所示:首先构建一个单链表,然后初始化头节点,随后往下面添加节点,最后打印整个链表的数据。

单链表.png-13.5kB

2.1单向链表——新增与查询

第一步: 对节点进行定义,我们这里以平常经常使用的几个大品牌电脑为例,包含以下字段:序号、品牌名称、品牌外号、指向下一个节点。

class ComputerNode {public int no; //序号public String name; //品牌名称public String nickname; //品牌外号public ComputerNode next; //指向下一个节点//构造器public ComputerNode(int no, String name, String nickname) {this.no = no;this.name = name;this.nickname = nickname;}//为了显示方法,我们重新toString@Overridepublic String toString() {return "ComputerNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";}}

第二步: 定义一个单链表并初始化头节点

class SingleLinkedList {//先初始化一个头节点,头不存放具体的数据private ComputerNode head = new ComputerNode(0, "", "");}

第三步: 往这个单链表添加节点

class SingleLinkedList {……//添加节点到单向链表public void add(ComputerNode computerNode) {//因为head节点不能动,因此我们需要一个辅助遍历 tempComputerNode temp = head;//遍历链表,找到最后while(true) {//找到链表的最后if(temp.next == null) {//next字段为空,则表示为链表结尾break;}//如果没有找到最后,next字段不为空 将temp后移temp = temp.next;}//当退出while循环时,temp就指向了链表的最后//将最后这个节点的next指向新的节点temp.next = computerNode;}
}

第四步: 定以显示链表的方法

class SingleLinkedList {……//显示链表[遍历]public void list() {//判断链表是否为空if(head.next == null) {System.out.println("链表为空");return;}//因为头节点,不能动,因此我们需要一个辅助变量来遍历ComputerNode temp = head.next;while(true) {//判断是否到链表最后if(temp == null) {break;}//输出节点的信息System.out.println(temp);//将temp后移, 一定小心temp = temp.next;}}
}

第五步: 我们对以上方法进行一次整体的测试,可以看到一种现象,链表最后展示的结果是按照代码添加的顺序排列的

public class SingleLinkedListDemo {public static void main(String[] args) {//先创建节点ComputerNode computer1 = new ComputerNode(1, "联想", "美帝良心想");ComputerNode computer2 = new ComputerNode(2, "惠普", "铁板熊掌普");ComputerNode computer4 = new ComputerNode(4, "戴尔", "人傻钱多戴");ComputerNode computer3 = new ComputerNode(3, "华硕", "奸若磐石硕");//创建要给链表并将节点加入到链表SingleLinkedList singleLinkedList = new SingleLinkedList();singleLinkedList.add(computer1);singleLinkedList.add(computer2);singleLinkedList.add(computer4);singleLinkedList.add(computer3);//显示链表,最后得到的结果如下,显然链表最后展示的结果是按照代码添加的顺序排列的// ComputerNode [no=1, name=联想, nickname=美帝良心想]// ComputerNode [no=2, name=惠普, nickname=铁板熊掌普]// ComputerNode [no=4, name=戴尔, nickname=人傻钱多戴]// ComputerNode [no=3, name=华硕, nickname=奸若磐石硕]singleLinkedList.list();}}

image.png-31.3kB

第六步: 我们在前面基础上增加一个要求:按照编号顺序排序,并且重复编号的值不能添加

class SingleLinkedList {……//第二种方式在添加电脑时,根据编号顺序将电脑插入到指定位置public void addByOrder(ComputerNode computerNode) {//头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置//因为单链表,因为我们找的temp 是位于 添加位置的前一个节点,否则插入不了ComputerNode temp = head;boolean flag = false; // flag标志添加的编号是否存在,默认为falsewhile(true) {if(temp.next == null) {//说明temp已经在链表的最后break;}if(temp.next.no > computerNode.no) { //位置找到,就在temp的后面插入break;} else if (temp.next.no == computerNode.no) {//说明希望添加的heroNode的编号已然存在flag = true; //说明编号存在break;}temp = temp.next; //后移,遍历当前链表}//判断flag 的值if(flag) { //不能添加,说明编号存在System.out.printf("准备插入的电脑的编号 %d 已经存在了, 不能加入\n", computerNode.no);} else {//插入到链表中, temp的后面computerNode.next = temp.next;temp.next = computerNode;}}}

第七步:add方法改为addByOrder并测试,发现链表能按照编号顺序添加节点

public class SingleLinkedListDemo {public static void main(String[] args) {……//创建要给链表并将节点加入到链表SingleLinkedList singleLinkedList = new SingleLinkedList();/*singleLinkedList.add(computer1);singleLinkedList.add(computer2);singleLinkedList.add(computer4);singleLinkedList.add(computer3);*/singleLinkedList.addByOrder(computer1);singleLinkedList.addByOrder(computer2);singleLinkedList.addByOrder(computer4);singleLinkedList.addByOrder(computer3);……}}

image.png-40kB

第八步: 我们测试一下重复添加某个节点会出现何种情况

public class SingleLinkedListDemo {public static void main(String[] args) {……//重复添加computer3singleLinkedList.addByOrder(computer1);singleLinkedList.addByOrder(computer2);singleLinkedList.addByOrder(computer3);singleLinkedList.addByOrder(computer3);……}}

image.png-31.8kB

2.2单向链表——修改

单向链表修改参考下图,直接找到对应的节点修改内容即可

image.png-30.6kB

第一步: 定义单链表修改方法

class SingleLinkedList {……//修改节点的信息, 根据no编号来修改,即no编号不能改.//说明//1. 根据 newComputerNode 的 no 来修改即可public void update(ComputerNode newComputerNode) {//判断是否空if(head.next == null) {System.out.println("链表为空~");return;}//找到需要修改的节点, 根据no编号//定义一个辅助变量ComputerNode temp = head.next;boolean flag = false; //表示是否找到该节点while(true) {if (temp == null) {break; //已经遍历完链表}if(temp.no == newComputerNode.no) {//找到flag = true;break;}temp = temp.next;}//根据flag 判断是否找到要修改的节点if(flag) {temp.name = newComputerNode.name;temp.nickname = newComputerNode.nickname;} else { //没有找到System.out.printf("没有找到 编号 %d 的节点,不能修改\n", newComputerNode.no);}}……}    

第二步: 测试单链表修改方法

public class SingleLinkedListDemo {public static void main(String[] args) {……System.out.println("修改后");ComputerNode newComputerNode = new ComputerNode(3, "宏碁", "偷工减料碁");singleLinkedList.update(newComputerNode);singleLinkedList.list();……}}

image.png-57.2kB

2.3单向链表——删除

链表删除操作如下图,修改节点指向下两个节点,随后下一个节点Target node被垃圾回收2

image.png-39.9kB

第一步: 定义链表删除方法

class SingleLinkedList {……//删除节点public void del(int no) {ComputerNode temp = head;boolean flag = false; // 标志是否找到待删除节点的while(true) {if(temp.next == null) { //已经到链表的最后break;}if(temp.next.no == no) {//找到的待删除节点的前一个节点tempflag = true;break;}temp = temp.next; //temp后移,遍历}//判断flagif(flag) { //找到//可以删除temp.next = temp.next.next;}else {System.out.printf("要删除的 %d 节点不存在\n", no);}}
}

第二步: 测试链表删除方法

public class SingleLinkedListDemo {public static void main(String[] args) {……System.out.println("删除后");singleLinkedList.del(3);singleLinkedList.list();……}}

image.png-46.2kB

3.单向链表——整体代码

下面附上这次单向链表的所有代码

package com.maxchen.demo.linkedlist;/*** @ClassName: SingleLinkedListDemo* @Description: TODO* @Author Maxchen* @Date 2020/4/1 18:32* @Version V1.0.0*/
public class SingleLinkedListDemo {public static void main(String[] args) {//先创建节点ComputerNode computer1 = new ComputerNode(1, "联想", "美帝良心想");ComputerNode computer2 = new ComputerNode(2, "惠普", "铁板熊掌普");ComputerNode computer3 = new ComputerNode(3, "华硕", "奸若磐石硕");ComputerNode computer4 = new ComputerNode(4, "戴尔", "人傻钱多戴");//创建要给链表并将节点加入到链表SingleLinkedList singleLinkedList = new SingleLinkedList();/*singleLinkedList.add(computer1);singleLinkedList.add(computer2);singleLinkedList.add(computer4);singleLinkedList.add(computer3);*/singleLinkedList.addByOrder(computer1);singleLinkedList.addByOrder(computer2);singleLinkedList.addByOrder(computer4);singleLinkedList.addByOrder(computer3);//显示链表,最后得到的结果如下,显然链表最后展示的结果是按照代码添加的顺序排列的// ComputerNode [no=1, name=联想, nickname=美帝良心想]// ComputerNode [no=2, name=惠普, nickname=铁板熊掌普]// ComputerNode [no=4, name=戴尔, nickname=人傻钱多戴]// ComputerNode [no=3, name=华硕, nickname=奸若磐石硕]System.out.println("删除前");singleLinkedList.list();//        System.out.println("修改后");
//        ComputerNode newComputerNode = new ComputerNode(3, "宏碁", "偷工减料碁");
//        singleLinkedList.update(newComputerNode);System.out.println("删除后");singleLinkedList.del(3);singleLinkedList.list();}}class SingleLinkedList {//先初始化一个头节点,头不存放具体的数据private ComputerNode head = new ComputerNode(0, "", "");//添加节点到单向链表public void add(ComputerNode computerNode) {//因为head节点不能动,因此我们需要一个辅助遍历 tempComputerNode temp = head;//遍历链表,找到最后while (true) {//找到链表的最后if (temp.next == null) {//next字段为空,则表示为链表结尾break;}//如果没有找到最后,next字段不为空 将temp后移temp = temp.next;}//当退出while循环时,temp就指向了链表的最后//将最后这个节点的next指向新的节点temp.next = computerNode;}//第二种方式在添加电脑时,根据编号顺序将电脑插入到指定位置public void addByOrder(ComputerNode computerNode) {//头节点不能动,因此我们仍然通过一个辅助指针(变量)来帮助找到添加的位置//因为单链表,因为我们找的temp 是位于 添加位置的前一个节点,否则插入不了ComputerNode temp = head;boolean flag = false; // flag标志添加的编号是否存在,默认为falsewhile (true) {if (temp.next == null) {//说明temp已经在链表的最后break;}if (temp.next.no > computerNode.no) { //位置找到,就在temp的后面插入break;} else if (temp.next.no == computerNode.no) {//说明希望添加的heroNode的编号已然存在flag = true; //说明编号存在break;}temp = temp.next; //后移,遍历当前链表}//判断flag 的值if (flag) { //不能添加,说明编号存在System.out.printf("准备插入的电脑的编号 %d 已经存在了, 不能加入\n", computerNode.no);} else {//插入到链表中, temp的后面computerNode.next = temp.next;temp.next = computerNode;}}//修改节点的信息, 根据no编号来修改,即no编号不能改.//说明//1. 根据 newComputerNode 的 no 来修改即可public void update(ComputerNode newComputerNode) {//判断是否空if (head.next == null) {System.out.println("链表为空~");return;}//找到需要修改的节点, 根据no编号//定义一个辅助变量ComputerNode temp = head.next;boolean flag = false; //表示是否找到该节点while (true) {if (temp == null) {break; //已经遍历完链表}if (temp.no == newComputerNode.no) {//找到flag = true;break;}temp = temp.next;}//根据flag 判断是否找到要修改的节点if (flag) {temp.name = newComputerNode.name;temp.nickname = newComputerNode.nickname;} else { //没有找到System.out.printf("没有找到 编号 %d 的节点,不能修改\n", newComputerNode.no);}}//删除节点public void del(int no) {ComputerNode temp = head;boolean flag = false; // 标志是否找到待删除节点的while (true) {if (temp.next == null) { //已经到链表的最后break;}if (temp.next.no == no) {//找到的待删除节点的前一个节点tempflag = true;break;}temp = temp.next; //temp后移,遍历}//判断flagif (flag) { //找到//可以删除temp.next = temp.next.next;} else {System.out.printf("要删除的 %d 节点不存在\n", no);}}//显示链表[遍历]public void list() {//判断链表是否为空if (head.next == null) {System.out.println("链表为空");return;}//因为头节点,不能动,因此我们需要一个辅助变量来遍历ComputerNode temp = head.next;while (true) {//判断是否到链表最后if (temp == null) {break;}//输出节点的信息System.out.println(temp);//将temp后移, 一定小心temp = temp.next;}}}class ComputerNode {public int no; //序号public String name; //品牌名称public String nickname; //品牌外号public ComputerNode next; //指向下一个节点//构造器public ComputerNode(int no, String name, String nickname) {this.no = no;this.name = name;this.nickname = nickname;}//为了显示方法,我们重新toString@Overridepublic String toString() {return "ComputerNode [no=" + no + ", name=" + name + ", nickname=" + nickname + "]";}}

  1. 数据结构之线性结构和非线性结构 ↩︎

  2. 动画讲解链表3大基本操作 - 插入,删除与翻转 ↩︎


http://www.ppmy.cn/news/825718.html

相关文章

Vue3+Vite+Pinia+Naive后台管理系统搭建之六:Axios 网络请求

前言 如果对 vue3 的语法不熟悉的,可以移步Vue3.0 基础入门快速入门。 Axios 详情可移步官网参看:Axios 官网 1. 安装依赖 yarn add axios // or npm install axios 2. .env 环境配置 对环境配置不了解的可移步:Vue 入门系列&#xff1a…

HTML5+CSS3小实例:带进度条的人物卡片切换效果

实例:带进度条的人物卡片切换效果 技术栈:HTML+CSS 效果: 源码: 【html】 <!DOCTYPE html> <html><head><meta http-equiv="content-type" content="text/html; charset=utf-8"><meta name="viewport" conte…

ffmpeg学习 源代码编译、英伟达硬件加速

使用cpu进行软编解码时&#xff0c;cpu效率低并且占用高。使用硬件加速&#xff0c;能够明显降低CPU的占用&#xff0c;参看博客 ffmpeg学习&#xff08;16&#xff09;AVDevice使用。 这里以使用英伟达gpu进行h264编解码加速为例说明&#xff0c;其他平台类似。 1、winodws硬…

英伟达笔试面试题整理DIY

nvidia 不是家喻户晓&#xff0c;但对于搞计算机可视化和图形的人来说应该没人不知道吧。 C/C编程&#xff0c;C#编程&#xff0c;Python 编程&#xff0c;Windows 操作系统知识&#xff0c;Linux 操作系统知识&#xff0c;SQL 语言知识&#xff0c;正则 表达式知识&#xff0…

英伟达jetson硬件(NX,nano,AGX,TX1,TX2)通用开机自动开启风扇教程

英伟达jetson硬件通用的风扇开机自启动教程 1.安装jetson-stats2.重启硬件&#xff0c;然后进入jtop3.操作设置4.重启测试风扇是否自动开启 1.安装jetson-stats &#xff08;先安装一下pip&#xff09; sudo apt-get install python3-pip然后就可以正常使用pip进行安装&#…

英伟达 nvidia jetson AGX xavier 风扇开机自启动设置

1. 风扇开机自启设置 切换到 root 用户 sudo su修改风扇pwm调速文件权限 chmod 777 /sys/devices/pwm-fan/target_pwm修改系统外设配置文件&#xff0c;并添加风扇开机启动任务&#xff1a; vim /lib/systemd/system/rc-local.service注意&#xff1a; 1.如不知道该文件路径…

Vue3+Vite+Pinia+Naive后台管理系统搭建之二:scss 的安装和使用

前言 如果对 vue3 的语法不熟悉的&#xff0c;可以移步 Vue3.0 基础入门&#xff0c;快速入门。 1. 安装依赖 yarn add sass -D // or npm install sass -D 2. 页面样式初始化 reset.scss /* 新建 src/assets/style/reset.scss */ /* 页面样式初始化 */ html, body, div, s…

跳木桩----(爱思创)

源代码 #include <bits/stdc.h> using namespace std;int main(){int n,a[10010],maxd,ans0;cin>>n;for(int i1;i<n;i){cin>>a[i];}cin>>maxd;sort(a1,an1);for(int i1;i<n;i){if(a[i]-a[i-1]<maxd){ans;}else{break;}}cout<<ans<&…