双向带头循环链表
一、概念

何为双向:此链表每一个节点的指针域由两部分组成,一个指针指向下一个节点,另一个指针指向上一个节点,并且两头的节点也是如此,头节点的下一个节点是尾节点,尾节点的上一个节点是头节点;
何为带头:此处的头节点是一个 哨兵位,在链表定义时就要手动设置,此节点只是起到头的作用,并不是真正的节点;在双向带头循环链表为空时,链表中只有一个头节点,当链表中连头节点都不存在时,此链表不能被称作一个有效链表;
何为循环:头节点的指针域中能指向前一个节点的指针指向尾节点,尾节点中能指向下一个节点的指针指向头节点,使得这个链表循环;
二、节点结构
struct ListNode
{ListDatatype data;struct ListNode* next;struct ListNode* prev;
};
data存储节点的胡数据,next作用是指向下一个节点,prev的作用是指向前一个节点;
三、基本功能实现
1、头文件
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<assert.h>
typedef int ListDatatype;typedef struct ListNode
{ListDatatype data;struct ListNode* next;struct ListNode* prev;
}ListNode;//初始化链表
void LTInit(ListNode** pphead);//尾插
void LTPushBack(ListNode* phead, ListDatatype x);//头插
void LTPushFront(ListNode* phead, ListDatatype x);//头删
void LTPopFront(ListNode* phead);//尾删
void LTPopBack(ListNode* phead);//查找节点
ListNode* NodeFind(ListNode* phead,ListDatatype x);//删除指定位置的节点
void LTErase(ListNode* phead,ListNode* pos);//在指定位置之后插入节点
void LTInsert(ListNode* phead, ListNode* pos,ListDatatype x);//销毁双向带头循环链表
void LTDestroy(ListNode** phead);
2、函数的实现
初始化
//初始化
void LTInit(ListNode** pphead)
{assert(pphead);*pphead = (ListNode*)malloc(sizeof(ListNode));if (*pphead == NULL){perror("malloc failed");exit(1);}(*pphead)->data = -1;(*pphead)->next = (*pphead)->prev = *pphead;
}
初始化传二级指针,涉及到头节点的修改,默认-1为无效值,给定头节点的数据域是-1;让头节点的next和prev指针都指向自己,实现循环。
申请节点
//申请节点
ListNode* BuyNode(ListDatatype x)
{ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));if (newnode == NULL){perror("malloc failed");exit(1);}newnode->data = x;newnode->next = newnode;newnode->prev = newnode;
}
申请的节点数据域为你传进的值;同样地,新节点地next和prev指针都指向自己;
尾插
//尾插
void LTPushBack(ListNode* phead, ListDatatype x)
{assert(phead);ListNode* newnode = BuyNode(x);newnode->next = phead;newnode->prev = phead->prev;phead->prev->next = newnode;phead->prev = newnode;
}
尾插不涉及到头节点地改变,所以只需要传入一级指针;尾插进入一个数据,涉及到头节点的prev指针指向发生变化,和原连边尾节点的next指针指向发生变化,为了不造成节点找不到的情况,先修改newnode的指针指向,让newnode的next指向phead,prev指向原链表的尾节点,也就是phead->prev;此操作完成后,先让原链表的尾节点(phead->prev)的next指向newnode,使得newnode成为新的尾节点,再让头节点phead的prev指向作为新的尾节点的newnode,完成循环;
头插
//头插
void LTPushFront(ListNode* phead, ListDatatype x)
{assert(phead);ListNode* newnode = BuyNode(x);newnode->next = phead->next;newnode->prev = phead;phead->next->prev = newnode;phead->next = newnode;
}
头插是往头节点(哨兵位)的下一个指针插入节点,因为头节点不存放有效数据,在链表中的的作用只是当做一个桩子;所以先让newnode的next指针指向原链表头指针的下一个节点,也就是phead->next,再让newnode的prev指针指向phead; 再让原链表头节点之后的那个节点(phead->next)的prev指向newnode,最后再让头节点的next指针指向newnode
注意:最后两行代码不能颠倒顺序,若是颠倒,那么phead的next指针就先指向newnode,再进行( phead->next->prev = newnode;)操作时相当于newnode->prev=newnode,这样无法实现双向(尾插同样如此)
尾删
//尾删
void LTPopBack(ListNode* phead)
{assert(phead && phead->next!=phead);ListNode* del = phead->prev;phead->prev = del->prev;del->prev->next = phead;free(del);del = NULL;
}
尾删首先要保证链表中含有可以删除的有效节点,所以assert断言中(phead->next!=phead)操作必不可少,若是只含有头节点,那么相当于phead->next=phead这行代码,那么断言错误;当然头节点是必须存在的;
先把要删除的尾节点存在del中,先不删除del,先把要修改的指针指向修改好之后再删除要删除的尾节点,这样是为了避免找不到尾节点的前后节点;pehad->prev就是尾节点,把头节点的prev指针指向倒数第二个节点也就是(del->prev),再让倒数第二个节点的next指针指向头节点;最后释放尾节点del;
头删
//头删
void LTPopFront(ListNode* phead)
{assert(phead && phead->next!=phead);//头删时要存在有效节点ListNode* del = phead->next;phead->next = del->next;del->next->prev = phead;free(del);del = NULL;
}
头删删除的是头节点后面的那个指针,和尾删断言一样,链表中要存在有效节点,否则断言报错;先把要删除的节点存放起来,再先修改指针的指向,最后删除del;
查找结点
//查找节点
ListNode* NodeFind(ListNode* phead,ListDatatype x)
{assert(phead);ListNode* pcur = phead->next;while (pcur != phead){if (pcur->data == x){return pcur;}pcur = pcur->next;}return NULL;
}
查找节点的方法是把链表遍历一遍,看有没有数据域等于要查找的节点的数据域,若是有则返回这个节点,反之返回NULL;
在指定位置之后插入节点
//在指定位置之后插入节点
void LTInsert(ListNode* phead, ListNode* pos,ListDatatype x)
{assert(phead && pos);ListNode* newnode = BuyNode(x);newnode->next = pos->next;newnode->prev = pos;pos->next->prev = newnode;pos->next = newnode;
}
需要配合查找节点一起使用,将查找到的节点作为pos,在此后插入节点,同样地先改变newnode的先后指针指向,再去修改pos以及pos后一个几点的指针指向;
删除指定位置的节点
//删除指定位置的节点
void LTErase(ListNode* phead, ListNode* pos)
{assert(phead);assert(pos);pos->prev->next = pos->next;pos->next->prev = pos->prev;free(pos);pos = NULL;
}
pos必须存在,删除先改变删除的节点的前后节的指针的指向,再删除pos;
销毁链表
//销毁双向带头循环链表
void LTDestroy(ListNode** pphead)
{assert(pphead && *pphead);ListNode* pcur = (*pphead)->next;while (pcur != *pphead){ListNode* next = pcur->next;free(pcur);pcur = next;}free(*pphead);*pphead = NULL;
}
销毁链表涉及到头节点的改变。要传入二级指针;并且要销毁链表中每一个节点,遍历删除,最后free掉头节点并置为空。
相关文章:
双向带头循环链表
一、概念 何为双向:此链表每一个节点的指针域由两部分组成,一个指针指向下一个节点,另一个指针指向上一个节点,并且两头的节点也是如此,头节点的下一个节点是尾节点,尾节点的上一个节点是头节点;…...
探索TASKCTL和 DataStage 的ETL任务调度协同
在复杂多变的企业环境中,高效、准确的数据处理是支撑业务决策与运营的核心。本文将深入探讨任务调度平台TASKCTL与ETL工具DataStage的深度融合,通过详尽的代码示例、结合细节以及实际案例的具体描述,展示这两个工具如何携手打造企业数据处理生…...
Facebook软体机器人与机器人框架:创新社交互动的未来
随着人工智能技术的不断进步,Facebook正通过软体机器人和先进的机器人框架,重新定义社交互动的未来。这些创新不仅提升了用户体验,也为开发者提供了强大的工具来构建下一代社交应用。 一、Facebook软体机器人:智能化的社交伙伴 …...
掌握音视频转换的艺术:用FFmpeg解锁多媒体的无限可能
在数字时代,音视频内容无处不在,从在线课程、娱乐视频到专业会议,它们都是信息传播的关键载体。然而,随着多媒体格式的不断演进,我们常常会遇到格式不兼容的问题,这成为了享受或处理这些内容的一大障碍。幸…...
C基础day9
一、思维导图 二、课后练习 1> 使用递归实现 求 n 的 k 次方 #include<myhead.h>int Pow(int n,int k) {if(k 0 ) //递归出口{return 1;}else{return n*Pow(n,k-1); //递归主体} }int main(int argc, const char *argv[]) {int n0,k0;printf("请输入n和k:&…...
32. 小批量梯度下降法(Mini-batch Gradient Descent)
在深度学习模型的训练过程中,梯度下降法是最常用的优化算法之一。我们前面介绍了批量梯度下降法(Batch Gradient Descent)和随机梯度下降法(Stochastic Gradient Descent),两者各有优缺点。为了在计算速度和…...
MySQL第八次作业
一、备份与恢复作业: 创库,建表: CREATE DATABASE booksDB; use booksDB; CREATE TABLE books ( bk_id INT NOT NULL PRIMARY KEY, bk_title VARCHAR(50) NOT NULL, copyright YEAR NOT NULL ); CREATE TABLE authors …...
【合集】临时邮箱网站 临时邮箱API(持续更新)
众所周知,在注册一些账户时,比较常见的验证方式就是邮箱,但是在进行一些小众和不知名网站注册时,邮箱的泄露可能预示着不休止的邮件推送。尤其是当我们只是想临时使用邮箱这种情况,第二种,批量注册账号的情…...
职场新人感受
互联网职场感受 阶段介绍 24届6月底毕业生,之前从未实习过。 岗位是后端开发(JAVA),目前已经上班三周(前两周看文档和做了半个简单需求,第三周脱产新人培训)。 职场体验 职场和想象中的工作…...
Window 下Mamba 环境安装踩坑问题汇总及解决方法 (无需绕过selective_scan_cuda)
导航 Mamba 及 Vim 安装问题参看本人之前博客:Mamba 环境安装踩坑问题汇总及解决方法Linux 下Vmamba 安装教程参看本人之前博客:Vmamba 安装教程(无需更改base环境中的cuda版本)Windows 下 VMamba的安装参看本人之前博客…...
前端项目本地的node_modules直接上传到服务器上无法直接使用(node-sasa模块报错)
跑 jekins任务的服务器不能连接外网下载依赖包,就将本地下载的 node_modules直接上传到服务器上,但是运行时node-sass模块报错了ERROR in Missing binding /root/component/node_modules/node-sass/vendor/linux-x64-48/binding.node >> 报错信息类…...
Hadoop3:动态扩容之新增一台机器的初始化工作
一、需求描述 给Hadoop集群动态扩容一个节点 那么,这个节点是全新的,我们需要做哪些准备工作,才能将它融入集群了? 二、初始化配置 1、修改IP和hostname vim /etc/sysconfig/network-scripts/ifcfg-ens33 vim /etc/hostname2、…...
【正点原子i.MX93开发板试用连载体验】录音小程序采集语料
本文最早发表于电子发烧友论坛:【新提醒】【正点原子i.MX93开发板试用连载体验】基于深度学习的语音本地控制 - 正点原子学习小组 - 电子技术论坛 - 广受欢迎的专业电子论坛! (elecfans.com) 接下来就是要尝试训练中文提示词。首先要进行语料采集,这是一…...
【EasyExcel】动态替换表头内容并应用样式
1.定义实体类 import com.alibaba.excel.annotation.ExcelProperty; import com.alibaba.excel.annotation.ContentStyle; import com.alibaba.excel.metadata.BorderStyleEnum; import com.alibaba.excel.metadata.VerticalAlignmentEnum; import com.alibaba.excel.metadata.…...
RocketMQ实现分布式事务
RocketMQ的分布式事务消息功能,在普通消息基础上,支持二阶段的提交。将二阶段提交和本地事务绑定,实现全局提交结果的一致性。 1、生产者将消息发送至RocketMQ服务端。 2、RocketMQ服务端将消息持久化成功之后,向生产者返回Ack确…...
【Rust练习】2.数值类型
练习题来自https://practice-zh.course.rs/basic-types/numbers.html 1 // 移除某个部分让代码工作 fn main() {let x: i32 5;let mut y: u32 5;y x;let z 10; // 这里 z 的类型是? }y的类型不对,另外,数字的默认类型是i32 fn main() {let x: i…...
通过 PPPOE 将 linux 服务器作为本地局域网 IPv4 外网网关
将 linux 服务器作为本地外网网关,方便利用 Linux 生态中的各种网络工具,对流量进行自定义、精细化管理… 环境说明 拨号主机:CentOS 7.9, Linux Kernel 5.4.257 拨号软件: rp-pppoe-3.11-7.el7.x86_64初始化 1、升级系统到新的稳定内核&a…...
gin源码分析
一、高性能 使用sync.pool解决频繁创建的context对象,在百万并发的场景下能大大提供访问性能和减少GC // ServeHTTP conforms to the http.Handler interface. // 每次的http请求都会从sync.pool中获取context,用完之后归还到pool中 func (engine *Engin…...
数学建模入门
目录 文章目录 前言 一、数学建模是什么? 1、官方概念: 2、具体过程 3、适合哪一类人参加? 4、需要有哪些学科基础呢? 二、怎样准备数学建模(必备‘硬件’) 1.组队 2.资料搜索 3.常用算法总结 4.论文撰写的…...
【学习笔记】无人机(UAV)在3GPP系统中的增强支持(十二)-无人机群在物流中的应用
引言 本文是3GPP TR 22.829 V17.1.0技术报告,专注于无人机(UAV)在3GPP系统中的增强支持。文章提出了多个无人机应用场景,分析了相应的能力要求,并建议了新的服务级别要求和关键性能指标(KPIs)。…...
反向工程与模型迁移:打造未来商品详情API的可持续创新体系
在电商行业蓬勃发展的当下,商品详情API作为连接电商平台与开发者、商家及用户的关键纽带,其重要性日益凸显。传统商品详情API主要聚焦于商品基本信息(如名称、价格、库存等)的获取与展示,已难以满足市场对个性化、智能…...
Vue3 + Element Plus + TypeScript中el-transfer穿梭框组件使用详解及示例
使用详解 Element Plus 的 el-transfer 组件是一个强大的穿梭框组件,常用于在两个集合之间进行数据转移,如权限分配、数据选择等场景。下面我将详细介绍其用法并提供一个完整示例。 核心特性与用法 基本属性 v-model:绑定右侧列表的值&…...
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南
精益数据分析(97/126):邮件营销与用户参与度的关键指标优化指南 在数字化营销时代,邮件列表效度、用户参与度和网站性能等指标往往决定着创业公司的增长成败。今天,我们将深入解析邮件打开率、网站可用性、页面参与时…...
基于Java Swing的电子通讯录设计与实现:附系统托盘功能代码详解
JAVASQL电子通讯录带系统托盘 一、系统概述 本电子通讯录系统采用Java Swing开发桌面应用,结合SQLite数据库实现联系人管理功能,并集成系统托盘功能提升用户体验。系统支持联系人的增删改查、分组管理、搜索过滤等功能,同时可以最小化到系统…...
人工智能(大型语言模型 LLMs)对不同学科的影响以及由此产生的新学习方式
今天是关于AI如何在教学中增强学生的学习体验,我把重要信息标红了。人文学科的价值被低估了 ⬇️ 转型与必要性 人工智能正在深刻地改变教育,这并非炒作,而是已经发生的巨大变革。教育机构和教育者不能忽视它,试图简单地禁止学生使…...
MySQL 索引底层结构揭秘:B-Tree 与 B+Tree 的区别与应用
文章目录 一、背景知识:什么是 B-Tree 和 BTree? B-Tree(平衡多路查找树) BTree(B-Tree 的变种) 二、结构对比:一张图看懂 三、为什么 MySQL InnoDB 选择 BTree? 1. 范围查询更快 2…...
日常一水C
多态 言简意赅:就是一个对象面对同一事件时做出的不同反应 而之前的继承中说过,当子类和父类的函数名相同时,会隐藏父类的同名函数转而调用子类的同名函数,如果要调用父类的同名函数,那么就需要对父类进行引用&#…...
篇章二 论坛系统——系统设计
目录 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 1. 数据库设计 1.1 数据库名: forum db 1.2 表的设计 1.3 编写SQL 2.系统设计 2.1 技术选型 2.2 设计数据库结构 2.2.1 数据库实体 通过需求分析获得概念类并结合业务实现过程中的技术需要&#x…...
webpack面试题
面试题:webpack介绍和简单使用 一、webpack(模块化打包工具)1. webpack是把项目当作一个整体,通过给定的一个主文件,webpack将从这个主文件开始找到你项目当中的所有依赖文件,使用loaders来处理它们&#x…...
CSS 工具对比:UnoCSS vs Tailwind CSS,谁是你的菜?
在现代前端开发中,Utility-First (功能优先) CSS 框架已经成为主流。其中,Tailwind CSS 无疑是市场的领导者和标杆。然而,一个名为 UnoCSS 的新星正以其惊人的性能和极致的灵活性迅速崛起。 这篇文章将深入探讨这两款工具的核心理念、技术差…...
