04、数据结构与算法---双向链表

理解好这个与单向链表的差异很关键🙂


一、手动实现

java 复制代码
package structure;

public class MyLinkedList2 {
    // 标记头
    public  ListNode head;
    // 标记尾
    public  ListNode last;

    // 结点类定义
      static class ListNode {
        public int val;
        public ListNode next;
        public ListNode prev;

        public ListNode(int val) {
            this.val = val;
        }
    }

    // 头插法
    public void addFirst(int data) {

        ListNode n = new ListNode(data);

        //头结点为空,新节点既当头又当尾
        if(head == null){
            head = n;
            last = n;
        } else {
        n.next = head;
        head.prev = n;
        head = n;
        }

    }

    // 尾插法
    public void addLast(int data) {

        ListNode n = new ListNode(data);
        //用不到head
        if(last == null){
            head = n;
            last = n;
        } else {
            last.next = n;
            n.prev = last;
            last = n;
        }
    }

    //得到单链表结点的数量
    public int size() {
        int count = 0;
        ListNode cur = head;
        while (cur != null){
            count++;
            cur = cur.next;
        }
        return count;
    }

    // 任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index, int data) {

        int size = size();

        if (index < 0 || index > size){
            System.out.println("index不合法");
            return;
        }
        if (index == 0){
            addFirst(data);
            return;
        }
        if (index == size){
            addLast(data);
            return;
        }

        //找到index下标的地址curN
        ListNode curN = findIndex(index);

        ListNode n = new ListNode(data);
        n.next = curN;
        curN.prev.next = n;
        n.prev = curN.prev;
        curN.prev = n;

    }
    //寻找index下标的地址curN
    public ListNode findIndex(int index){
        ListNode cur = head;

        while (index != 0){
            cur = cur.next;
            index--;
        }
        return cur;
    }

    // 查找是否包含关键字key是否在单链表当中
    public boolean contains(int key) {
        ListNode cur = head;
        while (cur != null){
            if (cur.val == key){
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

    // 删除第一次出现关键字为key的节点
public void remove(int key) {

   ListNode del = head;

    while (del != null) {

        if (del.val == key) {

            //情况一:删除头,并保证它不能为空
            if (del == head) {

                head = head.next;
                if (head == null) {
                    last = null;
                } else {
                    head.prev = null;
                }

            }

             //情况二:删除尾
             else if (del == last) {
                 last = last.prev;
                 last.next = null;
             }

             //情况三:删除中间
             else {
                 ListNode before = del.prev;
                 before.next = del.next;
                 del.next.prev = before;
             }
             //删除一个后就退出
                return;
        }

            del = del.next;
        }

    }


    public void show(){

    ListNode cur = head;

        while (cur != null){
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

    public void clear(){
          head = null;
          last = null;
      }

    public static void main(String[] args) {

        MyLinkedList2 m = new MyLinkedList2();

        m.addLast(48);
        m.addLast(48);
        m.addLast(36);
        m.addLast(48);
        m.addLast(48);

        m.show();
        m.remove(48);
        m.show();

    }

}

二、与单向链表方法的对比图

1、头插法

假如head为空,应当设置新节点既是头又是尾

倘若没考虑到,head.prev就相当于是null.prev,代码空指针异常


2、尾插法

跟上面一个道理


3、指定位置插入中间元素

依旧遵循先绑定后面的原则,注意先后次序


4、删除遇到的第一个值为key的结点

分为三种情况,图中仅展示了删除中间结点

删除头结点的时候还要考虑到头结点为空的情况

它跟单链表删除的最大区别在于😮:

单链表需要找到一个前置结点来完成删除操作

双向链表本身就是双向的,不用单独找


5、删除所有值为key的结点

只需要把上图对应的方法中,限制只执行一次删除的return去掉,就可以执行循环删除的逻辑了


三、单链表的习题

5、合并两个有序链表

java 复制代码
 //合并两个有序链表
    public Node mergeTwoLists(Node list1, Node list2) {

        Node newH = new Node();
        Node tmp = newH;

        while (list1 != null && list2!= null){
            if(list1.val < list2.val){
                tmp.next = list1;
                list1 = list1.next;
            }else {
                tmp.next = list2;
                list2 = list2.next;
            }
            tmp = tmp.next;
        }

            if(list1 == null){
                tmp.next = list2;
            }

            if(list2 == null){
                tmp.next = list1;
            }

        return newH.next;
    }

这里使用了傀儡结点newH,它的next来存放头结点地址,让tmp代替newH往下走,一直到某个链表为空就合并完成了


6、回文链表

java 复制代码
  //回文链表
    public boolean isPalindrome(Node head) {

        Node slow = head;
        Node fast = head;

        if(head == null || head.next == null){
            return true;
        }

        //判断条件要奇偶兼容
        //1、找到中间结点slow
        while (fast != null && fast.next != null){
            fast = fast.next.next;
            slow = slow.next;
        }

        //2、翻转链表
        Node cur = slow.next;
        while (cur != null){
            Node curN = cur.next;
            cur.next = slow;
            slow = cur;
            cur = curN;
        }

        //3、判断回文结构
        while (head != slow){
            if(head.val != slow.val){
                return false;
            }
            //需要考虑偶数情况下的判断
            if(head.next == slow){
                return true;
            }
            head = head.next;
            slow = slow.next;
        }
        return true;
    }

这个相当于是之前两个题的缝合,找中间结点,翻转,最后回文

可以参考之前单链表的这两个题目解法来理解

第1、3步骤,需要考虑奇偶的场景


7、链表相交(较简单)

java 复制代码
 //链表相交
    public Node getIntersectionNode(Node headA, Node headB) {
        Node pl = headA;
        Node ps = headB;
        int linA = 0;
        int linB = 0;

        //确保两个链表不为空
        if(headA == null || headB == null){
            return null;
        }

        while(pl != null){
            pl = pl.next;
            linA++;
        }
        pl = headA;

        while(ps != null){
            ps = ps.next;
            linB++;
        }
        ps = headB;

        int lin = linA-linB;
        if(lin<0){
            pl = headB;
            ps = headA;
            lin = linB-linA;
        }
        //走到这里保证了pl是长链表,ps是短的

        //让pl先走差值步
        while(lin != 0 ){
            lin--;
            pl = pl.next;
        }
        //用值比较不安全,不能证明这是同一个结点
        while(pl != ps){
            pl = pl.next;
            ps = ps.next;
        }
        // pl == ps: 可能是交点,也可能都是null(不相交)
        return pl;
    }

主要逻辑就是让长链表先走差值步,最后两个一起走,找到一样的结点即相交结点


8、环形链表Ⅱ

首先要知道一个数学原理,在快慢指针相遇后:

从头节点和相遇点同时出发,每次走1步,它们再次相遇的位置就是环的入口

java 复制代码
   //环形链表Ⅱ
    public Node detectCycle(Node head) {
        Node fast = head;
        Node slow = head;

        //保证头不为空
        if(head == null || head.next == null){
            return null;
        }

        while (fast != null && fast.next != null){
            //结束情况一:循环走完结束
            fast = fast.next.next;
            slow = slow.next;

            //结束情况二:有环,直接退出
            if(fast == slow){
                break;
            }
        }

        //确保是因为二结束
        if (fast == slow){
            fast = head;

            while (fast != slow){
                fast = fast.next;
                slow = slow.next;
            }
            return fast;
        }
        return null;
    }

返回的是环的入口节点,没有环就是null


也有个更简洁的写法:

java 复制代码
   简洁版本
        public Node detectCycle(Node head) {
            if (head == null || head.next == null) {
                return null;
            }

           Node fast = head;
            Node slow = head;

            while (fast != null && fast.next != null) {
                fast = fast.next.next;
                slow = slow.next;

                if (fast == slow) {
                    fast = head;
                    while (fast != slow) {
                        fast = fast.next;
                        slow = slow.next;
                    }
                    return fast;
                }
            }

            return null;
        }
    }

本章完

相关推荐
Flittly2 小时前
【SpringAIAlibaba新手村系列】(17)百炼 RAG 知识库应用
java·人工智能·spring boot·spring·ai
穿条秋裤到处跑2 小时前
每日一道leetcode(2026.04.11):三个相等元素之间的最小距离 II
算法·leetcode
努力d小白3 小时前
java 数据类型
java
色空大师3 小时前
【微服务项目-短信平台】
java·redis·微服务·rabbitmq·springcloud·短信
我命由我123453 小时前
Android Jetpack Compose - SearchBar(搜索栏)、Tab(标签页)、时间选择器、TooltipBox(工具提示)
android·java·java-ee·kotlin·android studio·android jetpack·android-studio
橘颂TA3 小时前
【笔试】算法的暴力美学——牛客 BC140:杨辉三角
数据结构·牛客
27669582923 小时前
token1005 算法分析
java·前端·javascript·token·token1005·携程酒店·token算法分析
海寻山3 小时前
Java内部类:4种类型+实战场景+面试避坑
java·开发语言·面试
Lsk_Smion3 小时前
Hot100(开刷) 之 长度最小的数组--删除倒数第N个链表--层序遍历
java·数据结构·算法·kotlin