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

Linux 系统管理员必备:Chrome 企业版离线部署全攻略(含 RPM/DEB 双版本) 本文详细介绍了Linux系统管理员如何离线部署Chrome企业版,涵盖RPM和DEB双版本。从获取官方离线安装包到静默安装、权限配置及企业级策略管理,提供全流程解决方案,特别适合Ubuntu等DEB系系统管理员参考。 阅读详情


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

双向链表的的实现

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

双向链表节点结构定义

  • 带头双向循环链表:带头,双向,循环就是这个链表的特点,他与单链表就是两个极端,一个十分简陋,一个应有尽有。但双向链表的实现可要比单链表简单许多,这都多亏了他的结构特点
  • 个人建议写双向链表时一定要画图,这样会清晰很多
    双向链表
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;
}
华为OD机试 新系统真题 【最小代价完成论文评审】 华为OD机试 新系统真题【最小代价完成论文评审】,提供C语言、Java、C++、Python、Go、JavaScript源码实现,并提供完整思路讲解,以及在线OJ模拟做题。华为OD机试真题【最小代价完成论文评审】多语言题解。 阅读详情

相关推荐

保姆级教程:手把手带你复现APFNet论文的RGBT目标跟踪模型(附完整代码与数据集配置)

本文提供了一份详细的APFNet论文复现指南,涵盖从环境搭建到模型测试的全流程。通过解析多模态融合模块和渐进式训练流程,帮助读者高效完成RGBT目标跟踪模型的复现工作,特别针对训练和测试中的常见问题提供实用解决方案。

weixin_29056781的博客 342

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

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

helloworld1932的博客 435

金蝶云星空初级实施认证考题

Q4.[单选题] (1.5分) 总账中的多账簿账表主要是基于什么进行查看的Q47.[多选题] (2.5分) 销售订单的收款计划页签勾选预收之后,预收控制环节可以控制哪些节点?

m0_51994557的博客 3121

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

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

weixin_33884611的博客 560

信息隐藏 数字图像空域隐写与分析技术的实现

【代码】信息隐藏 数字图像空域隐写与分析技术的实现。

Yungoal的博客 469

SAP-PP 返工订单成本归集优化:从物料结算到成本中心的配置与增强实践

本文深入探讨SAP-PP模块中返工订单成本归集的优化方案,重点解析从物料结算到成本中心的配置与增强实践。通过结算参数文件配置、分配结构调整及增强开发实战,解决返工费用误计入物料成本的问题,确保财务核算精准性。文章还提供测试验证方案与常见报错处理技巧,助力企业实现成本数据清晰流向分析。

weixin_29260291的博客 365

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

此次工程还是使用了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

Python爬虫实战:新浪财经股票金融数据全方位抓取教程

本教程系统介绍了新浪财经股票数据爬取技术,从静态网页解析、实时接口请求到异步爬取和数据存储,帮助你搭建了一个完整的金融数据采集流程。未来,可以结合深度学习模型进行行情预测,或者将数据接入量化交易系统,实现自动化投资。

2201_76125261的博客 2210

C语言实现双向循环链表

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

BeiXi's Blog 2565

一文吃透 OData Deep Insert:从框架原理到 ABAP 实战

本文面向ABAP工程师,解析OData服务中的Deep Insert技术。文章首先阐述Deep Insert的业务背景,说明其适用于父子层级数据必须成组创建的场景(如销售订单与行项目)。随后深入框架实现机制,介绍SAP Gateway如何通过CREATE_DEEP_ENTITY方法处理嵌套请求,并强调开发者的责任边界。通过销售订单案例,文章对比了Deep Insert与$batch的适用场景差异,提供请求负载示例和DPC_EXT实现骨架代码。最后给出MPC_EXT声明深结构的三种套路,以及事务处理和错误处理

2007 年 ~ 2025 年,深耕 SAP 技术 18 年 225

跨平台商品模型大解析:亚马逊 vs 阿里 vs 拼多多

本文深入探讨了亚马逊、阿里巴巴和拼多多三大电商平台的商品模型设计,分析了它们在变体关系、商品抽象层级和SKU定义等方面的异同。亚马逊模型以物流和变体为中心,强调全球仓配体系下的标准化管理;阿里模型采用多视角的弹性结构,适用于多类目、多品牌、多卖家环境;拼多多模型则结合平台标准化与商家个性化,适合短期促销和快速上新场景。文章还提供了实践建议,帮助自研电商平台或商品中台的开发者选择合适的商品结构,并强调了商品模型在电商系统中的核心作用。通过对比分析,本文旨在为读者提供商品建模的启发和参考。

曾经“等你生日那天”都遥远得像未来,如今却可欢愉的挥手说“下个十年见” 13万+

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

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

A__B__C__的博客 2007

IEM模型,地表粗糙度模拟

利用微波反演土壤水分时候,利用IEM模型可以实现对随机地表的模拟

【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

精雕软件(JDpaint) 5.5 绿色版

精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版精雕软件(JDpaint) 5.5 绿色版

jdk-8u144-linux-x64.tar.gz.

该资源为搭建Linux系统Java环境用,资源的后缀为tar.gz格式的压缩包

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

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

进阶之路 300

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

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、付费专栏及课程。

余额充值