链表补充练习,双链表的模拟实现

前言❤️❤️

hello hello💕,这里是洋不写bug~😄,欢迎大家点赞👍👍,关注😍😍,收藏🌹🌹

这篇博客是链表博客的最后一篇,内容比较少,就是模拟双链表的实现,进一步的锻炼代码能力,初学的铁汁建议先模拟实现单链表(链表博客中有提到),再来模拟实现双链表,因为很多方法是在单链表的基础上改了一点

这个专栏的数据结构是代码都是用Java来写的,JavaSE专栏现在已经全部更新完成,铁汁们复习基础知识时非常推荐使用,可以试一下💪💪💪

🎇个人主页:洋不写bug的博客
🎇所属专栏:数据结构专栏
🎇复习Java基础知识:Java学习之旅,从入门到进阶
🎇铁汁们对于数据结构基础的各种核心知识(不太常用的也有😆),都可以在上面的数据结构专栏学习,专栏正在持续更新中🐵🐵,有问题可以写在评论区或者私信我哦~

1,双链表简介

  • 单链表就是节点中只存了 next 引用,只能找到下一个节点。

  • 双链表中存储了 prev 引用和 next 引用,也可以找到上一个节点,这样在使用时自然是更方便的

  • 双链表的缺点就是空间利用率低,在Java中,链表的一个引用占8个字节,双向链表的引用就占16个字节,如果每个节点中保存数据的部分,占用空间很小(例如只保存了一个int类型的数字),那链表的空间利用率就是很低的

  • 2,双链表的模拟实现

  • 接下来就模拟实现个自己的双链表,里面存入的数据类型固定为String

  • 先建个Node类表示节点,相比单链表要多定义一个属性prev

java 复制代码
public class Node {
    public String val;
    public Node next = null;
    public Node prev = null;

    public Node(String val) {
        this.val = val;
    }
}
  • 在MyDLinkedList类中,搞一个尾节点,方便后续进行尾插
  • 对于addFirst方法,要判断下,如果head为null,也就是链表为空,那就让head和tail指向null;如果链表不为空,那就先让newNode指向head,把head的perv改为newNode,再更新下head
java 复制代码
public class MyDLinkedList {

    private Node head = null;
    private Node tail = null;
    
    public void addFirst(String val){
        Node newNode = new Node(val);
        if(head == null){
            head = newNode;
            tail = newNode;
        }else{
            newNode.next = head;
            head.prev = newNode;
            head = newNode;
        }
    }
}
  • 接着再写个toString方法,打印时使用
java 复制代码
    public String toString() {
        StringBuilder stringBuilder = new StringBuilder();
        stringBuilder.append("[");
        Node cur = head;
        while(cur != null){
            stringBuilder.append(cur.val);
            if(cur.next != null){
                stringBuilder.append(",");
            }
            cur = cur.next;
        }
        stringBuilder.append("]");
        return stringBuilder.toString();
    }
  • 每写完一个方法,都建议在main方法中测试一下(后面每个方法的测试代码博主就不粘了)
java 复制代码
public class Test {
    public static void main(String[] args) {
        MyDLinkedList myDLinkedList = new MyDLinkedList();
        myDLinkedList.addFirst("aaa");
        myDLinkedList.addFirst("bbb");
        myDLinkedList.addFirst("ccc");
        myDLinkedList.addFirst("ddd");
        System.out.println(myDLinkedList);
    }
}
  • 接着是尾插,因为链表中存储了最后一个节点的引用,就无需再去遍历找尾节点了
java 复制代码
    public void addLast(String val){
        Node newNode = new Node(val);
        if(head == null){
            head = newNode;
            tail = newNode;
        }else{
            tail.next = newNode;
            newNode.prev = tail;
            tail = newNode;
        }
    }
  • size方法如下,写法跟单链表相同
java 复制代码
 public int size(){
        Node cur = head;
        int size = 0;
        while(cur != null){
            size++;
            cur = cur.next;
        }
        return size;
    }
  • 中间位置插入方法add,就是传入下标和元素值,在中间位置插入元素,条件判断的时候,是可以等于size()的,相当于尾插;也可以等于0,相当于头插
  • 下面这两种写法,单从代码上来看,第二种写法效率是比较高的,因为第一种写法size()是可能执行两次的(判断时和打印错误信息时),而size()方法是遍历整个链表,还是有一定开销的
  • 但是如果测试的话,这两种写法的执行效率可能也差不多,这是因为Java的编译器在编译代码的时候,具有优化功能,可能会对代码的顺序,写法之类的进行调整,以降低程序员之间的代码差距(所以铁汁们用哪种写法根据自己的喜好来即可🐵)
java 复制代码
if(index < 0 || index > size()){
            throw new IndexOutOfBoundsException("size:" + size() + ",index:" + index);
        }
java 复制代码
int size = size();
        if(index < 0 || index > size){
            throw new IndexOutOfBoundsException("size:" + size + ",index:" + index);
        }
  • 接着分析中间位置插入的逻辑,首先把新节点的prev指向前面的节点,next指向后面的节点,接着再调下前面节点的next,和后面节点的prev即可

  • 对于首插和尾插,直接调用前面的方法即可

java 复制代码
    public void add(int index,String val){
        int size = size();
        if(index < 0 || index > size){
            throw new IndexOutOfBoundsException("size:" + size + ",index:" + index);
        }
        if(index == 0){
            addFirst(val);
            return;
        }
        if(index == size){
            addLast(val);
            return;
        }
        Node newNode = new Node(val);
        Node prev = head;
        for(int i = 0;i < index - 1;i++){
            prev = prev.next;
        }
        Node next = prev.next;
        newNode.prev = prev;
        newNode.next = next;
        prev.next = newNode;
        next.prev = newNode;
    }
  • contains和indexOf方法都是遍历下链表即可
java 复制代码
    public boolean contains(String val){
        if(head == null){
            return false;
        }
        Node cur = head;
        while(cur != null){
            if(cur.val.equals(val)){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }
java 复制代码
    public int indexOf(String val){
        int index = 0;
        if(head == null){
            return -1;
        }
        Node cur = head;
        while(cur != null){
            if(cur.val.equals(val)){
                return index;
            }
            index++;
            cur = cur.next;
        }
        return -1;
    }
  • removeFirst方法,删除头部元素,需要分为空链表,只有一个节点的链表,还有多个节点的链表
java 复制代码
    public void removeFirst(){
        if(head == null){
            return;
        }
        if(head.next == null){
            head = null;
            tail = null;
            return;
        }
        Node cur = head.next;
        cur.prev = null;
        head = head.next;
    }
  • removeLast方法,和removeFirst的写法类似
java 复制代码
    public void removeLast(){
        if(head == null){
            return;
        }
        if(head.next == null){
            head = null;
            tail = null;
            return;
        }
        Node cur = tail.prev;
        cur.next = null;
        tail = cur;
    }
  • 指定位置删除,remove方法,里面传入index,这里index就不能等于size()了,因为链表的下标是从0到index - 1的,需要找到要删除元素的上个位置和下个位置

  • 如果要删除的是头节点或者尾节点,直接调用前面已经写过的removeFirst和RemoveLast方法即可,排除这两种情况后,prev和next就不可能是null,我们就可以放心的去写代码

java 复制代码
   public void remove(int index){
        int size = size();
        if(index < 0 || index >= size){
            throw new IndexOutOfBoundsException("size:" + size + ",index:" + index);
        }
        if(index == 0){
            removeFirst();
            return;
        }
        if(index == size - 1){
            removeLast();
            return;
        }

        Node prev = head;
        for(int i = 0;i < index - 1;i++){
            prev = prev.next;
        }
        Node next = prev.next.next;
        prev.next = next;
        next.prev = prev;
    }
  • reomve方法传入元素的版本,就遍历一下链表,找到toRemove节点,接获取它的上个节点和下个节点,分别修改下next和prev的指向即可
java 复制代码
    public void remove(String val){
        Node toRemove = head;
        while(toRemove != null){
            if(toRemove.val.equals(val)){
                break;
            }
            toRemove = toRemove.next;
        }
        if(toRemove == null){
            return;
        }
        if(toRemove == head){
            removeFirst();
            return;
        }
        if(toRemove == tail){
            removeLast();
            return;
        }
        Node prev = toRemove.prev;
        Node next = toRemove.next;
        prev.next = next;
        next.prev = prev;
    }

结语

  • 双链表的模拟实现在日常开发中上不会用到,这里模拟实现同样是锻炼我们的代码能力,包括画图分析,对于特殊情况和边界的处理
  • 在写代码时,可以先考虑正常的情况,先写出代码,再结合正常情况的代码来分析,加上处理特殊情况的逻辑(remove方法的逻辑就是这样)
  • 初学数据结构的铁汁,如果能把这三篇博客中的代码都自己敲一遍,你做链表题目的代码能力一定会有个很大的提升💪💪💪
相关推荐
后台模板学习38 分钟前
用 IM 即时聊天项目一次讲清消息去重算法的踩坑与解决方案
java·数据库·spring
秋名RG40 分钟前
2026/6/15 系统故障复盘与整改方案
java·架构
今天要早睡_42 分钟前
C++ 核心语法速过:命名空间、引用、函数重载与 nullptr 深度解析
android·java·c++
南讯股份Nascent1 小时前
2026年CRM技术选型观察:当“数据打通“成为第一需求,电商CRM的架构价值在哪里
java·大数据·用户运营
心易行者1 小时前
Python自动化测试7步落地法:用python在线运行省掉90%环境配置时间
java·开发语言·人工智能·python·log4j·ai编程
白狐_7981 小时前
408 数据结构|外部排序:流程与 k 路归并
数据结构·算法
heima20161 小时前
长期复盘:拼团活动链接开发公司的行业现状与困境洞察
链表
霸道流氓气质2 小时前
Spring AI 技术细节:VectorStore 多库统一抽象
java·人工智能·spring
今天AI了吗2 小时前
Codex 配置自定义 AI API 完整指南:从零到一接入你的专属模型
java·人工智能·python·数据分析·embedding