【数据结构】Java实现双向链表

目录

1. 接口的实现

2. 动手实现双链表

2.1 重写SeqList接口方法

2.2 在当前链表尾部添加节点(尾插)

2.3 在当前链表头部添加节点(头插)

2.4 检验index是否合法

2.5 在 第index位置添加节点(任意位置)

2.6  删除第index个节点

2.7 删除第一个值element的节点

2.8 删除所有值element的节点

2.9 修改第index个节点的值为element

2.10 获取第index个节点的值

2.11 判断链表中是否存在element

2.12  获取element在链表中的位置

2.13 打印链表

2.14  获取链表长度以及清空链表

3. DoubleLinkedList整体实现

3.1 DoubleLinkedList类

3.2 Test类

3.3 测试结果


LinkedList 的底层是双向链表结构 ,由于链表没有将元素存储在连续的空间中,元素存储在单独的节点中,然后通过引用将节点连接起来了,因此在在任意位置插入或者删除元素时,不需要搬移元素,效率比较高。

1. 接口的实现

(这个接口在前面的单链表,动态数组中也可以用到)

public interface SeqList<E> {
    //    尾插
    void add(E element);
    //    将 element 插入到 index 位置
    void add(int index,E element);
    //    删除 index 位置元素<返回删除的值
    E removeByIndex(int index);
    //    删除第一个值element的元素
    void removeByValue(E element);
    //    删除所有值element的元素
    void removeAllValue(E element);
    //    将下标 index 位置元素设置为 element,返回替换前的值
    E set(int index,E element);
    E get(int index);
    //    判断 o 是否在其中
    boolean contains(E element);
    int indexOf(E element);
    int size();
    void clear();
}

2. 动手实现双链表

2.1 重写SeqList接口方法

定义双向链表类,实现SeqList方法,重写同String方法。

(Alt + insert 快速实现方法重写)

package seqlist.link;

import seqlist.SeqList;

public class DoubleLinkedList<E> implements SeqList<E> {
    private DoubleNode head;//头节点
    private DoubleNode tail;//尾节点

    private int size; // 车厢节点个数,保存的元素个数

    //车厢类的定义,车厢作为火车的内部类,对外部完全隐藏
    private class DoubleNode {
        E val;//保存的元素
        DoubleNode prev;
        DoubleNode next;
        DoubleNode(E val) {
            this.val = val;
        }
        public DoubleNode(E val, DoubleNode prev, DoubleNode next) {
            this.val = val;
            this.prev = prev;
            this.next = next;
        }
    }
    public void addFrist(E val){ }
    @Override
    public void add(E element) { }
    @Override
    public void add(int index, E element) { }
    @Override
    public E removeByIndex(int index) { }
    @Override
    public void removeByValue(E element) { }
    @Override
    public void removeAllValue(E element) { }
    @Override
    public E set(int index, E element) { }
    public boolean rangeCheck(int index){ }
    @Override
    public E get(int index) { }
    @Override
    public boolean contains(E element) { }
    @Override
    public int indexOf(E element) { }
    @Override
    public int size() { }
    @Override
    public void clear() { }
    @Override
    public String toString() { }
}

2.2 在当前链表尾部添加节点(尾插)

(1)size++

(2)如果链表为空,这个新插入的节点就是头节点

(3)链表不为空,将当前链表的尾节点的next指向新节点,新节点的前驱prev指向尾节点

(4)更新当新节点为尾节点

public void add(E element) {
        DoubleNode node = new DoubleNode(element);
        size ++;
        if (head == null){
            head = node;
        }else {
            node.prev = tail;
            tail.next = node;
        }
        tail = node;
    }

2.3 在当前链表头部添加节点(头插)

这里头插不是重写方法!(因为前面的动态数组没又头插这一说法,所以这个方法就不放在接口里了)

(1)size++

(2)如果链表尾空,那么新节点就是头节点和尾节点

(3)链表不为空,将新节点的next指向head,head的前驱prev指向新节点

(4)更新新节点为头节点head

public void addFirst(E element) {
        DoubleNode node = new DoubleNode(element);
        size ++;
        if(head == null){
            tail = node;
        }else {
            node.next = head;
            head.prev = node;
        }
        head = node;
    }

2.4 检验index是否合法

 private boolean rangeCheck(int index) {
        if (index < 0 ||index >= size) {
            return false;
        }
        return true;
    }

2.5 在 第index位置添加节点(任意位置)

(1)判断index是否合法,不合法退出
(2)判断是不是头插或者尾插,调用相应的方法添加后退出
(3)调用node方法index位置节点的找到前驱prev,将prev.next作为后继节点node方法判断index是比较靠近head就从前往后遍历,比较靠近tail就从后往前遍历,使代码效率更高
(4)节点链接,先连左边区域,再连右边区域
(5)size++ 
DoubleNode node = new DoubleNode(element,prev,next);
看前面的构造函数,这时候已经将node.prev = prev,node.next = next
    public void add(int index, E element) {
        if (index < 0 || index > size){
            throw new IllegalArgumentException("add index illegal");
        }
        if(head == null){
            addFirst(element);
            return;
        }
        if(index == size){
            add(element);
            return;
        }

        DoubleNode prev = node(index - 1);
        DoubleNode next = prev.next;
        DoubleNode node = new DoubleNode(element,prev,next);
        // 先处理左边区域
        prev.next = node;
        // 再处理右半区域
        next.prev = node;
        size ++;
    }
    // 根据传入索引与中间位置的关系,决定到底从前向后寻找节点还是从后向前寻找节点
    // 内部使用的工具方法
    private DoubleNode node(int index){
        if (index < (size>>1)){
            DoubleNode ret = head;
            for (int i = 0; i < index; i++) {
                ret = ret.next;
            }
            return ret;
        }else {
            DoubleNode ret = tail;
            for (int i = size -1; i > index; i--) {
                ret = ret.prev;
            }
            return ret;
        }
    }

2.6  删除第index个节点

(1)判断index是否合法,不合法退出

(2)调用node方法找到待删除节点

(3)调用unlink(node)方法进行删除,将node的前驱prev和后继next连接,将node的prev和next置空null,再size--。(前驱prev为空时,node是头节点,将新的头节点设为node的下一个节点next;后继next为空时node为尾节点,将node的前驱prev设尾节点)

(4)返回被删除节点的值

public E removeByIndex(int index) {
        if (!rangeCheck(index)){
            throw new IllegalArgumentException("removeByIndex index illegal");
        }
        DoubleNode node = node(index);
        unlink(node);
        return node.val;
    }
    private void unlink(DoubleNode node){
        DoubleNode prev =node.prev;
        DoubleNode next = node.next;
        // 先处理左半区域
        if(prev == null){
            this.head = next;
        }else {
            node.next = null;
            prev.next = next;
        }
        // 在处理右半区域
        if(next == null){
            this.tail = prev;
        }else {
            node.next = null;
            next.prev = prev;
        }
        size--;
    }

2.7 删除第一个值element的节点

遍历链表,在链表中找到与element值相等的节点,调用unlink(node)方法进行删除
这里的图和2.6 中的图一致
    public void removeByValue(E element) {
        DoubleNode node = head;
        for (int i = 0; i < size; i++) {
            if (node.val.equals(element)){
                unlink(node);
                return;
            }
            node = node.next;
        }
    }

2.8 删除所有值element的节点

(1)遍历链表,在链表中找到与element值相等的节点,调用unlink(node)方法进行删除

(2)链表有多长就要遍历几次,以防有的节点没有被遍历(此时,每当进行一次删除,size就会减一,直接用size遍历可能导致某些节点漏掉了,因此用length保存初始的size值)

public void removeAllValue(E element) {
        DoubleNode node = head;
        // 因为每次unlink之后都会修改size的值,但是删除所有元素,
        // 要把所有链表节点全部遍历一遍
        int length = this.size;
        for (int i = 0; i < length; i++) {
            DoubleNode next = node.next;
            if (node.val.equals(element)) {
                unlink(node);
            }
            node = next;
        }
    }

2.9 修改第index个节点的值为element

(1)判断index是否合法,不合法退出

(2)调用node方法找到该节点

(3)保存原来节点的值

(4)修改该节点的值

(5)返回原来节点的值

    public E set(int index, E element) {
        if(!rangeCheck(index)){
            throw new IllegalArgumentException("set index illeagal");
        }
        DoubleNode node = node(index);
        E oldVal = node.val;
        node.val = element;
        return oldVal;
    }

2.10 获取第index个节点的值

(1)判断index是否合法,不合法退出

(2)调用node方法找到该节点并返回

public E get(int index) {
        if (!rangeCheck(index)) {
            throw new IllegalArgumentException("get index illegal!");
        }
        return node(index).val;
    }

2.11 判断链表中是否存在element

public boolean contains(E element) {
        DoubleNode node = head;
        while (node.next != null){
            if (node.val.equals(element)){
                return true;
            }
            node = node.next;
        }
        return false;
    }

2.12  获取element在链表中的位置

public int indexOf(E element) {
        DoubleNode node = head;
        int i = 0;
        while (node.next != null){
            if (node.val.equals(element)){
                return i;
            }
            i ++;
            node = node.next;
        }
        return -1;
    }

2.13 打印链表

public String toString() {
        StringBuilder sb = new StringBuilder();
        for(DoubleNode x = head; x != null; x = x.next){
            sb.append(x.val);
            sb.append("->");
            if(x.next == null){
                // 此时temp走到了尾结点
                sb.append("NULL");
            }
        }
        return sb.toString();
    }

2.14  获取链表长度以及清空链表

    public int size() {
        return size;
    }

    @Override
    public void clear() {
        while (head.next != null){
            DoubleNode node = head.next;
            head.next =null;
            head.prev = null;
            head = node;
        }
        head = null;
        size = 0;
    }

3. DoubleLinkedList整体实现

3.1 DoubleLinkedList类

public class DoubleLinkedList<E> implements SeqList<E> {
    private DoubleNode head;//头节点
    private DoubleNode tail;//尾节点

    private int size; // 车厢节点个数,保存的元素个数

    //车厢类的定义,车厢作为火车的内部类,对外部完全隐藏
    private class DoubleNode {
        E val;//保存的元素

        DoubleNode prev;
        DoubleNode next;
        DoubleNode(E val) {
            this.val = val;
        }

        public DoubleNode(E val, DoubleNode prev, DoubleNode next) {
            this.val = val;
            this.prev = prev;
            this.next = next;
        }
    }

//    w尾插
    @Override
    public void add(E element) {
        DoubleNode node = new DoubleNode(element);
        size ++;
        if (head == null){
            head = node;
        }else {
            node.prev = tail;
            tail.next = node;
        }
        tail = node;
    }
    public void addFirst(E element) {
        DoubleNode node = new DoubleNode(element);
        size ++;
        if(head == null){
            tail = node;
        }else {
            node.next = head;
            head.prev = node;
        }
        head = node;
    }

    @Override
    public void add(int index, E element) {
        if (index < 0 || index > size){
            throw new IllegalArgumentException("add index illegal");
        }
        if(head == null){
            addFirst(element);
            return;
        }
        if(index == size){
            add(element);
            return;
        }

        DoubleNode prev = node(index - 1);
        DoubleNode next = prev.next;
        DoubleNode node = new DoubleNode(element,prev,next);
        // 先处理左边区域
        prev.next = node;
        // 再处理右半区域
        next.prev = node;
        size ++;
    }
    // 根据传入索引与中间位置的关系,决定到底从前向后寻找节点还是从后向前寻找节点
    // 内部使用的工具方法
    private DoubleNode node(int index){
        if (index < (size>>1)){
            DoubleNode ret = head;
            for (int i = 0; i < index; i++) {
                ret = ret.next;
            }
            return ret;
        }else {
            DoubleNode ret = tail;
            for (int i = size -1; i > index; i--) {
                ret = ret.prev;
            }
            return ret;
        }
    }


    @Override
    public E removeByIndex(int index) {
        if (!rangeCheck(index)){
            throw new IllegalArgumentException("removeByIndex index illegal");
        }
        DoubleNode node = node(index);
        unlink(node);
        return node.val;
    }

    @Override
    public void removeByValue(E element) {
        DoubleNode node = head;
        for (int i = 0; i < size; i++) {
            if (node.val.equals(element)){
                unlink(node);
                return;
            }
            node = node.next;
        }
    }

    @Override
    public void removeAllValue(E element) {
        DoubleNode node = head;
        // 因为每次unlink之后都会修改size的值,但是删除所有元素,
        // 要把所有链表节点全部遍历一遍
        int length = this.size;
        for (int i = 0; i < length; i++) {
            DoubleNode next = node.next;
            if (node.val.equals(element)) {
                unlink(node);
            }
            node = next;
        }
    }

    private void unlink(DoubleNode node){
        DoubleNode prev =node.prev;
        DoubleNode next = node.next;
        // 先处理左半区域
        if(prev == null){
            this.head = next;
        }else {
            node.next = null;
            prev.next = next;
        }
        // 在处理右半区域
        if(next == null){
            this.tail = prev;
        }else {
            node.next = null;
            next.prev = prev;
        }
        size--;
    }

    private boolean rangeCheck(int index) {
        if (index < 0 ||index >= size) {
            return false;
        }
        return true;
    }

    @Override
    public E set(int index, E element) {
        if(!rangeCheck(index)){
            throw new IllegalArgumentException("set index illeagal");
        }
        DoubleNode node = node(index);
        E oldVal = node.val;
        node.val = element;
        return oldVal;
    }

    @Override
    public E get(int index) {
        if (!rangeCheck(index)) {
            throw new IllegalArgumentException("get index illegal!");
        }
        return node(index).val;
    }

    @Override
    public boolean contains(E element) {
        DoubleNode node = head;
        while (node.next != null){
            if (node.val.equals(element)){
                return true;
            }
            node = node.next;
        }
        return false;
    }

    @Override
    public int indexOf(E element) {
        DoubleNode node = head;
        int i = 0;
        while (node.next != null){
            if (node.val.equals(element)){
                return i;
            }
            i ++;
            node = node.next;
        }
        return -1;
    }

    @Override
    public int size() {
        return size;
    }

    @Override
    public void clear() {
        while (head.next != null){
            DoubleNode node = head.next;
            head.next =null;
            head.prev = null;
            head = node;
        }
        head = null;
        size = 0;
    }

    @Override
    public String toString() {
        StringBuilder sb = new StringBuilder();
        for(DoubleNode x = head; x != null; x = x.next){
            sb.append(x.val);
            sb.append("->");
            if(x.next == null){
                // 此时temp走到了尾结点
                sb.append("NULL");
            }
        }
        return sb.toString();
    }
}

3.2 Test类

public class DoubleNodeTest {
    public static void main(String[] args) {
        DoubleLinkedList<Integer> list = new DoubleLinkedList<>();
        list.add(1);
        list.add(3);
        list.add(5);
        list.add(9);
        list.add(3);
        list.add(3);
        System.out.println(list);
        System.out.println("清空链表");
        list.clear();
        System.out.println(list);
        list.add(6);
        list.add(10);
        list.add(5);
        list.add(7);
        list.add(10);
        list.add(10);

        System.out.println(list);
        System.out.println("------------添加测试-----------");
        System.out.println("从链表尾部添加99,头部添加99999");
        list.add(99);
        list.addFirst(99999);
        System.out.println(list.size());
        System.out.println("添加index为2,element为88");
        list.add(2,88);
        System.out.println(list);
        System.out.println(list.size());
        System.out.println("-----------删除测试------------");
        System.out.println("删除index为0");
        list.removeByIndex(0);
        System.out.println("删除元素为6");
        list.removeByValue(6);
        System.out.println("删除所有10");
        list.removeAllValue(10);
        System.out.println(list);
        System.out.println("-----------其他------------");
        System.out.println("查看是否包含10这个元素");
        System.out.println(list.contains(10));
        System.out.println("修改index为3,element为19");
        list.set(3,19);
        System.out.println("获取index为3的元素");
        System.out.println(list.get(3));
        System.out.println(list);
        System.out.println("获取element为88的index");
        System.out.println(list.indexOf(88));
        System.out.println("获取链表长度");
        System.out.println(list.size());
        System.out.println("清空链表");
        list.clear();
        System.out.println(list + "...");
    }
}

3.3 测试结果

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

【精品】华为认证数通HCIA+HCIP题库分享(含答案解析)

嗨~大家好久不见&#xff0c;我是薄荷学姐&#xff0c;随着华为业务也全球领域的迅猛发展&#xff0c;越来越多人开始重视华为认证的重要性。今天给大家分享一下去年8月份的题库&#xff0c;基本都是一样&#xff0c;希望可以帮助到大家哈想要通过华为认证&#xff0c;除了进行…

gdb调试工具和makemakefile工具

gdb调试工具和make/makefile工具 文章目录gdb调试工具和make/makefile工具一、gdb调试工具1.debug/release2.使用二、make/makefile1.什么是make/makefile2.编写一、gdb调试工具 1.debug/release 程序有两种默认的发布方式debug和release。release是无法进行调试的。Linux中g…

Bing+ChatGPT 对传统搜索引擎的降维打击

早些时候申请了新版 Bing 的内测资格&#xff0c;终于收到了通过的邮件。 一天的体验之后&#xff0c;我的感受是&#xff1a;当新版 Bing 具备了 ChatGPT 的聊天能力之后&#xff0c;它的能力不论是对传统搜索引擎&#xff0c;还是 ChatGPT 自身&#xff0c;都将是降维打击。 …

菜鸟刷题Day3

⭐作者&#xff1a;别动我的饭 ⭐专栏&#xff1a;菜鸟刷题 ⭐标语&#xff1a;悟已往之不谏&#xff0c;知来者之可追 一.字符串压缩&#xff1a;面试题 01.06. 字符串压缩 - 力扣&#xff08;LeetCode&#xff09; 描述 字符串压缩。利用字符重复出现的次数&#xff0c;编…

Python程序员看见一个好看的手机壁纸网站,开撸!

人生苦短&#xff0c;我用python 最近好像没什么大事&#xff0c; .那就采集一下小——姐——姐————看下吧~ python 安装包资料:点击此处跳转文末名片获取 最近有同学的爬虫代码出了bug&#xff0c;给问我怎么改 于是就发现了这个好看的手机壁纸网站。 这个图片应该是违规…

【Unity工具,简单学习】PUN 2,多人在线游戏开发,初步使用

【Unity工具&#xff0c;简单学习】PUN 2&#xff0c;多人在线网络工具前言简单介绍安装简单使用一些 nomenclature 部分连接到 Server设置简单的大厅UI游戏场景搭建关卡加载事后前言 链接 简单介绍 PUN 可以让你简单地开发多人游戏&#xff0c;在全球范围推出 让开发者不用…

【Java学习笔记】38.Java 发送邮件

Java 发送邮件 使用Java应用程序发送 E-mail 十分简单&#xff0c;但是首先你应该在你的机器上安装 JavaMail API 和Java Activation Framework (JAF) 。 您可以从 Java 网站下载最新版本的 JavaMail&#xff0c;打开网页右侧有个 Downloads 链接&#xff0c;点击它下载。 您…

MySQL注入秘籍【上篇】

MySQL注入秘籍【上篇】1.数据库敏感信息常用语句2.联合(UNION)查询注入3.报错注入原理常见报错注入函数1.数据库敏感信息常用语句 获取数据库版本信息 select version(); select innodb_version;获取当前用户 select user();获取当前数据库 select database()&#xff1b;数…

高数重点总结

高数 公式不要去死记 配合训练题在训练中记忆 完成一下这些题目 高中函数图像回忆 与其记忆各种公式不如去思考他们的本质 这和调用c动态库可不一样 考试的时候你相当于在使用汇编答题 1 定义域&#xff08;x&#xff09; 性质 1/x(x!0)√x(x>0 || x0)log a x (x>…

血氧仪是如何得出血氧饱和度值的?

目录 一、血氧饱和度概念 二、血氧饱和度监测意义 三、血氧饱和度的监测方式 四、容积脉搏波计算血氧饱和度原理 五、容积脉搏波波形的测量电路方案 1&#xff09;光源和光电探测器的集成测量模块&#xff1a;SFH7050—反射式 2&#xff09;模拟前端 六、市面上血氧仪类型…

Spring 源码解析 - Bean创建过程 以及 解决循环依赖

一、Spring Bean创建过程以及循环依赖 上篇文章对 Spring Bean资源的加载注册过程进行了源码梳理和解析&#xff0c;我们可以得到结论&#xff0c;资源文件中的 bean 定义信息&#xff0c;被组装成了 BeanDefinition 存放进了 beanDefinitionMap 容器中&#xff0c;那 Bean 是…

图形视图框架QGraphicsScene(场景,概念)

QGraphicsScene 该类充当 QGraphicsItems 的容器。它与 QGraphicsView 一起使用&#xff0c;用于在 2D 表面上可视化图形项目&#xff0c;例如线条、矩形、文本甚至自定义项目。 QGraphicsScene具有的功能&#xff1a; 提供用管理大量数据项的高速接口传播事件到每一个图形项…

艹,终于在8226上把灯点亮了

接上次点文章ESP8266还可以这样玩这次&#xff0c;我终于学会了在ESP8266上面点亮LED灯了现在一个单片机的价格是几块&#xff0c;加上一个晶振&#xff0c;再来一个快递费&#xff0c;十几块钱还是需要的。所以能用这个ESP8266来当单片机玩&#xff0c;还是比较不错的可以在ub…

【设计模式】创建型设计模式

文章目录1. 基础①如何学习设计模式② 类模型③ 类关系2. 设计原则3. 模板方法① 定义②背景③ 要点④ 本质⑤ 结构图⑥ 样例代码4. 观察者模式① 定义②背景③ 要点④ 本质⑤ 结构图⑥ 样例代码5. 策略模式① 定义②背景③ 要点④ 本质⑤ 结构图⑥ 样例代码1. 基础 ①如何学习…

三维电子沙盘数字沙盘开发教程第7课

三维电子沙盘数字沙盘大数据人工智能开发教程第7课设置system.ini 如下内容Server122.112.229.220userGisTestPasswordchinamtouch.com该数据库中只提供 成都市火车南站附近的数据请注意&#xff0c;104.0648,30.61658利用三方工具&#xff0c;如幻影粒子&#xff1a;或者flash…

python例程:《彩图版飞机大战》程序

目录开发环境要求运行方法《彩图版飞机大战》程序使用说明源码示例源码及说明文档下载路径开发环境要求 本系统的软件开发及运行环境具体如下。 操作系统&#xff1a;Windows 7、Windows 10。 Python版本&#xff1a;Python 3.7.1。 开发工具&#xff1a;PyCharm 2018。…

软件测试拿了几个20K offer,分享一波面经

1、你的测试职业发展是什么? 测试经验越多&#xff0c;测试能力越高。所以我的职业发展是需要时间积累的&#xff0c;一步步向着高级测试工程师奔去。而且我也有初步的职业规划&#xff0c;前3年积累测试经验&#xff0c;按如何做好测试工程师的要点去要求自己&#xff0c;不断…

算法基础---基础算法

文章目录 快速排序归并排序二分 整数二分浮点数二分高精度 高精度加法高精度减法高精度乘法高精度除法前缀和 一维前缀和二维前缀和差分 一维差分二维差分双指针位运算离散化区间合并一、快速排序 思想&#xff1a;1.首先确定一个分界点&#xff08;随机取任意一点为…

【云原生】k8s集群命令行工具kubectl基础操作命令实践详解

kubectl基础操作命令详解一、准备工作1.1、Replication Controller1.2、Deployment1.3、DaemonSet1.4、查看创建的svc和pod1.5、kubectl 命令自动补全设置二、kubectl语法三、基础操作命令3.1、api-resources3.2、api-versions3.3、create3.4、expose3.5、run3.6、set3.6.1、en…

filebrowser的权限实现RBAC效果

filebrowser安装和支持定制化&#xff0c;建议参考我这一篇文章filebrowser安装言归正传&#xff0c;目前发现客户需要有文件权限管理功能&#xff0c;我一开始也没仔细研究这块&#xff0c;我以为不支持呢&#xff0c;我今天就认真的了研究 下这个存储服务&#xff0c;其实是支…