• 数据结构 —— 双向链表(超详细图解 & 接口函数实现)


    系列文章目录

    数据结构 —— 顺序表
    数据结构 —— 单链表
    数据结构 —— 双向链表
    数据结构 —— 栈



    前言

    数据结构是计算机存储、组织数据的方式。数据结构是指相互之间存在一种或多种特定关系的数据元素的集合。数据结构是一种十分优秀的解决实际问题的模板,是先进思想的结晶。博主将会用代码结合大量图解,对数据结构进行深度剖析,以便大家更好的学习数据结构的思想。


    一、示例问题:城市对外回环交通

    城市对外交通实现了从一个城市到另一个城市的道路,但今天所讨论的仅限于一个城市连接其他两个城市的回环交通结构。

    1.城市回环交通

    城市对外交通指的是城市与城市之间的交通,是连接城市与城市之间往返的重要路径

    在这里插入图片描述

    2.逻辑示意图

    城市回环交通结构与本文将的内容极其相似,我们由城市的回环交通来引入双向带头循环链表

    在这里插入图片描述

    3.双向链表的引入

    双向链表的引入:既能指向前一个节点又指向下一个节点存储内容地址

    二、双向链表的概念

    1.定义

    链表:由 n 个节点链接成一个链表,即线性表的链式存储结构

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

    节点:

    1. 我们把存储数据元素信息的域称为数据域。
    2. 存储数据元素之间的链接信息即下一个存储数据元素地址的部分称为指针域。
    3. 由数据域和指针域组成的数据元素a的存储映像称为节点。

    在这里插入图片描述

    2.结构

    双向链表的结构类型

    typedef int LTDateType;			//便于更改存储类型
    
    typedef struct ListNode {		
        LTDateType data;			//数据域:存储数据元素信息
        struct ListNode *prev;		//指针域:存储上一个节点信息
        struct ListNode *next;		//指针域:存储下一个节点信息
    } ListNode;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    3.存储

    存储:用动态开辟的sizeof(ListNode)大小的一块空间进行存储

    在这里插入图片描述

    三、双向链表的接口函数

    1.动态申请空间

    动态开辟一块sizeof(ListNode)大小的空间进行存储

    // 动态申请一个结点
    ListNode *BuyListNode(LTDateType x) {
        ListNode *node = (ListNode *) malloc(sizeof(ListNode));
        node->data = x;
        node->prev = NULL;
        node->next = NULL;
        return node;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    在这里插入图片描述

    2.创建哨兵位

    创建返回链表的哨兵位

    // 创建返回链表的哨兵位
    ListNode *ListInit() {
        ListNode *pHead = BuyListNode(-1);
        pHead->prev = pHead;
        pHead->next = pHead;
        return pHead;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    在这里插入图片描述

    3.查找指定数据

    双向链表查找指定数据

    // 双向链表查找
    ListNode *ListFind(ListNode *pHead, LTDateType x) {
        assert(pHead);
    
        ListNode *curr = pHead->next;
        while (curr != pHead) {
            if (curr->data == x) {
                return curr;
            }
            curr = curr->next;
        }
        return NULL;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13

    在这里插入图片描述

    4.指定位置插入

    双向链表在指定位置 pos 插入 x

    // 双向链表在pos位置插入x
    void ListInsert(ListNode *pos, LTDateType x) {
        assert(pos);
    
        ListNode *newNode = BuyListNode(x);
        ListNode *prev = pos->prev;
    
        newNode->prev = prev;
        newNode->next = pos;
        prev->next = newNode;
        pos->prev = newNode;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    在这里插入图片描述

    5.指定位置删除

    双向链表删除在指定位置 pos

    // 双向链表在pos位置删除
    void ListErase(ListNode *pos) {
        assert(pos);
        assert(pos != pos->next);
    
        pos->next->prev = pos->prev;
        pos->prev->next = pos->next;
        free(pos);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    在这里插入图片描述

    6.头部插入

    双向链表在哨兵位之后的位置插入 x

    // 双向链表头插
    void ListPushFront(ListNode *pHead, LTDateType x) {
        ListInsert(pHead->next, x);
    }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    7.头部删除

    双向链表删除哨兵位的后一个节点

    // 双向链表头删
    void ListPopFront(ListNode *pHead) {
        ListErase(pHead->next);
    }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    8.尾部插入

    双向链表在哨兵位之前的位置插入 x

    // 双向链表尾插
    void ListPushBack(ListNode *pHead, LTDateType x) {
        ListInsert(pHead, x);
    }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    9.尾部删除

    双向链表删除哨兵位的前一个节点

    // 双向链表尾删
    void ListPopBack(ListNode *pHead) {
        ListErase(pHead->prev);
    }
    
    • 1
    • 2
    • 3
    • 4

    在这里插入图片描述

    10.计算链表大小

    计算双向链表的大小

    // 计算大小
    int ListSize(ListNode *pHead) {
        ListNode *curr = pHead->next;
        int size = 0;
        while (curr != pHead) {
            size++;
            curr = curr->next;
        }
        return size;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    11.销毁链表

    销毁双向链表,请手动置空

    // 销毁(手动置空)
    void ListDestory(ListNode *pHead) {
        ListNode *curr = pHead->next;
        while (curr != pHead) {
            ListNode *next = curr->next;
            free(curr);
            curr = next;
        }
        free(pHead);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10


    四、总结

    双向链表是解决实际问题时极其常用的一种数据结构,是非常重要的解决问题的方式。双向链表的各种接口的复现,有利于更好的学习数据结构的思想,有利于开阔视野,学习前辈的智慧结晶。对我们后续解决实际问题也会有很大帮助。

  • 相关阅读:
    三篇学会MySQL数据库【查询详解】
    【Linux】虚拟机部署与发布J2EE项目(Windows版本)
    第二十七讲.动态设置相关参数(replication为2和blocksize为10字节)
    Ajax——AJAX跨域问题
    2022最新windows上传ipa文件到app store的方法
    鼠标维修笔记
    前端-(6)
    【图解大数据技术】流式计算:Spark Streaming、Flink
    Redis理解
    快速分割视频,并提取原视频中的音频单独保存
  • 原文地址:https://blog.csdn.net/qq_64589446/article/details/126220605