当前位置: 首页 > news >正文

(数据结构)双向链表

(数据结构)带头双向循环链表

前言

  • 前面链表部分,咱们着重讲解了不带头单向不循环链表,简称单链表。那么链表其实也分很多种类适用于各种各样的场景。通过单链表的学习,其实我们已经大致了解了链表的绝大多数的内容,所以接下来我通过再为大家讲解一个带头双向循环链表,那么剩下的链表的种类大家就可以各自组合,各自书写了。
  • 链表种类:
    在这里插入图片描述
  • 两种代表链表:
  • 在这里插入图片描述
    1. 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
    2. 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带 来很多优势,实现反而简单了,后面我们代码实现了就知道了。

整体的实现代码如下:(头文件)

 #include<stdio.h>
 #include<stdlib.h>
 #include<assert.h>
 
 //对int类型取别名是为了后续方便更改数据类型
 typedef int LTDateType;
 //链表节点的结构
 typedef struct ListNode
 {
     struct ListNode *prev;
     struct ListNode *next;
     LTDateType date;
 }LTNode;
 
 //动态申请一个节点
 LTNode *BuyLTNode(LTDateType x);
 //对链表进行初始化
 LTNode *LTInit();
 //打印链表
 void LTPrint(LTNode* phead);
 
 //尾部插入数据
 void LTpushBack(LTNode *phead,LTDateType x);
 //尾部删除数据
 void LTpopBack(LTNode* phead);
 
 //头部插入数据
 void LTpushFront(LTNode *phead,LTDateType x);
 //头部删除数据
 void LTpopFront(LTNode* phead);
 
 //计算链表中有效数据的个数
 int LTSize(LTNode* phead);
 
 //查找数据值为x的节点
 LTNode *LTFind(LTNode *phead,LTDateType x);
 //在pos节点前插入数据为x的节点
 void LTInsert(LTNode* pos, LTDateType x);
 //删除pos位置处的节点
 void LTErase(LTNode* pos);
 
 //销毁链表
 void LTDestroy(LTNode* phead);
  • 函数实现文件:
 #include "List.h"
 
 LTNode *BuyLTNode(LTDateType x)
 {
     LTNode *node= (LTNode*)malloc(sizeof (LTNode));
     if(node==NULL)
     {
         perror("malloc fail");
         exit(-1);
     }
     node->date=x;
     node->next=NULL;
     node->prev=NULL;
 
     return node;
 }
 
 LTNode *LTInit()
 {
     LTNode *phead= BuyLTNode(-1);
     phead->prev=phead;
     phead->next=phead;
 
     return phead;
 }
 
 void LTPrint(LTNode* phead)
 {
     assert(phead);
 
     printf("phead<->");
     LTNode *cur=phead->next;
     while(cur!=phead)
     {
         printf("%d<->",cur->date);
         cur=cur->next;
     }
     printf("phead\n");
 }
 
 void LTpushBack(LTNode *phead,LTDateType x)
 {
     //判断phead是否为空指针
     assert(phead);
     //尾节点如下,无需再去遍历寻找尾节点
     LTNode *tail=phead->prev;
     //创建一个新节点
     LTNode *newnode=BuyLTNode(x);
 
     //改变指针指向,将尾节点与新节点相连
     newnode->prev=tail;
     tail->next=newnode;
 
     newnode->next=phead;
     phead->prev=newnode;
 
 }
 
 void LTpopBack(LTNode* phead)
 {
     assert(phead);
 
     if (phead->next == phead)
         exit(-1);
     else
     {
         LTNode *tail=phead->prev;
         phead->prev=tail->prev;
         phead->prev->next=phead;
         free(tail);
     }
 }
 
 void LTpushFront(LTNode *phead,LTDateType x)
 {
     assert(phead);
 /*  LTNode *newnode= BuyLTNode(x);
 
     newnode->next=phead->next;
     phead->next->prev=newnode;
 
     phead->next=newnode;
     newnode->prev=phead;
 */
     LTNode *newnode= BuyLTNode(x);
     LTNode *first=phead->next;
 
     phead->next=newnode;
     newnode->prev=phead;
     newnode->next=first;
     first->prev=newnode;
 
 }
 
 void LTpopFront(LTNode* phead)
 {
     assert(phead);
 
     if (phead->next == phead)
         exit(-1);
     else
     {
         /* LTNode *first=phead->next;
         phead->next=first->next;
         phead->next->prev=phead;
         free(first);*/
         LTNode *first=phead->next;
         LTNode *second=first->next;
         free(first);
         phead->next=second;
         second->prev=phead;
     }
 
 }
 
 int LTSize(LTNode* phead)
 {
     assert(phead);
     int size=0;
     LTNode *cur=phead->next;
     while(cur!=phead)
     {
         size++;
         cur=cur->next;
     }
     return size;
 }
 
 LTNode *LTFind(LTNode *phead,LTDateType x)
 {
     assert(phead);
 
     LTNode *cur=phead->next;
     while(cur!=phead)
     {
         if(cur->date==x)
             return cur;
         cur=cur->next;
     }
 
     return NULL;
 
 }
 
 void LTInsert(LTNode* pos, LTDateType x)
 {
     assert(pos);
 
     LTNode *posPrev=pos->prev;
     LTNode *newnode= BuyLTNode(x);
 
     posPrev->next=newnode;
     newnode->prev=posPrev;
     newnode->next=pos;
     pos->prev=newnode;
 
 }
 
 void LTErase(LTNode* pos)
 {
     assert(pos);
 
 /*    pos->prev->next=pos->next;
     pos->next->prev=pos->prev;
 
     free(pos);*/
     LTNode *posPrev=pos->prev;
     LTNode *posNext=pos->next;
 
     free(pos);
 
     posPrev->next=posNext;
     posNext->prev=posPrev;
 
 }
 
 void LTDestroy(LTNode* phead)
 {
     assert(phead);
 
     LTNode *cur=phead->next;
     while(cur!=phead)
     {
         LTNode *next=cur->next;
         free(cur);
 
         cur=next;
     }
 
     free(phead);
 
 }
 
  • 测试文件:(这里的测试文件,其实就是大家调用写好的函数,看功能是否正确,大家可以自行更改,这里只是一个示例)
 #include "List.h"
 
 void test1()
 {
     LTNode *plist=LTInit();
     LTpushBack(plist,1);
     LTpushBack(plist,2);
     LTpushBack(plist,3);
     LTpushBack(plist,4);
     LTPrint(plist);
     int c= LTSize(plist);
     printf("%d\n",c);
     LTNode *node= LTFind(plist,3);
     LTInsert(node,6);
     LTErase(node);
     LTPrint(plist);
     LTDestroy(plist);
     plist=NULL;
 }
 
 int main()
 {
     test1();
 
     return 0;
 }
 

函数逐个讲解:

  1. 第一个动态申请链表节点:
 
 LTNode *BuyLTNode(LTDateType x)
 {
     LTNode *node= (LTNode*)malloc(sizeof (LTNode));
     if(node==NULL)
     {
         perror("malloc fail");
         exit(-1);
     }
     node->date=x;
     node->next=NULL;
     node->prev=NULL;
 
     return node;
 }
  • 这个函数没什么好说的,就是用malloc和sizeof来申请节点,如果申请成功就赋值,失败则exit。
  1. 初始化函数:
LTNode *LTInit()
{
    LTNode *phead= BuyLTNode(-1);
    phead->prev=phead;
    phead->next=phead;

    return phead;
}
  • 在这里插入图片描述
  1. 打印函数:

    void LTPrint(LTNode* phead)
    {
        assert(phead);
    
        printf("phead<->");
        LTNode *cur=phead->next;
        while(cur!=phead)
        {
            printf("%d<->",cur->date);
            cur=cur->next;
        }
        printf("phead\n");
    }
    

在这里插入图片描述
4. 尾部插入函数:

void LTpushBack(LTNode *phead,LTDateType x)
{
    //判断phead是否为空指针
    assert(phead);
    //尾节点如下,无需再去遍历寻找尾节点
    LTNode *tail=phead->prev;
    //创建一个新节点
    LTNode *newnode=BuyLTNode(x);

    //改变指针指向,将尾节点与新节点相连
    newnode->prev=tail;
    tail->next=newnode;

    newnode->next=phead;
    phead->prev=newnode;

}

在这里插入图片描述
5. 尾部删除函数

void LTpopBack(LTNode* phead)
{
    assert(phead);

    if (phead->next == phead)
        exit(-1);
    else
    {
        LTNode *tail=phead->prev;
        phead->prev=tail->prev;
        phead->prev->next=phead;
        free(tail);
    }
}

在这里插入图片描述
6. 头部插入函数

void LTpushFront(LTNode *phead,LTDateType x)
{
    assert(phead);
/*  LTNode *newnode= BuyLTNode(x);

    newnode->next=phead->next;
    phead->next->prev=newnode;

    phead->next=newnode;
    newnode->prev=phead;
*/
    LTNode *newnode= BuyLTNode(x);
    LTNode *first=phead->next;

    phead->next=newnode;
    newnode->prev=phead;
    newnode->next=first;
    first->prev=newnode;

}

在这里插入图片描述
7. 头部删除函数

void LTpopFront(LTNode* phead)
{
    assert(phead);

    if (phead->next == phead)
        exit(-1);
    else
    {
        /* LTNode *first=phead->next;
        phead->next=first->next;
        phead->next->prev=phead;
        free(first);*/
        LTNode *first=phead->next;
        LTNode *second=first->next;
        free(first);
        phead->next=second;
        second->prev=phead;
    }

}

在这里插入图片描述
8. 计算有效节点个数

int LTSize(LTNode* phead)
{
    assert(phead);
    int size=0;
    LTNode *cur=phead->next;
    while(cur!=phead)
    {
        size++;
        cur=cur->next;
    }
    return size;
}

在这里插入图片描述

  • 补充一点就是,我这里是因为哨兵位的结构和链表节点的结构是一样的,是共用链表节点的结构的。也就是说,哨兵位里面的date的类型是和链表节点里存的数据类型是一样的,都是LTDataType。所以这里的如果要用哨兵位记录链表长度就只能是int类型,如果LTDataType变成了char类型这些,那么哨兵位里的data+1结果就会不如人意了,字符+1和int整型+1那肯定是不一样的结果嘛。
  • 但是如果你就要哨兵位记录链表长度,那就单独再创建一个哨兵位的结构,和链表节点的结构脱离,这个可以参考我讲队列的那篇文章。
  1. 查找数据的值为x的节点
LTNode *LTFind(LTNode *phead,LTDateType x)
{
    assert(phead);

    LTNode *cur=phead->next;
    while(cur!=phead)
    {
        if(cur->date==x)
            return cur;
        cur=cur->next;
    }

    return NULL;

}
  • 这里这个函数功能和上一个函数,也就是计算有效个数函数和打印函数实现逻辑类似,就不多说了。
  1. 在pos位置前插入值为x的节点
void LTInsert(LTNode* pos, LTDateType x)
{
    assert(pos);

    LTNode *posPrev=pos->prev;
    LTNode *newnode= BuyLTNode(x);

    posPrev->next=newnode;
    newnode->prev=posPrev;
    newnode->next=pos;
    pos->prev=newnode;

}

在这里插入图片描述

  • 这个函数也可以复用在尾插和头插函数里
  1. 删除pos位置的节点

void LTErase(LTNode* pos)
{
    assert(pos);

/*    pos->prev->next=pos->next;
    pos->next->prev=pos->prev;

    free(pos);*/
    LTNode *posPrev=pos->prev;
    LTNode *posNext=pos->next;

    free(pos);

    posPrev->next=posNext;
    posNext->prev=posPrev;

}

在这里插入图片描述

  • 这个函数也可以复用在尾删和头删里
  1. 链表销毁函数
void LTDestroy(LTNode* phead)
{
    assert(phead);

    LTNode *cur=phead->next;
    while(cur!=phead)
    {
        LTNode *next=cur->next;
        free(cur);

        cur=next;
    }


}

在这里插入图片描述

  • 到此,链表的内容就全部讲完了。

相关文章:

  • 2025-03-09 学习记录--C/C++-PTA 习题11-1 输出月份英文名
  • BEVDepth: Acquisition of Reliable Depth for Multi-view 3D Object Detection 论文阅读
  • 代码随想录算法训练营第八天|Leetcode 151.翻转字符串里的单词 卡码网:55.右旋转字符串 字符串总结 双指针回顾
  • 基于Spring3的抽奖系统
  • 通义千问:Qwen2.5-0.5B模型架构解释
  • 绘制列线图并解释线性模型的Python包-nomogram-explainer(记我的第一个pypi项目)
  • 配置多区域OSPF,配置OSPF手动汇总,配置OSPF特殊区域
  • IO多路复用实现并发服务器
  • 电路研究10——MPU6050电路搭建
  • InDraw6.2.3 | 甾体、核苷、黄酮类化合物实现简称命名
  • 模板注入Smarty
  • 《C++ primer》第六章
  • Linux基本操作指令3
  • 用K8S部署Milvus服务
  • 设备树的概念
  • 【网络编程】简单的网络服务器设计
  • 编程题-计算器(中等)
  • 耘锄、铧式犁、畦作
  • 实现静态网络爬虫(入门篇)
  • openwrt路由系统------Linux 驱动开发的核心步骤
  • 网站建设与推广培训学校/网站制作和推广
  • 给公司做网站和公众号需要多少钱/女排联赛最新排行榜
  • 简易静态网站制作流程图/网站有吗免费的
  • 建设网站好公司/培训机构管理系统
  • h5网站开发/企业自助建站
  • 芜湖网站推广/邯郸seo优化