【DS】C语言实现带头双向循环链表(详细图解)

奔图M6700-M6800-M7100-M7200 系列打印机拆解教程? 阅读详情


上篇博客已经对链表的种类进行了简略介绍。详细讲解了如何实现 单链表,感兴趣的可以去看看。现在进行 带头双向循环链表(习惯称为 双向链表)的实现

双向链表的的实现

  • 与单链表一样,双向链表也是对增删查改这些功能进行实现
    双向链表目录

双向链表节点结构定义

  • 带头双向循环链表:带头,双向,循环就是这个链表的特点,他与单链表就是两个极端,一个十分简陋,一个应有尽有。但双向链表的实现可要比单链表简单许多,这都多亏了他的结构特点
  • 个人建议写双向链表时一定要画图,这样会清晰很多
    双向链表
typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;//尾
	LTDataType data;
	struct ListNode* next;//头
}LTNode;
  • 该结构包括了一个前驱指针指向前一个节点
  • 数据域
  • 指向下一个节点的next指针

打印数据

  • 为了方便观察,还是先实现一个打印函数。
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur!= phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
}

双向链表

  • 由于结构不同,不能再像单链表这样来遍历链表了。观察上图可以看到head是链表的头,完成一次循环后会回到head,所以pcur!= phead,便是控制循环的条件

初始化

  • 带头即malloc出一个节点,这个节点作为头节点(也称为哨兵位),数据域不存储有效数据(这里默认设置为-1但,是无效数据)。
  • 这时只有一个头节点,把他们都指向头节点,再返回即完成初始化。
    头节点操作
LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc");
		return 1;
	}
	phead->data = -1;
	phead->next = phead->prev = phead;
	return phead;
}

插入数据

  • 这里我们也进行头插和尾插的操作。
  • 频繁插入数据就需要频繁申请节点,所以依旧封装一个ListBuyNode函数来申请节点并把数据放入节点,最后返回。
LTNode* ListBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc");
		return 1;
	}
	node->data = x;
	node->next = node->prev = NULL;
	return node;
}
  • 还需要注意到是prev指针和next指针指向的是哪里,弄清楚这个就很好理解插入操作了
    指针介绍

头插

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	node->next = phead->next;
	node->prev = phead;

	phead->next->prev = node;
	phead->next = node;
}
  • 使用assert断言来确保传过来的是有效的地址
  • 插入数据时我们一般先对新节点进行连接,再去处理前一个和后一个节点
  • 实在不理解时就去画图!!!画图后会发现清晰明了
    头插操作

尾插

void LTPushback(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	node->prev = phead->prev;
	node->next = phead;

	phead->prev->next = node;
	phead->prev = node;
}
  • 看图操作清晰明了,操作顺序依旧先对新节点进行操作,再处理其他节点
    尾插

删除数据

头删

void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;
	
	del->next->prev = phead;
	phead->next = del->next;
	
	free(del);
	del = NULL;
}
  • 删除数据必须确保有数据,所以用assert断言一下phead->next != phead
  • 头删比较简单,只需处理要删除节点del的下一个节点的prev指针和头节点的next指针即可
    头删

尾删

void LTPopBack(LTNode* phead)
{
	assert(phead&&phead->next!=phead);
	LTNode* del = phead->prev;

	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;

}
  • 尾删需要处理头节点的prev指针和删除节点前一个节点的next指针
    尾删

指定位置插入数据

  • 要找到指定的位置,通过LTFind函数实现。
  • 遍历链表,找到指定的数据则返回其地址,否则返回空
LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}
  • 这里我们只实现了指定位置后插入数据,你也可以自己实现指定位置之前。
void LTInser(LTNode* phead, LTNode* pos, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	
	node->prev = pos;
	node->next = pos->next;

	pos->next->prev = node;
	pos->next = node;
}
  • 依旧是先对node节点操作,再对pos节点的next指针和pos节点的下一个节点的prev操作
    指定位置之后

指定位置删除数据

void LTErase(LTNode* phead, LTNode* pos)
{
	assert(phead && phead->next != phead);
	LTNode* del = pos;

	del->prev->next = del->next;
	del->next->prev = del->prev;

	free(del);
	del = NULL;
}
  • 删除指定位置数据也简单,只对del节点的上一个节点的next指针和del节点的下一个节点的prev指针操作即可
    删除指定位置数据

销毁链表

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);//需要手动置空哨兵位
	phead = NULL;
}
  • 由于链表节点是在堆区开辟的,为防止内存泄漏,需要及时释放
  • 用next指针保存下一个节点,再释放当前节点,不断遍历直到释放完
  • 最后手动置空一下头节点

总结

带头双向循环链表因为结构的不同,比单链表更为容易实现,而且在尾插操作是可以直接找到尾节点,不需要遍历链表来找尾,在时间复杂度上更胜一筹。而单链表在OJ题上用得很多,也十分重要。
自此数据结构中最常用的单向不带头不循环(单链表)和带头双向循环链表(双向链表)的实现就完成了,会了这两种最常用的链表,剩余六种也是湿湿碎啦。希望这两篇博客对大家有所帮助。

完整代码

  • 分为三个文件,也可以自己合成一个文件。
    List.h
#pragma once
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>

typedef int LTDataType;

typedef struct ListNode
{
	struct ListNode* prev;//尾
	LTDataType data;
	struct ListNode* next;//头
}LTNode;

LTNode* LTInit();//初始化链表
void LTPrint(LTNode* phead);//打印链表
void LTPushback(LTNode* phead, LTDataType x);//尾插
void LTPushFront(LTNode* phead, LTDataType x);//头插
void LTPopBack(LTNode* phead);//尾删
void LTPopFront(LTNode* phead);//头删
LTNode* LTFind(LTNode* phead, LTDataType x);//查找
void LTInser(LTNode* phead, LTNode* pos, LTDataType x);//指定位置之后插入
void LTErase(LTNode* phead, LTNode* pos);//删除指定位置
void LTDestory(LTNode* phead);//销毁链表

List.c

#define _CRT_SECURE_NO_WARNINGS 1 
#include"List.h"

LTNode* LTInit()
{
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (phead == NULL)
	{
		perror("malloc");
		return 1;
	}
	phead->data = -1;
	phead->next = phead->prev = phead;
	return phead;
}

void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur!= phead)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
}

LTNode* ListBuyNode(LTDataType x)
{
	LTNode* node = (LTNode*)malloc(sizeof(LTNode));
	if (node == NULL)
	{
		perror("malloc");
		return 1;
	}
	node->data = x;
	node->next = node->prev = NULL;
	return node;
}

void LTPushback(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	node->prev = phead->prev;
	node->next = phead;

	phead->prev->next = node;
	phead->prev = node;
}

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	node->next = phead->next;
	node->prev = phead;

	phead->next->prev = node;
	phead->next = node;
}

void LTPopBack(LTNode* phead)
{
	assert(phead&&phead->next!=phead);
	LTNode* del = phead->prev;

	del->prev->next = phead;
	phead->prev = del->prev;

	free(del);
	del = NULL;

}

void LTPopFront(LTNode* phead)
{
	assert(phead && phead->next != phead);
	LTNode* del = phead->next;

	del->next->prev = phead;
	phead->next = del->next;

	free(del);
	del = NULL;
}

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

void LTInser(LTNode* phead, LTNode* pos, LTDataType x)
{
	assert(phead);
	LTNode* node = ListBuyNode(x);
	node->prev = pos;
	node->next = pos->next;

	pos->next->prev = node;
	pos->next = node;
}

void LTErase(LTNode* phead, LTNode* pos)
{
	assert(phead && phead->next != phead);
	LTNode* del = pos;

	del->prev->next = del->next;
	del->next->prev = del->prev;

	free(del);
	del = NULL;
}

void LTDestory(LTNode* phead)
{
	assert(phead);
	LTNode* pcur = phead->next;
	while (pcur != phead)
	{
		LTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	free(phead);//需要手动置空哨兵位
	phead = NULL;
}

test.c

#define _CRT_SECURE_NO_WARNINGS 1 
#include"List.h"

void LTtest()
{
	//LTNode*phead= LTInit();

	//LTPushback(phead, 0);
	//LTPushback(phead, 1);
	//LTPushback(phead, 2);
	//LTPushFront(phead, 0);
	//LTPopBack(phead);
	//LTPopFront(phead);
	//LTNode* find = LTFind(phead, 0);
	//LTInser(phead, find, 5);

	//LTPrint(phead);
	//
	//LTDestory(phead);
	
}


int main()
{
	LTtest();

	return 0;
}
点云数据增强及预处理 数据增强: 1. random world flip:沿着x或者y轴,翻转点云 点云的x或者y,要翻转 3d box中心的x或者y,要翻转 3d box的朝向要翻转 point segmentation label,点云的相对顺序没有发生变化 2. random_world_rotation:沿着点云原点,旋转点云 点,x,y,绕z轴旋转 3d box,绕z轴旋转,中心和朝向都要变化 point segmentation label,点云的相对顺序没有发生变化 3. random_world_scaling 阅读详情

相关推荐

华为eNSP Pro实战:NE/CE设备SSH远程配置避坑指南

本文针对华为eNSP Pro用户在模拟多台NE路由器或CE交换机时遇到的浏览器卡顿问题,提供了详细的SSH远程配置避坑指南。通过对比浏览器与SSH客户端的资源占用和操作体验,文章详细阐述了为NE和CE设备配置SSH服务的关键步骤、命令差异及常见错误排查方法,旨在帮助用户提升实验效率,实现稳定高效的远程管理。

weixin_29196891的博客 223

数据结构——双向循环链表

双向循环链表的构建以及基本操作

helloworld1932的博客 435

Windows 11 RDP Wrapper 配置教程与常见问题解决方法

解锁家庭版RDP主机功能(Windows 11家庭版原生不支持作为RDP服务器)突破单用户连接限制,实现多用户同时远程登录(类似服务器版功能)它不修改系统核心文件,通过注入方式工作,相对安全可靠。RDP Wrapper是Windows 11家庭版用户启用远程桌面、突破单用户限制的高效工具。遵循本文步骤可快速完成配置,遇到问题时按常见问题排查方案逐一解决,多数问题可通过更新配置文件或重启服务解决。若长期使用,建议关注官方GitHub仓库的更新,确保兼容性和安全性。

weixin_41521681的博客 1365

C语言通用双向循环链表操作函数集

  说明      相比Linux内核链表宿主结构可有多个链表结构的优点,本函数集侧重封装性和易用性,而灵活性和效率有所降低。     可基于该函数集方便地构造栈或队列集。     本函数集暂未考虑并发保护。   一  概念      链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序通过链表中的指针链接次序实现。链表由一系列存储结点组成,结点可在运行时动态生成。每个结...

weixin_33884611的博客 560

OpenCV实战:如何用Debevec算法搞定相机响应函数(附完整代码)

本文详细介绍了如何使用OpenCV实现Debevec算法计算相机响应函数(CRF),并应用于HDR图像合成。通过完整的代码示例和工程实践问题解决方案,帮助开发者掌握从原理到实现的关键步骤,提升高动态范围成像和计算机视觉任务的处理能力。

weixin_29061041的博客 229

UE5 C++教程(一、基本结构)

虚幻API由三块组成,分别是Runtime,Developer utilitiew,functionality for the Editor,大多数游戏性编程只使用运行时模块,最常见的三个是Corn、CornUObject和Engine。

skycol的博客 1万+

[数据结构]——模拟实现双向循环链表

此次工程还是使用了3个源文件list.h(头文件源代码),main.c(实现接口的具体代码),list.c(单链表逻辑) list.h #pragma once #include&lt;stdio.h&gt; #include&lt;stdlib.h&gt; #include&lt;assert.h&gt; typedef int LTDataType; typedef struct Li...

lucky52529的博客 556

从零搭建私有AI工作站:Ollama+DeepSeek+Dify本地化部署全攻略

本文提供了一份详尽的本地化AI工作站搭建指南,核心在于实现数据安全与离线可用。通过整合Ollama、DeepSeek与Dify三大工具,手把手教你完成从环境配置、模型部署到可视化应用构建的全过程,重点解决了Dify连接Ollama等关键步骤,助你打造一个完全自主可控的私有AI开发环境。

weixin_28396629的博客 311

C语言实现双向循环链表

在之前的文章中,我写过一篇关于C语言实现双向链表博文,介绍了双向链表的实现过程以及双向链表的优势,接下来我首先给大家介绍一下循环链表和双向链表的区别,之后再给大家介绍双向循环链表的具体实现。 循环链表和双向链表的区别 1、最后一个结点指针指向不同 在建立一个循环链表时,必须使其最后一个结点的指针指向表头结点,而不是像双向链表那样置为NULL。此种情况还用于在最后一个结点后插入一个新的结点。 2、判...

BeiXi's Blog 2565

cursor AI代码编辑器IDE对接deepseek模型api;字节ai编辑器trae

参考: https://www.cursor.com/ https://platform.deepseek.com/https://www.cursor.com/下载安装后,注册账号登录即可 再高级里选择设置 再模型这注册模型: 模型名称一定要填正确,url地址填好,最后填上秘钥验证

weixin_42357472的博客 1万+

解决Google Play签名问题:Android App Bundle上传指南

摘要:本文分享了解决Android App Bundle上传至Google Play时遇到的签名问题的经验。当使用pepk.jar工具加密私钥时,出现"NoSuchAlgorithmException"错误。解决方案包括:确保使用JDK 11+版本、更新pepk.jar工具、验证密钥库信息,并提供详细命令行示例。通过升级开发环境和正确配置签名参数,最终成功生成加密密钥文件。文章为开发者提供了实用的技术参考,强调耐心排查问题的重要性。(149字)

m0_62153576的博客 145

C语言_双向循环链表的基本操作

目录: 1、初始化 2、头部插入 3、头部删除 4、尾部插入 5、尾部删除 6、打印链表 7、任意位置插入 8、查找值为data的节点 9、指定位置删除 10、销毁链表 1、初始化: 创建一个节点,给节点赋值为0;因为是循环链表,所以让它的_pNext指针域和_pPre 指针域都指向自己 void DCListInit ...

A__B__C__的博客 2007

实战17:GCN+LSTM图卷积神经网络预警预测 完整代码数据集

实战17:GCN+LSTM图卷积神经网络预警预测 完整代码数据集

1945

【DS】带头双向循环链表的实现@线性表 —— 增删查改

目录0. 引1. 双向循环链表实现1.1 创建、销毁、申请新节点、打印1.1.1 创建1.1.2 销毁1.1.3 申请新节点1.1.4 打印1.2 尾插、尾删1.2.1 尾插1.2.2 尾删1.3 头插、头删1.3.1头插1.3.2 头删1.4 查找、任意位置插入、任意位置删除1.4.1 查找1.4.2 任意位置插入1.4.3 任意位置删除1.4.4 复用2. 顺序表和链表的比较附:`List.h``List.c``test.c` ????本文出现如此多草莓的原因是,我昨天晚上吃了好多草莓,并且现在还想吃。

浮光掠影 1837

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

计算机控制技术及应用

《计算机控制技术及应用》通过总结作者的科研成果和吸收国内外的先进理论、方法和技术,以计算机控制系统的网络化、开放化、智能化和集成化发展趋势为主线索,主要介绍计算机控制系统的基本概念和计算机控制系统的典型形式,计算机控制系统的数学描述与基本分析方法,以及数字PID控制器、最少拍控制系统、达林算法、状态变量设计法等设计方法及其各种改进算法。重点突出计算机控制系统输入输出通道接口的设计方法和典型控制系统的设计案例与实现方法等。

【DS】带头双向循环链表之C语言实现

带头双向循环链表的实现 DList.h文件 #pragma once #include <stdio.h> #include <assert.h> #include <malloc.h> typedef int DLTDataType; typedef struct DListNode // 循环链表节点 { DLTDataType _data; ...

进阶之路 300

OV9734 DATASHEET,完整规格书

OV9734完整规格书,包括电气参数,尺寸规格,寄存器配置表等

DS----双链表(双向带头循环)详解和实现

这里写自定义目录标题欢迎使用Markdown编辑器新的改变功能快捷键合理的创建标题,有助于目录的生成如何改变文本的样式插入链接与图片如何插入一段漂亮的代码片生成一个适合你的列表创建一个表格设定内容居中、居左、居右SmartyPants创建一个自定义列表如何创建一个注脚注释也是必不可少的KaTeX数学公式新的甘特图功能,丰富你的文章UML 图表FLowchart流程图导出与导入导出导入 欢迎使用Markdown编辑器 你好! 这是你第一次使用 Markdown编辑器 所展示的欢迎页。如果你想学习如何使用Mar

weixin_45302844的博客 257

【DS】C语言实现单链表(创建过程详细讲解)

用C语言实现链表结构,初步接触数据结构,使用C语言对链表这一数据结构进行模拟实现,增强对链表的理解。

Mesar的博客 1143

实验三 双向链表 循环链表

1

fangyuxuan123的博客 356

DS_2019_4_24(无头单项非循环链表的增,删,改,查)

如果有来生,原做一只鸟,飞越永恒,没有迷途的苦恼. 东方有火红的希望,南方有温暖的巢床,向西逐退残阳,向北唤醒芬芳. 如果有来生,希望每次相遇,都能化为永恒.–三毛 1. SList.h #pragma once typedef int SDataType; // 链表的节点 typedef struct SListNode{ SDataType _data; struct SList...

Mind and Hand 213

DS_4_29(带头节点双向循环链表增,删,改,查的实现)

这里是引用 1. DList.h #pragma once typedef int DLDataType; //双向链表结构体名称 typedef struct DListNode{ struct DListNode* _pNext; //指向当前节点的下一个 struct DListNode* _pP...

Mind and Hand 199

C : DS双向链表—前驱后继

在双向链表中,A有一个指针指向了后继节点B,同时,B又有一个指向前驱节点A的指针。这样不仅能从链表头节点的位置遍历整个链表所有节点,也能从链表尾节点开始遍历所有节点。对给定的每个关键字,输出此关键字前驱节点关键字和后继节点关键字。如果给定的关键字没有前驱或者后继,则不输出。对于给定的一列数据,按照给定的顺序建立双向链表,按照关键字找到相应节点,输出此节点的前驱节点关键字及后继节点关键字。第一行两个正整数n(代表节点个数),m(代表要找的关键字的个数)。接下来有m个要查找的关键字,每个占一行。

m0_73605639的博客 628

数据结构和算法(三)线性表:静态链表、循环链表、双向链表

线性表 1.1静态链表 1.2循环链表 1.3双向链表 double linkedlist 1.1静态链表 静态链表就是使用静态的数组来描述单链表。意思就是:用游标代替指针。 #include <stdio.h> //定义静态链表 (即 游标实现法) #define ElemType int #define MAXSIZE 1000 t...

qq_41543888的博客 254

带头双向循环链表

7.尾插节点(实际上就是复用了插入节点)

qq_65112813的博客 179

【DS】单链表@线性表 —— 增删查改

模拟实现list要熟悉耶!这个不简单呢。。当然了不是因为单链表,没关系,主要还是要熟悉。。。

浮光掠影 2134

【DS基础】静态链表

目的:为没有指针的程序语言所设计的实现单链表的方法静态链表(游标实现法)数组的元素被分为两个数据域data和cur

SuperiorEE的博客 534

DS--链表的概念及单链表接口的实现及其应用

链表 概念:链表是一种物理存储结构上非连续,非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接此项实现的。 链表的结构类型可分为: 1,单链表,双向链表 2,不带头单链表,带头链表 3,单链表,循环单链表 链表的实现 一,单链表接口的实现 声明文件SList.h SList.h #ifndef _SLIST_H_ #define _SLIST_H_ #include<Window...

烟华的博客 463

基于Linux+6818开发板实现普通电子相册翻页功能

更多资料请点击:我的目录 本篇仅用于记录自己所学知识及应用,代码仍可优化,仅供参考,如果发现有错误的地方,尽管留言于我,谢谢。 首先是外部进程传参,传进的是某目录文件的路径(绝对路径/相对路径)。接着打开目录文件,遍历目录内所有的文件,将后缀名为“.bmp”的普通文件全部找出来,并拼接保存它们的路径到双向循环链表里,一个节点存放一个bmp图片的路径。再通过触摸屏返回的坐标值进行逻辑判断,例如返回坐标值 < 400,触摸了左边的显示屏,我们应该让双向循环链表的当前节点 p 指向 p->prev节点

佳佳鸽的博客 1万+
上一篇: 【DS】C语言实现单链表(创建过程详细讲解)
下一篇: 【DS】C语言实现栈(动画演示)
Mesar_
博客等级 码龄3年 926粉丝 56原创
评论 2
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值