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

前言❤️❤️

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方法的逻辑就是这样)
  • 初学数据结构的铁汁,如果能把这三篇博客中的代码都自己敲一遍,你做链表题目的代码能力一定会有个很大的提升💪💪💪
相关推荐
小羊没烦恼!3 天前
微服务化的基石——持续集成
java·大数据·word·powerpoint·.net
俊昭喜喜里3 天前
java中的继承和多态的区别
java
小羊没烦恼!3 天前
初探性能优化——2个月到4小时的性能提升
java·开发语言·windows·算法·c#
譕痕3 天前
JSONObject与JSONArray封装数据格式区别
java·json
胡写代码3 天前
别再前后端各写一套表单校验了
java·后端
小鱼能吃糖3 天前
缺陷修复总览 · mall电商项目:5类缺陷,1个病根,4个业务域
java·电商
此时不提桶,更待何时3 天前
01-06-A-JVM排查实战详解
java·jvm
vipxieliang3 天前
ValidX 在 DDD 领域驱动设计中的实践
java·spring boot
m0_547486663 天前
《数据结构教程》全套 PPT课件2026
数据结构
ba_pi3 天前
mysql 查询所有表名并授权
java