目录

🐧今日良言:生如逆旅单行道,哪有岁月可回头

⛄一、介绍双向链表

🐟1.双向链表的相关定义

🐎2.双向链表的几种类型

⛄二、实现双向链表

🐄1.思路分析

🐉2.双向链表中的相关操作

⛄三、完整代码


🐧今日良言:生如逆旅单行道,哪有岁月可回头

Java双向链表实现 java双向链表类_结点

⛄一、介绍双向链表

🐎1.双向链表的相关定义

双向链表也叫双链表,是链表的一种,它的每个数据结点中都有两 指针 ,分别指向直接后继节点和直接前驱节点。

🐟2.双向链表的几种类型

带头循环双向链表

Java双向链表实现 java双向链表类_双向链表_02

带头不循环双向链表

Java双向链表实现 java双向链表类_结点_03

不带头循环双向链表

Java双向链表实现 java双向链表类_结点_04

不带头不循环双向链表

Java双向链表实现 java双向链表类_链表_05

实现的是不带头不循环双向链表

⛄二、实现双向链表

🐉1.思路分析

经过单链表的学习,对于链表我们有了一定的了解,双向链表只是在单链表的基础之上多了一个prev(指向直接前驱节点)的指针域,先创建一个主类MyLinkedList,我们将对双向链表进行的相关操作全部放到这个类中,然后创建一个节点类ListNode ,其中的成员属性如下:

public int val;// 数据域 public ListNode prev; // 指向前驱节点的指针域 public ListNode next; // 指向后继节点的指针域

提供一个构造方法,进行初始化

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

我们还需要在类MyLinkedList中定义一个指向头结点的ListNode 类型 的head和指向尾结点的ListNode类型的tail 

public ListNode head;// 头节点 public ListNode tail;// 尾节点

🐄2.双向链表中的相关操作

1.打印双向链表

双向链表的打印和单链表相同,定义一个辅助节点cur,从头节点head处开始打印,当cur为空时结束循环代码如下

// 打印双向链表
    public void display() {
        ListNode cur = this.head;
        if (cur == null) {
            System.out.println("双向链表为空");
            return;
        }
        while (cur != null) {
            System.out.print(cur.val+" ");
            cur = cur.next;
        }
        System.out.println();
    }

2.得到链表结点个数

与单链表求节点个数方法相同,定义一个辅助节点从head开始,再定义一个count用来计数,每次count++,cur为空结束循环,代码如下:

// 求链表长度
    public int size() {
        ListNode cur = this.head;
        int count = 0;
        while (cur != null) {
          count++;
          cur = cur.next;
        }
        return count;
    }

3.头插法

先判断当前头节点是否为空,为空说明是第一次插入数据,直接将要插入的这个节点当做头节点和尾节点,如果不为空说明不是第一次插入数据,此时先让这个节点的next指针域指向头节点,再让头节点的prev指针域指向该节点,最后将该节点当做新的头节点

图解如下:

Java双向链表实现 java双向链表类_Java双向链表实现_06

代码如下

// 头插法
    public void addFirst(int val) {
        ListNode node = new ListNode(val);
        if (this.head == null) {
            this.head = node;
            this.tail = node;
        } else {
            node.next = this.head;
            this.head.prev = node;
            this.head = node;
        }
    }

4.尾插法

首先也是判断head是否为空,如果为空就将待插入节点当做新的head和tail,如果不为空,说明不是第一次插入数据,与单链表不同,单链表是需要遍历找到最后一个节点,然后插入节点,而双向链表有一个尾结点tail,此时直接将节点插入到tail节点后面即可,最后将待插入节点当做新的tail,图解如下:

Java双向链表实现 java双向链表类_结点_07

代码如下

// 尾插法
    public void addLast(int val) {
        ListNode node = new ListNode(val);
        if (this.head == null) {
            this.head = node;
            this.tail = node;
        } else {
            this.tail.next = node;
            node.prev = this.tail;
            this.tail = node;
        }
    }

5.在任意位置插入节点,第一个节点为0下标

5.1.需要对双向链表进行判空,如果为空,抛出一个异常

if (isEmpty()) {
            throw new RuntimeException("双向链表为空");
        }

5.2我们需要检查要插入位置pos的合法性,当index <  0  || index > size() ,此时index不合法可以抛出一个异常

// 1.判断index合法性
        if (index < 0 || index > size()) {
            throw  new IndexException("index位置不合法");
        }

5.3此时需要判断是不是尾插法(当index == size())和头插法(index == 0)

// 头插尾插
        if (index == 0) {
            addFirst(val);
            return;
        }
        if (index == size()) {
            addLast(val);
            return;
        }

5.4创建一个方法getIndex,参数为index,与单链表的插入不同的是,可以定义一个辅助节点cur,让cur走index步,最后返回cur

private ListNode getIndex(int index) {
        ListNode cur = this.head;
        while (index > 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }

5.5这里需要注意连接顺序,连接顺序出错,可能会导致空指针异常

// 找到index下标开始插入
        ListNode cur = getIndex(index);
        ListNode node = new ListNode(val);
        node.next = cur;
        node.prev = cur.prev;
        cur.prev.next = node;
        cur.prev = node;

完整代码:

// 在index位置插入数据
    public void addIndex(int index,int val) {
         if (isEmpty()) {
            throw new RuntimeException("双向链表为空");
        }
        // 判断index合法性
        if (index < 0 || index > size()) {
            throw  new IndexException("index位置不合法");
        }
        // 头插尾插
        if (index == 0) {
            addFirst(val);
            return;
        }
        if (index == size()) {
            addLast(val);
            return;
        }
        // 找到index下标开始插入
        ListNode cur = getIndex(index);
        ListNode node = new ListNode(val);
        node.next = cur;
        node.prev = cur.prev;
        cur.prev.next = node;
        cur.prev = node;
    }
    private ListNode getIndex(int index) {
        ListNode cur = this.head;
        while (index > 0) {
            cur = cur.next;
            index--;
        }
        return cur;
    }

6.查找是否包含关键字key的节点在链表中

定义一个辅助节点cur,从头节点开始查找,找到就返回true,否则返回false

public boolean contains(int val) {
        if (isEmpty()) {
            throw new RuntimeException("双向链表为空");
        }
        // 遍历查找
        ListNode cur = this.head;
        while (cur != null) {
            if (cur.val == val) {
                return true;
            }
            cur = cur.next;
        }
        return false;
    }

7.删除第一次出现的key值节点

1.先判断双向链表是否为空

2.定义一个辅助节点cur,从头结点开始,当cur为空结束循环,进入循环后,先判断当前cur.val是否等于key值,如果等于就执行删除操作

3.执行删除操作时,需要对当前cur节点进行讨论

3.1 如果当前cur节点是头结点,让头节点指向头节点的下一个节点,接下来的操作,是将新头节点的prev指针域置空,但是,这里不能忽略一种情况,如果我的链表只有一个节点,那么当前新头节点就是空指针,执行空指针的prev操作会发生空指针异常,所以说,这里需要判断新头节点如果为空,就将尾节点tail也置空,如果不为空,就将新头节点的prev指针置空

this.head = this.head.next; // 判断是不是只有一个节点的情况 if (this.head == null) { this.tail = null; } else { this.head.prev = null; }

3.2如果当前cur节点不是头节点,此时,判断当前节点是不是尾结点

如果是尾结点的话,直接修改tail的指向,让tail指向前一个节点,然后再将修改后的tail的next指针域置空 

this.tail = this.tail.prev; this.tail.next = null;

如果不是尾结点,让当前cur的前一个节点的next指针域指向cur的下一个节点,再让cur的下一个节点的prev指针域指向cur的前一个节点

Java双向链表实现 java双向链表类_Java双向链表实现_08

cur.prev.next = cur.next;
cur.next.prev = cur.prev;

删除节点后直接return结束操作即可。 

 完整代码:

// 删除第一次出现key值的节点
    public void remove(int key) {
        if (this.head == null) {
            System.out.println("链表为空,无法删除");
            return;
        }
        ListNode cur = this.head;
        while (cur != null) {
            if (cur.val == key) {
                // 如果是删除头节点
                if (cur == this.head) {
                    this.head = this.head.next;
                    // 此时判断是不是只有一个节点的情况,避免空指针异常
                    if (this.head != null) {
                        this.head.prev = null;
                    } else {
                        // 只有一个节点
                        this.tail = null;
                    }
                } else {
                    //  不是头节点
                    // 判断是不是尾节点
                    if (cur == this.tail) {
                        this.tail = cur.prev;
                        this.tail.next = null;
                    } else {
                        cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    }
                }
                return;
            }
            cur = cur.next;
        }
      
       System.out.println("没有你要删除的数据");
        
    }

8.删除所有的key值节点

该操作与删除第一次出现key值节点的操作思路是一样的,只是删除第一次出现的key值操作删除节点后,我们直接return 结束了删除操作,而删除所有的key值节点删除return操作,让cur等于null结束循环,即可删除所有key节点

代码如下

// 删除第一次出现key值的节点
    public void remove(int key) {
        if (this.head == null) {
            System.out.println("链表为空,无法删除");
            return;
        }
        ListNode cur = this.head;
        while (cur != null) {
            if (cur.val == key) {
                // 如果是删除头节点
                if (cur == this.head) {
                    this.head = this.head.next;
                    // 此时判断是不是只有一个节点的情况,避免空指针异常
                    if (this.head != null) {
                        this.head.prev = null;
                    } else {
                        // 只有一个节点
                        this.tail = null;
                    }
                } else {
                    //  不是头节点
                    // 判断是不是尾节点
                    if (cur == this.tail) {
                        this.tail = cur.prev;
                        this.tail.next = null;
                    } else {
                        cur.prev.next = cur.next;
                        cur.next.prev = cur.prev;
                    }
                }
            }
            cur = cur.next;
        }
      
       System.out.println("没有你要删除的数据");
        
    }

9.清空双向链表

定义一个辅助结点cur,从头节点开始,直到cur为空结束操作,在循环体中,每次记录当前cur节点的下一个后继节点curNext,然后将当前cur的next域和prev域置空,最后修改cur为curNext。

当循环结束后,将head和tail置空,即可完成清空双向链表操作

代码如下:

// 清空链表
    public void clear() {
        ListNode cur = this.head;
        while (cur != null) {
            ListNode curNext = cur.next;
            cur.prev = null;
            cur.next = null;
            cur = curNext;
        }
        this.tail = null;
        this.head = null;
    }