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

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

双向链表 --- List

  • 前言
  • 一、初始化和销毁
    • 1.初始化
    • 2.销毁
  • 二、增删改查
    • 1.尾插
    • 2.头插
    • 3.尾删
    • 4.头删
    • 5.pos位置之后 / 之前插入
    • 6.删除pos位置节点
    • 7.查找指定数据的节点
  • 三、其他
    • 1.打印
    • 2.判空
  • 四、总结

前言

本次实现的双向链表,全名叫双向带头循环链表,使用C语言实现。
总体结构如下:

在这里插入图片描述
所以基于上图本次实现的双向链表成员有三:data数据域,prve指向前一个节点的指针域,next指向下一个节点的指针域。

// 重命名方便存储多种类型的数据
typedef int Datetype;// 实现双向链表,全称双向带头循环链表
typedef struct ListNode
{struct ListNode* next;           // 指向下一个节点的指针struct ListNode* prve;           // 指向前一个节点的指针Datetype data;                   // 存储的数据
}LNnode;

节点的结构就是双向链表的结构,这一点和单链表是一样的。

一、初始化和销毁

1.初始化

对于双向链表的初始化,不和单链表一样初始化为空,由于自带一个头节点,所以初始状态就只有一个头节点,并且也要满足双向而且循环的特点。
如下图:
在这里插入图片描述
初始状态直接自己指向自己即可。

// 申请节点
LNnode* LNBuynode(Datetype x)
{// 开一个节点大小的空间LNnode* newnode = (LNnode*)malloc(sizeof(LNnode));if (newnode == NULL){// 提示错误perror("malloc!!");// 强制不正常退出exit(1);}newnode->data = x;newnode->next = newnode->prve = newnode;return newnode;
}

在初始化函数中调用上述函数即可。

初始化函数1:

// 初始化(1)
void LNInit(LNnode** pphead)
{// 传过来的pphead不能为空assert(pphead);*pphead = LNBuynode(-1);
}

初始化函数2:

//初始化(2)
LNnode* LNInit()
{LNnode* phead = LNBuynode(-1);return phead;
}

对比上述两种函数,函数一是传递一个头节点过去,进行初始化;而函数二是直接进行初始化操作,本次使用的是函数二进行初始化。

2.销毁

双向链表的销毁就是对所有的节点进行销毁,头节点也不例外。

// 销毁链表
// 销毁链表是所有节点都销毁,头节点也要销毁
void LNdestroy(LNnode* phead)
{assert(phead);// 标记头节点的下一个节点LNnode* pcur = phead->next;// 销毁除头节点的其他节点while (pcur != phead){LNnode* pnext = pcur->next;free(pcur);pcur = pnext;}// 销毁头节点free(phead);phead = NULL;
}

不过销毁顺序有点不同,首先销毁除开头节点的其他节点,因为在这一步需要将头节点视作遍历条件,待节点销毁完毕之后再销毁头节点即可。

二、增删改查

1.尾插

尾插即在链表的最后一个节点之后再插入新节点,插入示意图如下:
在这里插入图片描述

// 尾插
void LNpush_back(LNnode* phead, Datetype x)
{assert(phead);// 首先申请节点LNnode* newnode = LNBuynode(x);// 改变各个节点中指针的指向,需要注意指向的顺序// 优先修改对链表没有影响的指针指向// 三个指针:phead,phead->prve(尾节点),newnode(尾插节点)newnode->next = phead;newnode->prve = phead->prve;phead->prve->next = newnode;phead->prve = newnode;
}

2.头插

头插是在链表的第一个有效节点之前插入数据,并非是在头节点之前插入数据,插入示意图如下:
在这里插入图片描述

// 头插
// 头插是插入到第一个有效节点之前,并非头节点之前
void LNpush_front(LNnode* phead, Datetype x)
{assert(phead);// 申请节点LNnode* newnode = LNBuynode(x);// 改变指针指向// 优先更改对链表整体结构没有影响的指针// 三个指针:phead,phead->next(第一个节点),newnodenewnode->prve = phead;newnode->next = phead->next;phead->next->prve = newnode;phead->next = newnode;
}

3.尾删

删除示意图如下:
在这里插入图片描述

// 尾删
void LNpop_back(LNnode* phead)
{// 首先对List判空// 链表非空才可以删除assert(!LNempty(phead));// 标记将要删除的尾节点LNnode* pdelete = phead->prve;// 改变指针指向// phead,pdelete(删除的尾节点),pdelete->prve(尾节点的前一个节点)pdelete->prve->next = phead;phead->prve = pdelete->prve;// 销毁节点free(pdelete);pdelete = NULL;
}

4.头删

删除示意图如下:
在这里插入图片描述

// 头删
void LNpop_front(LNnode* phead)
{// 首先对List判空// 链表非空才可以删除assert(!LNempty(phead));// 标记将要删除的头节点LNnode* pdelete = phead->next;// 改变指针指向// phead,pdelete(删除的第一个节点),pdelete->next(第二个节点)pdelete->next->prve = phead;phead->next = pdelete->next;// 销毁节点free(pdelete);pdelete = NULL;
}

5.pos位置之后 / 之前插入

本函数需要配合查找函数使用,pos位置之后插入(pos之前插入与之后插入高度相似)示意图如下:
在这里插入图片描述

pos位置之后差插入:

// 指定位置之后插入
void LNinsert_back(LNnode* pos, Datetype x)
{assert(pos);// 创建将要插入的新节点LNnode* newnode = LNBuynode(x);// 改变指针指向// pos,newnode,pos->next(pos的下一个节点)newnode->next = pos->next;newnode->prve = pos;pos->next->prve = newnode;pos->next = newnode;
}

pos位置之前插入:


// 指定位置之前插入
void LNinsert_front(LNnode* pos, Datetype x)
{assert(pos);// 创建将要插入的新节点LNnode* newnode = LNBuynode(x);// 改变指针指向// pos,newnode,pos->prve(pos的前一个节点)newnode->next = pos;newnode->prve = pos->prve;pos->prve->next = newnode;pos->prve = newnode;
}

6.删除pos位置节点

删除示意图如下:
在这里插入图片描述

// 删除指定位置的数据
void LNerase(LNnode* pos)
{assert(pos);// 改变指针指向// pos->next,pos->prvepos->next->prve = pos->prve;pos->prve->next = pos->next;
}

7.查找指定数据的节点

// 查找指定元素位置
LNnode* LNfind(LNnode* phead,Datetype x)
{assert(phead);// 标记第一个节点,方便后续遍历操作LNnode* pcur = phead->next;// 遍历// 遍历条件:不等于头节点继续遍历,一旦等于头节点则表示一轮遍历完毕while (pcur != phead){// 找到了if (pcur->data == x){// 找到返回其位置return pcur;}pcur = pcur->next;}// 没找到返回NULLreturn NULL;
}

三、其他

1.打印

// 打印
void lNPrint(LNnode* phead)
{// 首先打印数据只需要从第一个节点(不算头节点)开始,所以标记此节点为pcurLNnode* pcur = phead->next;// 其次打印结束条件等于头节点即可,因为链表本身是一个循环结构,需要规定中止条件while (pcur != phead){// 打印每个节点的数据printf("%d -> ", pcur->data);// 当前节点数据打印完之后使pcur指向下一个节点,继续打印,直到遇见pheadpcur = pcur->next;}printf("\n");
}

2.判空

双向链表为空,即只有一个头节点的情况下。

// 判空
// 若链表为空,则返回true,反之false
bool LNempty(LNnode* phead)
{// 双向链表为空即只剩下一个头节点assert(phead);return phead->next == phead;
}

四、总结

对比单链表,双向链表在实现方面要简单得多,只需要改变指针得指向即可,例如尾节点就直接能表示为phead->prve(这里phead代表头节点),并且也能完成单链表不能完成得操作,即从尾节点遍历到第一个节点的操作,并且大多数函数的时间复杂度都是为O(1)。

http://www.lqws.cn/news/558559.html

相关文章:

  • 问卷标记语言(QML):简化调查问卷设计与部署的XML解决方案
  • 【YOLOv13保姆级教程#03】自建数据集训练与验证(Train Val)全流程 | 手把手教你构建数据集、标签格式转换与yaml配置
  • Go开发工程师-Golang基础知识篇
  • Vue工程化实现约定式路由自动注册
  • 使用vue3构建一套网站
  • TCP 和 UDP 是什么?
  • 【Python基础】06 实战:视频压缩迷你脚本设计
  • 深入理解C#委托操作:添加、移除与调用全解析
  • 港澳地区,海外服务器ping通可能是地区运营商问题
  • MySQL为什么要使用b+树
  • 1 Studying《Computer Architecture A Quantitative Approach》1-4
  • 鸿蒙HarmonyOS 5小游戏实践:数字记忆挑战(附:源代码)
  • 信号处理学习——文献精读与code复现之TFN——嵌入时频变换的可解释神经网络(下)
  • 给定一个整型矩阵map,求最大的矩形区域为1的数量
  • Insar 相位展开真实的数据集的生成与下载(随机矩阵放大,zernike 仿真包裹相位)
  • Launcher3中的CellLayout 和ShortcutAndWidgetContainer 的联系和各自职责
  • 剑指offer50_0到n-1中缺失的数字
  • python -日期与天数的转换
  • autoas/as 工程的RTE静态消息总线实现与端口数据交换机制详解
  • 解决flash-attn安装报错的问题
  • 【C】陷波滤波器
  • 鸿蒙开发:资讯项目实战之底部导航封装
  • MySQL之MVCC实现原理深度解析
  • 类和对象(中)
  • springboot+Vue驾校管理系统
  • 开疆智能ModbusTCP转CClinkIE网关连接台达DVP-ES3 PLC配置案例
  • Java-正则表达式
  • 测量 Linux 中进程上下文切换需要的时间
  • cocos creator 3.8 - 精品源码 - 挪车超人(挪车消消乐)
  • 同步日志系统深度解析【链式调用】【宏定义】【固定缓冲区】【线程局部存储】【RAII】