数据结构模拟实现LinkedList双向不循环链表

目录

一、双向不循环链表的概念

二、链表的接口

三、链表的方法实现

(1)display方法

(2)size方法

(3)contains方法

(4)addFirst方法

(5)addLast方法

(6)addIndex方法

(7)remove方法

(8)removeAllKey方法

(9)clear方法

四、最终代码


一、双向不循环链表的概念

双向不循环链表中的节点有三个域,一个是存储数据的val域,一个是前驱prev域,还有一个是下个节点next域,和单向不同的就是多了一个前驱域。如图:

定义一个MyLinkedList类,这个类包含要模拟实现的方法,还有一个内部类ListNode,这个内部类就是链表的节点,代码如下:

public class MyLinkedList implements Ilist{public ListNode head;//头结点public ListNode last;//尾结点static class ListNode {int val;ListNode next;ListNode prev;public ListNode(int val) {this.val = val;}}
}


二、链表的接口

代码如下:

public interface Ilist {//头插法void addFirst(int data);//尾插法void addLast(int data);//任意位置插入,第一个数据节点为0号下标void addIndex(int index,int data);//查找是否包含关键字key是否在单链表当中boolean contains(int key);//删除第一次出现关键字为key的节点void remove(int key);//删除所有值为key的节点void removeAllKey(int key);//得到单链表的长度int size();void clear();void display();
}

三、链表的方法实现

(1)display方法

此方法是打印所有链表节点的val值,因此要遍历一遍链表的节点。代码如下:

    public void display() {ListNode cur = this.head;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}

(2)size方法

此方法计算链表中有多少个节点,所以也要遍历一遍链表,代码如下:

    public int size() {ListNode cur = this.head;int count = 0;while (cur != null) {count++;cur = cur.next;}return count;}

(3)contains方法

此方法查看是否有key值,有就返回true,没有就返回false,所以也要遍历一遍链表,代码如下:

    public int size() {ListNode cur = this.head;int count = 0;while (cur != null) {count++;cur = cur.next;}return count;}

(4)addFirst方法

此方法是头插方法,参数是链表的val域的值,所以调用此方法时,要创建一个节点,再把这个节点进行头插;头插时,要修改要插入节点的next域,指向原来的头结点,还有原来头结点的prev域,指向要插入的节点,最后再把头结点改为要插入的这个节点,如图:绿色箭头是修改指向

因为是新建的节点,所以这个节点的prev和next域都是null

修改完后,如图:

代码如下:

    public void addFirst(int data) {ListNode cur = new ListNode(data);if(this.head == null) {this.head = cur;this.last = cur;} else {cur.next = this.head;this.head.prev = cur;this.head = cur;}}

执行效果如下:

(5)addLast方法

此方法是尾插法,这里的尾插法时间复杂度是O(1),因为双向链表有一个记录尾结点的last,所以尾插的时候直接在尾结点插入要插入的节点,修改原来的尾结点的next域,要插入的节点prev修改成原来的尾结点,最后再把尾结点last修改成插入的节点,代码如下:

    public void addLast(int data) {ListNode cur = new ListNode(data);if(last == null) {head = cur;last = cur;} else {last.next = cur;cur.prev = last;last = cur;}}

执行效果如下:

(6)addIndex方法

此方法是在指定位置插入节点,第一要检查要插入位置的index下标是否合法,不合法就抛异常,这里定义第一个节点下标为0,第二个节点下标为1,依次类推,如果要插入位置的下标是0,就是头插,如果要插入位置的下标是链表长度(size方法),就是尾插;

要插入的位置在链表中间,我们要找出指定位置的前一个节点,修改前一个节点的next域,修改成要插入的节点,还有指定位置原来的节点的prev域也要修改,修改成要插入的节点。代码如下:

    public void addIndex(int index, int data) {//检查下标是否合法if(index < 0 || index > size()) {throw new IndexException("下标不合法");}if(index == 0) {addFirst(data);return;}if (index == size()) {addLast(data);return;}ListNode cur = new ListNode(data);ListNode prev = this.head;int count = 0;while (count < index - 1) {prev = prev.next;count++;}ListNode prevNext = prev.next;prev.next = cur;cur.prev = prev;cur.next = prevNext;prevNext.prev = cur;}
//自定义异常类
public class IndexException extends RuntimeException{public IndexException(String e) {super(e);}
}

执行效果如下:

(7)remove方法

此方法是移除第一个值为key的链表节点的方法,参数是就是key;要移除某一个节点,就要从头遍历一遍链表,如果没找到key值,就直接返回,不做任何操作;

这里要提前处理一些特殊情况,如果头结点的val值就是key,就要把head放在head的next域,然后判断这时候head是不是空,如果head不是空,head的prev就要修改成空,如果head是空,就要把last设为空,直接返回。

如果找到了,就要找要删除节点的前一个节点,这里会分两种情况,一种是要删除的节点后面没有节点了(尾结点),这时我们把要删除节点的前一个节点的next域改成null,last改成前一个节点;如果要删除的节点后面有节点,就要把前一个节点的next域改成要删除的节点的next,后一个的prev域改成前一个节点,代码如下:

    public void remove(int key) {if(head == null) {return;}if(head.val == key) {head = head.next;if(head != null) {head.prev = null;} else {last = null;return;}}ListNode prev = findPrev(key);if(prev == null) {//没有要删的元素return;}ListNode cur = prev.next;if(cur.next != null) {prev.next = cur.next;cur.next.prev = cur.prev;} else {//最后一个元素prev.next = cur.next;//nulllast = prev;}}//找到要删除节点的前一个节点private ListNode findPrev(int key) {ListNode cur = this.head;ListNode curNext = cur.next;while (curNext != null) {if(curNext.val != key) {cur = cur.next;curNext = curNext.next;} else {return cur;}}return null;}

执行效果如下:

(8)removeAllKey方法

此方法是删除所有节点的val值为key的方法,所以,我们要遍历一遍链表,如果head为空的话,就直接返回,不做任何操作;

我们定义prev是头结点,cur是头结点的next节点(要删除的节点),从头到尾遍历的是cur,如果cur的val值不等于key,prev和cur都要往后走一步;如果cur的val值等于key,会分成两种情况,就是cur后面是有没有节点,如果后面有节点,prev节点的next域就要改成cur的next,cur的下一个节点的prev域要改成prev,然后cur往后走一步;如果cur后面的节点为空,就直接把prev节点的next域改成空,把last改成prev,cur还要往后走一步结束循环。

最后不要忘了头结点还没有判断,要判断头结点的val值是否和key相等,如果不相等就不做任何操作,相等就把头结点head改成头结点的next,此时的头结点的prev改成null,注意,这里修改头结点的prev,要头结点head不为空,才能执行上面的操作,不然会空指针异常。

    public void removeAllKey(int key) {if(head == null) {return;}ListNode prev = this.head;ListNode cur = this.head.next;while (cur != null) {if(cur.val == key) {if(cur.next != null) {prev.next = cur.next;cur.next.prev = prev;} else {prev.next = cur.next;//nulllast = prev;}cur = cur.next;} else {prev = prev.next;cur = cur.next;}}if(head.val == key) {head = head.next;if(head != null) {head.prev = null;}}}

执行效果如下:

(9)clear方法

此方法是把链表中的所有节点中所有域都置为空,所以要遍历一遍链表,把节点prev和next域改为null,因为这里的val域类型是int,所以不用修改val域,代码如下:

    public void clear() {ListNode cur = this.head;while (cur != null) {ListNode curNext = cur.next;cur.next = null;cur.prev = null;cur = curNext;}head = null;last = null;}

执行效果如下:


四、最终代码

public class MyLinkedList implements Ilist{public ListNode head;//头结点public ListNode last;//尾结点static class ListNode {int val;ListNode next;ListNode prev;public ListNode(int val) {this.val = val;}}@Overridepublic void addFirst(int data) {ListNode cur = new ListNode(data);if(this.head == null) {this.head = cur;this.last = cur;} else {cur.next = this.head;this.head.prev = cur;this.head = cur;}}@Overridepublic void addLast(int data) {ListNode cur = new ListNode(data);if(last == null) {head = cur;last = cur;} else {last.next = cur;cur.prev = last;last = cur;}}@Overridepublic void addIndex(int index, int data) {//检查下标是否合法if(index < 0 || index > size()) {throw new IndexException("下标不合法");}if(index == 0) {addFirst(data);return;}if (index == size()) {addLast(data);return;}ListNode cur = new ListNode(data);ListNode prev = this.head;int count = 0;while (count < index - 1) {prev = prev.next;count++;}ListNode prevNext = prev.next;prev.next = cur;cur.prev = prev;cur.next = prevNext;prevNext.prev = cur;}@Overridepublic boolean contains(int key) {ListNode cur = this.head;while (cur != null) {if(cur.val == key) {return true;}cur = cur.next;}return false;}@Overridepublic void remove(int key) {if(head == null) {return;}if(head.val == key) {head = head.next;if(head != null) {head.prev = null;} else {last = null;return;}}ListNode prev = findPrev(key);if(prev == null) {//没有要删的元素return;}ListNode cur = prev.next;if(cur.next != null) {prev.next = cur.next;cur.next.prev = cur.prev;} else {//最后一个元素prev.next = cur.next;//nulllast = prev;}}private ListNode findPrev(int key) {ListNode cur = this.head;ListNode curNext = cur.next;while (curNext != null) {if(curNext.val != key) {cur = cur.next;curNext = curNext.next;} else {return cur;}}return null;}@Overridepublic void removeAllKey(int key) {if(head == null) {return;}ListNode prev = this.head;ListNode cur = this.head.next;while (cur != null) {if(cur.val == key) {if(cur.next != null) {prev.next = cur.next;cur.next.prev = prev;} else {prev.next = cur.next;//nulllast = prev;}cur = cur.next;} else {prev = prev.next;cur = cur.next;}}if(head.val == key) {head = head.next;if(head != null) {head.prev = null;}}}@Overridepublic int size() {ListNode cur = this.head;int count = 0;while (cur != null) {count++;cur = cur.next;}return count;}@Overridepublic void clear() {ListNode cur = this.head;while (cur != null) {ListNode curNext = cur.next;cur.next = null;cur.prev = null;cur = curNext;}head = null;last = null;}@Overridepublic void display() {ListNode cur = this.head;while (cur != null) {System.out.print(cur.val + " ");cur = cur.next;}System.out.println();}
}//自定义异常类
public class IndexException extends RuntimeException{public IndexException(String e) {super(e);}
}

都看到这了,点个赞再走吧,谢谢谢谢谢!!!

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

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

相关文章

【Java基础篇】While(true) 和 for(;;)哪个性能更好呢

两个无限循环的性能分析 ✔️两者反编译比较 ✔️两者反编译比较 While(true) 和 for(; &#x1f609; 都是做无限循环的代码&#xff0c;他们两个有什么区别呢&#xff1f; 关于这个问题&#xff0c;网上有很多的讨论&#xff0c;今天我收到私信&#xff0c;所以凑着假期&…

软件功耗管理

一、背景 功耗管理是由软件、处理器、外设、电源等一起构成的系统问题。 App中很小的低效行为在整个系统中累加后&#xff0c;会对电池寿命、性能、响应速度和温度产生明显的影响。作为app开发人员&#xff0c;我们有责任确保我们的app尽可能高效地运行。使用苹果推荐的API&a…

消息队列LiteQueue

文章目录 一、简介二、设计2.1 队列结构设计2.2 队列接口设计 三、实现3.1 队列锁的实现3.2 创建队列3.3 写入队列3.4 读出数据3.5 判断队列是否为空3.6 判断队列是否为满3.7 清空队列3.8 删除队列 四、测试参考 一、简介 收到消息时先把接收到的消息放到队列中。在任务中从队…

31K star!替换Postman ,开源优雅的API工具:Insomnia

API 调试工具大家第一个想到的肯定是Postman&#xff0c;但是这几年Postman在国内越来越难用&#xff0c;很多人也都在考虑找一个替代品。 今天我们来推荐一个可以替换掉Postman的 API 工具&#xff0c;他开源、支持本地使用&#xff0c;也更加轻量、更加优雅&#xff0c; 目前…

ROS TF坐标变换 - TF树

目录 一、TF树介绍二、TF2与TF三、构建TF树四、rviz查看TF坐标关系 一、TF树介绍 在机器人系统中&#xff0c;存在运动学模型和动力学模型。对于刚体机器人&#xff0c;动力学模型基于刚体动力学&#xff0c;代表机器人系统在运动过程中力/力矩与其运动状态的变化关系。而运动…

Python 基础语法01

变量声明 #运算 num 1 num 1 print("num 1",num)num - 1 print("num - 1", num)num * 4 print("num * 4",num)num 3 num % 2 print("num%2",num)num ** 2 print("num ** 2", num)num 9 num // 2 print("num // …

odoo17 | 创建一个新应用程序

前言 本章的目的是为创建一个全新的Odoo模块奠定基础。 我们将从头开始&#xff0c;以使我们的模块被Odoo识别所需的最低限度。 在接下来的章节中&#xff0c;我们将逐步添加功能以构建一个真实的业务案例。 教程 假设我门需要在odoo上开发一个新app模块例如房地产广告模块。…

C++的面向对象学习(9):文件操作

提示&#xff1a;文章写完后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 一、类的封装的多文件实现回顾二、文件操作1.对文件进行操作需要头文件<fstream>2.操作文件的三大类方法&#xff1a;读、写、读写 三、实现文本文件的读、写…

提取 PE 文件的各种信息

前段时间项目需要实现对 Windows PE 文件版本信息的提取&#xff0c;如文件说明、文件版本、产品名称、版权、原始文件名等信息。获取这些信息在 Windows 下当然有一系列的 API 函数供调用&#xff0c;简单方便。 我们先看一下PE文件结构&#xff0c;PE文件由DOS首部&#xff0…

数据结构期末复习(3)栈和队列

堆栈&#xff08;stack&#xff09; 堆栈&#xff08;stack&#xff09;是一种基于后进先出&#xff08;LIFO&#xff0c;Last In First Out&#xff09;原则的数据结构。它模拟了现实生活中的堆栈&#xff0c;类似于一摞盘子或一堆书。 堆栈有两个基本操作&#xff1a;入栈&a…

【代码解析】代码解析之生成token(1)

本篇文章主要解析上一篇&#xff1a;代码解析之登录&#xff08;1&#xff09;里的第8行代码调用 TokenUtils 类里的genToken 方法 https://blog.csdn.net/m0_67930426/article/details/135327553?spm1001.2014.3001.5501 genToken方法代码如下&#xff1a; public static S…

【UE 截图】 自定义截图路径 文件名

目录 0 引言1 实践 &#x1f64b;‍♂️ 作者&#xff1a;海码007&#x1f4dc; 专栏&#xff1a;UE虚幻引擎专栏&#x1f4a5; 标题&#xff1a;【UE 截图】 自定义截图路径 文件名❣️ 寄语&#xff1a;书到用时方恨少&#xff0c;事非经过不知难&#xff01;&#x1f388; 最…