数据结构与算法之双链表

Posted 未见花闻

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了数据结构与算法之双链表相关的知识,希望对你有一定的参考价值。

⭐️前面的话⭐️

本篇文章带大家认识数据结构与算法之双链表,链表是一种在逻辑结构连续,物理结构不连续的数据结构,可以分为单链表与双链表两类,正文将介绍双链表的增删查改,对于链表的概念已经在《数据结构与算法之单链表》一文中已经介绍过了,所以本文对于链表理论概念方面不再赘述,上次实现了不带头结点的单链表,这次就介绍一个带头的双链表吧!描述代码:Java。

📒博客主页:未见花闻的博客主页
🎉欢迎关注🔎点赞👍收藏⭐️留言📝
📌本文由未见花闻原创,CSDN首发!
📆首发时间:🌴2021年11月26日🌴
✉️坚持和努力一定能换来诗与远方!
💭参考书籍:📚《趣学数据结构》,📚《数据结构(C语言版)》,📚《漫画算法》
💬参考在线编程网站:🌐牛客网🌐力扣
博主的码云gitee,平常博主写的程序代码都在里面。
博主的github,平常博主写的程序代码都在里面。
🙏作者水平很有限,如果发现错误,一定要及时告知作者哦!感谢感谢!




1.双链表理论基础

1.1双链表的基本结构

我们知道链表在实际当中有8种,根据是否带头结点,是否循环,引用指向这三点派生出来许许多多类型的链表,但是本质上都是一样的,它始终在物理结构是离散的,逻辑结构上是连续的。本文将介绍的是带头结点非循环的双链表,下文都以双链表简称。
首先,无论是什么类型的链表,它结点的基本结构是一样的,都是数据域+引用(指针)域,单链表引用域只有一个方向的指向,而双链表引用域有前后双向的指向。

对于带头(傀儡)结点的双链表:头结点head的next域永远指向第一个结点,prev域永远指向null,如果该链表为空,则next域也指向空。

1.2双链表的与单链表的区别

单链表与双链表最大的区别就是方向,单链表是单向的,只能向一个方向“生长”,双链表是双向的,能够向双向进行“生长”。
在遍历链表时,单链表只能找到后继结点,找不到前驱结点,而双链表既能找到后继结点,也能找到前驱结点。
在对一目标结点进行删除时,单链表必须知道目标结点的前驱才能删除目标结点,而双链表不必知道目标结点的前驱,就能删除目标结点。

2.双链表从理论到实践

2.1双链表结点

class DoubleLinkedListNode 
    public int val;//数据域
    public DoubleLinkedListNode next;//指向后继
    public DoubleLinkedListNode prev;//指向前驱

    public DoubleLinkedListNode(int val) 
        this.val = val;//构造方法
    

2.2双链表(带头结点)类

public class DoubleLinkedList 
    public DoubleLinkedListNode head;//头结点
    public DoubleLinkedList() 
        this.head = new DoubleLinkedListNode(0);
    
    //功能实现方法

2.3双链表增删查改实现

2.3.1双链表的打印

这个很简单,就是遍历一遍双链表。

    public void display()
        DoubleLinkedListNode cur = head.next;
        while (cur != null) 
            System.out.print(cur.val + "   ");
            cur = cur.next;
        
        System.out.println();
    

2.3.2头插法

如果链表不为空,先让新结点指向后结点,再让头结点指向新结点。

如果链表为空,则不需要完成图中的步骤2(即不需要将head.next.prev指向新结点node)。

    //头插法
    public void addFirst(int data)
        DoubleLinkedListNode node = new DoubleLinkedListNode(data);
        node.next = head.next;
        if (head.next != null) 
            head.next.prev = node;
        
        head.next = node;
        node.prev = head;
    

2.3.3尾插法

找到链表最后一个结点,插入最后一个结点后即可,链表为空时,最后一个结点可能为头结点。

    //尾插法
    public void addLast(int data) 
        DoubleLinkedListNode cur = this.head;
        while (cur.next != null) 
            cur = cur.next;
        
        DoubleLinkedListNode node = new DoubleLinkedListNode(data);
        cur.next = node;
        node.prev = cur;
    

2.3.4链表长度获取

遍历就好。

    //得到双链表的长度
    public int size()
        int cnt = 0;
        DoubleLinkedListNode cur = this.head.next;
        while (cur != null) 
            cnt++;
            cur = cur.next;
        
        return cnt;
    

2.3.5在任意位置插入

分为3种情况:

  1. 在下标为0的位置插入,也就是头结点后,采用头插法。
  2. 在下标为链表长度size的位置插入,采用尾插法。
  3. 其他情况,按如图方式插入。


如果插入位置非法,直接返回或者抛异常,单链表实现中我们采用了直接返回的方法,这次采用抛异常。异常自定义,需要继承RuntimeExceptionException,前者时运行时异常的父类,会在运行时检查,后者为各类异常的父类,会在编译时检查。在这里,只有运行后才能知道传入的下标值是否合法,所以我们自定义异常时需继承RuntimeException类。

抛出的异常可以使用try…catch…语句捕获,这里就简单演示一下,如果不会直接返回就行,异常在后续博客会详细介绍。

try
    //可能会有异常的语句;
    dls.addIndex(20, 10);//可能存在下标非法
catch (IndexExcept(捕获异常类) e(引用变量)) 
	//捕获异常
    e.printStackTrace();//打印异常信息

class IndexExcept extends RuntimeException
    public IndexExcept(String message) 
        super(message);
    

    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data) throws IndexExcept
        if (index < 0 || index > size()) 
            throw new IndexExcept(index + "位置非法!");
        
        if (size() == 0) 
            addFirst(data);
            return;
        
        if (size() == index) 
            addLast(data);
            return;
        
        DoubleLinkedListNode node = new DoubleLinkedListNode(data);
        DoubleLinkedListNode cur = head;
        for (int i = 0; i < index; i++) 
            cur = cur.next;
        
        node.next = cur.next;
        cur.next.prev = node;
        node.prev = cur;
        cur.next = node;
    

2.3.6判断一个数据是否在链表中

遍历查找。

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

2.3.7删除双链表的结点

如果cur.next=null,直接让cur.prev = cur.next就可以了,其他情况将目标结点的前一个结点的next指向目标结点后一个结点,目标结点的后一个结点的prev指向目标结点的前一个结点。当链表为空的时候可以直接返回,也可以抛出一个异常,上次实现单链表采用了返回的方法,这次尝试抛一个自定义异常。

异常自定义,需要继承RuntimeExceptionException,前者时运行时异常的父类,会在运行时检查,后者为各类异常的父类,会在编译时检查。在这里,只有运行后才能知道链表是否为空,所以我们自定义异常时需继承RuntimeException类。

class LinkedListElemNull extends RuntimeException
    public LinkedListElemNull(String message) 
        super(message);
    

删除链表中值为key的第一个结点

    //删除第一次出现关键字为key的节点
    public void remove(int key)
        if (this.head.next == null) 
            throw new LinkedListElemNull("LinkedList is null");
        
        DoubleLinkedListNode cur = this.head.next;

        while (cur != null) 
            if (cur.val == key) 
                cur.prev.next = cur.next;
                if (cur.next != null) 
                    cur.next.prev = cur.prev;
                
                return;
            
            cur = cur.next;
        
        System.out.println("没有找到目标结点!");
    

删除链表中值为key的所有结点

    //删除所有值为key的节点
    public void removeAllKey(int key)
        if (this.head.next == null) 
            throw new LinkedListElemNull("LinkedList is null");
        
        DoubleLinkedListNode cur = this.head.next;
        while (cur != null) 
            if (cur.val == key) 
                cur.prev.next = cur.next;
                if (cur.next != null) 
                    cur.next.prev = cur.prev;
                
            
            cur = cur.next;
        
    

2.3.8双链表的销毁

与单链表一样,先保存后一个结点,再将当前结点的引用域置空。

    public void clear()
        DoubleLinkedListNode cur = this.head.next;
        DoubleLinkedListNode next = null;
        while (cur != null) 
            next = cur.next;
            cur.next = null;
            cur.prev = null;
            cur = next;
        
        this.head.next = null;
    

3.源代码

3.1实现类

class IndexExcept extends RuntimeException
    public IndexExcept(String message) 
        super(message);
    

class LinkedListElemNull extends RuntimeException
    public LinkedListElemNull(String message) 
        super(message);
    


class DoubleLinkedListNode 
    public int val;
    public DoubleLinkedListNode next;
    public DoubleLinkedListNode prev;

    public DoubleLinkedListNode(int val) 
        this.val = val;
    

public class DoubleLinkedList 
    public DoubleLinkedListNode head;//头结点
    public DoubleLinkedList() 
        this.head = new DoubleLinkedListNode(0);
    
    //头插法
    public void addFirst(int data)
        DoubleLinkedListNode node = new DoubleLinkedListNode(data);
        node.next = head.next;
        if (head.next != null) 
            head.next.prev = node;
        
        head.next = node;
        node.prev = head;
    
    //尾插法
    public void addLast(int data) 
        DoubleLinkedListNode cur = this.head;
        while (cur.next != null) 
            cur = cur.next;
        
        DoubleLinkedListNode node = new DoubleLinkedListNode(data);
        cur.next = node;
        node.prev = cur;
    
    //任意位置插入,第一个数据节点为0号下标
    public void addIndex(int index,int data) throws IndexExcept
        if (index < 0 || index > size()) 
            throw new IndexExcept(index + "位置非法!");
        
        if (size() == 0) 
            addFirst(data);
            return;
        
        if (size() == index) 
            addLast(data);
            return;
        
        DoubleLinkedListNode node = new DoubleLinkedListNode(data);
        DoubleLinkedListNode cur = head;
        for (int i = 0; i < index; i++) 
            cur = cur.next;
        
        node.next = cur.next;
        cur.next.prev = node;
        node.prev = cur;
        cur.next = node;
    
    //查找是否包含关键字key是否在单链表当中
    public boolean contains(int key)
        if (this.head.next == null) 
            return false;
        
        DoubleLinkedListNode cur = head.next;
        while (cur != null) 
            if (cur.val == key) 
                return true;
            
            cur = cur.next;
        
        return false;
    
    //删除第一次出现关键字为key的节点
    public void remove(int key)
        if (this.head.next == null) 
            throw new LinkedListElemNull("LinkedList is null");
        
        DoubleLinkedListNode cur = this.head.next;

        while (cur != null) 
            if (cur.val == key) 
                cur.prev.next = cur.next;
                if (cur.next != null) 
                    cur.next.prev = cur.prev;
                
                return;
            
            cur = cur.next;
        
        System.out.println("没有找到目标结点!");
    
    //删除所有值为key的节点
    public void removeAllKey(int key)
        if (this.head.next == null) 
            throw new LinkedListElemNull("LinkedList is null");
        
        DoubleLinkedListNode cur = this.head.next;
        while (cur != null) 
            if (cur.val == key) 
                cur.prev.next = cur.next;
                if (cur.next != null) 
                    cur.next.prev = cur.prev;
                
            
            cur = cur.next;
        
    
    //得到双链表的长度
    public int size()
        int cnt = 0;
        DoubleLinkedListNode cur = this.head.next;
        while (cur != null) 
            cnt++;
            cur = cur.next;
        
        return cnt;
    
    public void display()
        DoubleLinkedListNode cur = head.next;
        while (cur != null) 
            System.out.print(cur.val + "   ");
            cur = cur.next;
        
        System.out.println();
    
    public void clear()
        DoubleLinkedListNode cur = this.head.next;
        DoubleLinkedListNode next = null;
        while (cur != null) 
            next = cur.next;
            cur.next = null;
            cur.prev = null;
            cur = next;
        
        this.head.next = null;
    

3.2测试代码

public class Test 
    public static void main以上是关于数据结构与算法之双链表的主要内容,如果未能解决你的问题,请参考以下文章

#yyds干货盘点# 数据结构与算法之单链表

数据结构与算法之PHP实现链表类(单链表/双链表/循环链表)

双链表算法之遍历

双链表的结构和插入节点

数据结构与算法——双链表预习

数据结构与算法—一文多图搞懂双链表