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

news2024/9/23 5:27:23

目录

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

二、链表的接口

三、链表的方法实现

(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;//null
            last = 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;//null
                    last = 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;
        }
    }

    @Override
    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;
        }
    }

    @Override
    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;
        }
    }

    @Override
    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;
    }

    @Override
    public boolean contains(int key) {
        ListNode cur = this.head;
        while (cur != null) {
            if(cur.val == key) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    @Override
    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;//null
            last = 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;
    }

    @Override
    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;//null
                    last = prev;
                }
                cur = cur.next;
            } else {
                prev = prev.next;
                cur = cur.next;
            }
        }
        if(head.val == key) {
            head = head.next;
            if(head != null) {
                head.prev = null;
            }
        }
    }

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

    @Override
    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;
    }

    @Override
    public 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.coloradmin.cn/o/1360736.html

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

相关文章

网络连接 UDP2,UDP Connect, bind, send, recieve认知, -入门8

LWIP编程接口有RAW, NETCONN, SOCKET 2.UDP函数的理解 #define UDP_SERVER_PORT 8000 //PC side #define UDP_CLIENT_PORT 1234 // ctrl board side //PC IP address #define DEST_IP_ADDR0 192 #define DEST_IP_ADDR1 168 #define DEST_IP_ADDR2 3 #define DEST_IP_ADDR3 11…

2023-2024学年上学期算法设计与分析题期末考试模拟卷

2023-2024学年上学期算法设计与分析题期末考试模拟卷 文章目录 2023-2024学年上学期算法设计与分析题期末考试模拟卷单选题程序填空题输入格式:输出格式:输入样例1:输出样例1: 主观题 注意&#xff1a;该题集非标准答案&#xff0c;仅供参考&#xff0c;如果异议&#xff0c;请…

淘宝商品详情API接口(item_get-获得淘宝商品详情)主图,属性,sku,价格,搜索商品列表

淘宝开放平台提供了API接口&#xff0c;允许开发者获取淘宝商品的相关信息。为了获取商品详情&#xff0c;您可以使用 item_get API接口。以下是如何使用此API接口来获取商品的主图、属性、SKU、价格以及搜索商品列表的简要说明&#xff1a; 公共参数 名称类型必须描述keyStr…

SpringBoot Redis入门(一)——redis、Lettuce、Redisson使用

本章&#xff1a;将展示SpringBoot集成Redis三种客户端的配置要点和常见应用示例&#xff1b;下章&#xff1a;自行实现一个方法级的缓存注解&#xff0c;简化版的Cacheable&#xff0c;使初学者加深对Spring缓存框架的理解。 一、Lettuce客户端 Lettuce 是一种可扩展的、线程…

Java接口和抽象类的区别?

Java接口和抽象类的区别&#xff1f; Java接口和抽象类的含义&#xff1a; 接口&#xff08;Interface&#xff09;&#xff1a; 含义&#xff1a; 接口是一种抽象类型&#xff0c;它定义了一组抽象方法&#xff0c;但不能包含具体实现。接口可以包含常量和默认方法&#xff0c…

机器学习模型超参数优化,最频繁使用的5个工具包

优化超参数始终是确保模型性能最佳的关键任务。通常&#xff0c;网格搜索、随机搜索和贝叶斯优化等技术是主要使用的方法。 今天分享几个常用于模型超参数优化的 Python 工具包&#xff0c;如下所示&#xff1a; scikit-learn&#xff1a;使用在指定参数值上进行的网格搜索或随…

HarmonyOS应用开发学习笔记 包名、icon图标,应用名修改 UIAbility组件介绍、UIAbility启动模式、UIAbility组件基本用法

目前HarmonyOS应用主推的是Stage模型开发 一、Stage模型基本概念 项目描述UIAbility组件UIAbility组件是一种包含UI界面的应用组件&#xff0c;主要用于和用户交互。例如&#xff0c;图库类应用可以在UIAbility组件中展示图片瀑布流&#xff0c;在用户选择某个图片后&#xf…

Mongodb启动客户端 并简单演示切换数据库

上文 window下载安装Mongodb数据库 我们简单搭起了Mongodb的服务 但是 我们还需要一个客户端 我们还是将服务端启动起来 mongod --dbpath..\data\db我们再以管理员身份运行一个终端 直接进入 Mongodb安装目录的bin下 然后启动客户端的命令是 mongo --host 服务ip --port 服务…

Android studio环境配置

1.搜索android studio下载 Android Studio - Download 2.安装 3.配置环境 配置gradle&#xff0c;gradle参考网络配置。最后根据项目需求选择不同的jdk。

将PPT4页并排成1页

将PPT4页并排成1页打印 解决方法: 方法一 在打印时选择&#xff1a; 打开 PPT&#xff0c;点击文件选项点击打印点击整页幻灯片点击4张水平放置的幻灯平页面就会显示4张PPT显示在一张纸上 方法二 另存为PDF&#xff1a; 打开电脑上的目标PPT文件&#xff0c;点击文件点击…

一种DevOpts的实现方式:基于gitlab的CICD(一)

写在之前 笔者最近准备开始入坑CNCF毕业的开源项目&#xff0c;看到其中有一组开源项目的分类就是DevOpts。这个领域内比较出名的项目是Argocd&#xff0c;Argo CD 是一个用于 Kubernetes 的持续交付 (Continuous Delivery) 工具&#xff0c;它以声明式的方式实现了应用程序的…

手把手带你手撕一个shell

&#x1f3ac;慕斯主页&#xff1a;修仙—别有洞天 ♈️今日夜电波&#xff1a;HEART BEAT—YOASOBI 2:20━━━━━━️&#x1f49f;──────── 5:35 &#x1f504; ◀️ ⏸ ▶️ ☰ …

Underactuated Robotics - 欠驱动机器人学(一)- 全驱动与欠驱动系统

系列文章目录 前言 如今的机器人行动过于保守&#xff0c;只能完成机械性能所能完成的一小部分任务&#xff0c;实现一小部分性能。在某些情况下&#xff0c;我们仍然从根本上受限于在结构化工厂环境中成熟的刚性机械臂控制技术&#xff0c;在这种环境中&#xff0c;可以使用大…

cad快速看图软件免费版(手机在线cad快速看图)

cad快速看图软件免费版(手机在线cad快速看图) 很多机械设计师日常工作过程中涉及到多种格式的cad图纸&#xff0c;cad图纸大多都需要cad设计软件才能打开&#xff0c;然而很多小伙伴并没有下载相应的cad设计软件&#xff0c;这种情况下如何进行cad快速看图呢&#xff1f; 今天…

Chromedriver 下载和安装指南

1. 确定Chrome浏览器版本 首先&#xff0c;在谷歌浏览器中找到当前版本信息。 打开“设置”&#xff0c;点击“关于谷歌”即可看到版本号。确保后续下载的Chromedriver版本与Chrome浏览器版本一致。或者直接跳转网页地址&#xff1a;chrome://settings/help 2. 下载Chromedri…

告别2023,无论何年我都是最幸福的程序员。

序 2023年注定是不平凡的一年&#xff0c;有太多的意外&#xff0c;无论是工作还是家庭。唯一不变的是坚持&#xff0c;岁月的静好&#xff0c;离不开同事们的负重前行&#xff0c;作为一个程序员来说&#xff0c;我是幸运的&#xff0c;也是最幸福的。生活的漫漫长路&#xf…

Flask 会员列表展示

感谢编程浪子师傅的源码信息分享 web/controllers/member/Member.py # -*- coding: utf-8 -*- from flask import Blueprint,request,redirect,jsonify from common.libs.Helper import ops_render,iPagination,getCurrentDate,getDictFilterField,selectFilterObj from comm…

MongoDB数据类型详解

BSON 协议与数据类型 MongoDB 为什么会使用 BSON&#xff1f; JSON 是当今非常通用的一种跨语言 Web 数据交互格式&#xff0c;属 ECMAScript 标准规范的一个子集。JSON &#xff08;JavaScript Object Notation&#xff0c;JS 对象简谱&#xff09;即 JavaScript 对象表示法…

日程安排小程序实战教程

日常中我们经常有一些事情需要提醒自己&#xff0c;使用日历的形式比较符合实际的使用习惯。本篇我们就利用微搭低代码工具带着大家开发一款日程安排的小程序。 1 创建数据源 登录微搭低代码控制台&#xff0c;打开数据模型&#xff0c;点击创建 输入数据源的名称日程安排 …

vue封装组件(一)标签和下拉框组合实现添加数据

背景: 最近接入短剧内容&#xff0c;需要添加短剧合作方。在详情页需要支持添加组件 方案一&#xff1a;标签tag加上输入框实现添加数据。图片见下 这个是刚开始做的&#xff0c;后来产品觉得这样会造成随意修改数据&#xff0c;需要改成下拉框形式添加 方案二&#xff1a;标签…