带头双向循环链表的基本操作(c语言实现)

news2024/11/18 11:45:20

 带头双向循环链表

带头双向循环链表是一种结合了双向链表和循环链表特性的数据结构。其主要特点如下:

  1. 双向性:链表中的每个节点都有两个指针,一个指向下一个节点(next),另一个指向前一个节点(prev)。这种双向性使得链表中的任何节点都可以方便地访问其前驱节点和后继节点,从而支持向前和向后的遍历操作。

  2. 循环性:在带头双向循环链表中,最后一个节点的next指针指向链表的第一个节点,而第一个节点的prev指针指向最后一个节点,形成一个闭环。这种循环结构使得链表没有明确的开始和结束,可以从任何节点开始遍历整个链表。

  3. 简化操作:由于链表的循环性,无论是从头部还是尾部开始遍历,都可以无缝地连接到链表的另一端。这简化了在链表两端进行插入和删除节点的操作。例如,在链表头部插入一个新节点时,只需将新节点的prev指针指向最后一个节点,next指针指向原链表的第一个节点,并更新最后一个节点和原第一个节点的指针即可。

  4. 内存使用:带头双向循环链表相对于普通链表需要额外的空间来存储指针,但这也带来了操作的便利性和灵活性。在实际应用中,根据具体需求权衡空间复杂度和时间复杂度是很重要的。

  5. 适用场景:带头双向循环链表适用于需要频繁在链表头部和尾部进行插入和删除操作的场景,如实现循环队列、循环双向链表等数据结构。其循环性和双向性使得这些操作变得简单和高效。

需要注意的是,虽然带头双向循环链表具有许多优点,但它并不适用于所有场景。在选择数据结构时,需要根据具体的应用需求和性能要求进行权衡。

定义结点 

// 定义一个新的数据类型LTDataType,它实际上就是int类型的一个别名。  
typedef int LTDataType;  
  
// 定义一个名为ListNode的结构体,用于表示双向链表的一个节点。  
typedef struct ListNode  
{  
    // 指向下一个节点的指针  
    struct ListNode* next;  
      
    // 指向前一个节点的指针  
    struct ListNode* prev;  
      
    // 存储数据的成员,其类型为之前定义的LTDataType,即int类型  
    LTDataType data;  
  
} LTNode;

 链表的基本操作

LTNode* BuyListNode(LTDataType x);

//void LTInit(LTNode** pphead);
LTNode* LTInit();
void LTDestroy(LTNode* phead);
void LTPrint(LTNode* phead);
bool LTEmpty(LTNode* phead);

void LTPushBack(LTNode* phead, LTDataType x);
void LTPopBack(LTNode* phead);

void LTPushFront(LTNode* phead, LTDataType x);
void LTPopFront(LTNode* phead);

// posλ֮ǰһֵ
void LTInsert(LTNode* pos, LTDataType x);
void LTErase(LTNode* pos);

创建新结点

// 定义一个函数BuyListNode,它接受一个LTDataType类型的参数x,返回一个LTNode类型的指针。  
LTNode* BuyListNode(LTDataType x)  
{  
    // 使用malloc为LTNode结构体分配内存空间,并将返回的void*指针强制转换为LTNode*类型。  
    LTNode* node = (LTNode*)malloc(sizeof(LTNode));  
  
    // 检查malloc是否成功分配了内存。  
    if (node == NULL)  
    {  
        // 如果内存分配失败,使用perror函数输出错误信息。  
        perror("malloc fail");  
  
        // 直接退出程序,因为内存分配失败通常意味着严重的问题。  
        // 注意:通常这里应该返回NULL,让调用者处理错误,但这里选择了直接退出。  
        exit(-1);  
    }  
  
    // 初始化新节点的next和prev指针为NULL,表示这是链表中的一个孤立节点。  
    node->next = NULL;  
    node->prev = NULL;  
  
    // 将参数x赋值给新节点的data成员。  
    node->data = x;  
  
    // 返回新创建的节点指针。  
    return node;  
}

初始化双向链表

// 定义一个函数LTInit,它没有参数,返回一个LTNode类型的指针。  
LTNode* LTInit()  
{  
    // 调用BuyListNode函数,创建一个新的LTNode节点,并用-1初始化其data成员。  
    LTNode* phead = BuyListNode(-1);  
  
    // 将新节点的next指针指向其自身,表示这是一个循环链表。  
    phead->next = phead;  
  
    // 将新节点的prev指针也指向其自身,这是双向循环链表的特点。  
    phead->prev = phead;  
  
    // 返回头节点的指针。  
    return phead;  
}

销毁链表

void LTDestroy(LTNode* phead)  
{  
    LTNode* current = phead;  
    LTNode* next;  
  
    // 遍历链表,释放每个节点的内存  
    do {  
        next = current->next; // 保存下一个节点的指针  
        free(current);         // 释放当前节点的内存  
        current = next;        // 移动到下一个节点  
    } while (current != phead); // 遍历直到回到头节点  
  
    // 此时current指向头节点,但头节点已经在循环中被释放了  
    // 因此不需要再次释放phead  
}

打印双向链表

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

	printf("<=head=>");
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

链表判空

#include <assert.h> // 引入assert.h头文件以使用assert宏  
#include <stdbool.h> // 引入stdbool.h头文件以使用bool类型  
  
bool LTEmpty(LTNode* phead)  
{  
    // 使用assert宏来确保phead不为NULL,如果为NULL则终止程序  
    assert(phead != NULL);  
  
    // 判断链表是否为空,即头节点的next是否指向自身  
    // 如果是,说明链表为空(只有头节点),返回true  
    // 否则,返回false 
//也就是下面这个代码
/*if (phead->next == phead)
	{
		return true;
	}
	else
	{
		return false;
	}*/ 
//为了简化,改为下面这个
    return phead->next == phead;  


}

插入结点

void LTInsert(LTNode* pos, LTDataType x)  
{  
    assert(pos); // 确保pos不为NULL  
  
    LTNode* prev = pos->prev; // 获取pos节点的前一个节点  
    LTNode* newnode = BuyListNode(x); // 创建一个新的节点,并用x初始化其data成员  
  
    // 将newnode插入到prev和pos之间  
    prev->next = newnode; // prev的下一个节点指向newnode  
    newnode->prev = prev; // newnode的前一个节点指向prev  
    
    newnode->next = pos;  // newnode的下一个节点指向pos  
    pos->prev = newnode;  // pos的前一个节点指向newnode  
}

后插 

void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	//LTNode* newnode = BuyListNode(x);
	//LTNode* tail = phead->prev;

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

	LTInsert(phead, x);
}

尾删

void LTPopBack(LTNode* phead)  
{  
    assert(phead); // 确保phead不为NULL  
    assert(!LTEmpty(phead)); // 确保链表不为空  
  
    LTNode* tail = phead->prev; // 获取链表的尾节点  
    LTNode* tailPrev = tail->prev; // 获取尾节点的前一个节点  
  
    // 更新tailPrev的next指针,使其指向头节点  
    tailPrev->next = phead;  
    // 更新头节点的prev指针,使其指向tailPrev  
    phead->prev = tailPrev;  
    // 释放尾节点占用的内存  
    free(tail);  
    // 将tail指针置为NULL,避免悬挂指针  
    tail = NULL;  
}

头插

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

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

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

	// 㻻˳
	//newnode->next = phead->next;
	//phead->next->prev = newnode;

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

	LTInsert(phead->next, x);
}

头删

void LTPopFront(LTNode* phead)  
{  
    assert(phead); // 确保phead不为NULL  
    assert(!LTEmpty(phead)); // 确保链表不为空  
  
    LTNode* first = phead->next; // 获取链表的头节点(非哨兵节点)  
    LTNode* second = first->next; // 获取头节点的下一个节点  
  
    // 更新头节点的next指针,使其指向second  
    phead->next = second;  
    // 更新second的prev指针,使其指向头节点  
    second->prev = phead;  
  
    // 释放头节点占用的内存  
    free(first);  
      
    // 特别注意:如果链表中只有一个节点(包括头节点自身),则second此时就是头节点  
    // 在这种情况下,我们需要将头节点的prev和next指针都指向自身,以维持循环结构  
    if (second == phead) {  
        phead->prev = phead;  
        phead->next = phead;  
    }  
}

完整代码

typedef int LTDataType;  
typedef struct ListNode  
{  
  
    struct ListNode* next;  

    struct ListNode* prev;  
    LTDataType data;  
  
} LTNode;

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

	return node;
}

LTNode* LTInit()
{
	LTNode* phead = BuyListNode(-1);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

void LTDestroy(LTNode* phead);

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

	printf("<=head=>");
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

bool LTEmpty(LTNode* phead)
{
	assert(phead);

	/*if (phead->next == phead)
	{
		return true;
	}
	else
	{
		return false;
	}*/

	return phead->next == phead;
}


void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);

	//LTNode* newnode = BuyListNode(x);
	//LTNode* tail = phead->prev;

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

	LTInsert(phead, x);
}

void LTPopBack(LTNode* phead)
{
	assert(phead);
	assert(!LTEmpty(phead));

	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;

	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
	tail = NULL;
}

void LTPushFront(LTNode* phead, LTDataType x)
{
	assert(phead);

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

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

	// 㻻˳
	//newnode->next = phead->next;
	//phead->next->prev = newnode;

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

	LTInsert(phead->next, x);
}

void LTPopFront(LTNode* phead)  
{  
    assert(phead); // 确保phead不为NULL  
    assert(!LTEmpty(phead)); // 确保链表不为空  
  
    LTNode* first = phead->next; // 获取链表的头节点(非哨兵节点)  
    LTNode* second = first->next; // 获取头节点的下一个节点  
  
    // 更新头节点的next指针,使其指向second  
    phead->next = second;  
    // 更新second的prev指针,使其指向头节点  
    second->prev = phead;  
  
    // 释放头节点占用的内存  
    free(first);  
      
    // 特别注意:如果链表中只有一个节点(包括头节点自身),则second此时就是头节点  
    // 在这种情况下,我们需要将头节点的prev和next指针都指向自身,以维持循环结构  
    if (second == phead) {  
        phead->prev = phead;  
        phead->next = phead;  
    }  
}

void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);

	LTNode* prev = pos->prev;
	LTNode* newnode = BuyListNode(x);
	// prev newnode pos

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

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

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1621151.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

idea中打印日志不会乱码,但是部署到外部tomcat中乱码了。

问题&#xff1a;如图Tomcat乱码&#xff0c;而且启动时的系统日志不会乱码&#xff0c;webapp中的打印日志才乱码。 idea中的情况如下&#xff1a;正常中文展示。 问题分析&#xff1a;网上分析的原因是Tomcat配置的字符集和web应用的字符集不匹配&#xff0c;网上集中的解决…

分类预测 | Matlab实现CNN-BiLSTM-SAM-Attention卷积双向长短期记忆神经网络融合空间注意力机制的数据分类预测

分类预测 | Matlab实现CNN-BiLSTM-SAM-Attention卷积双向长短期记忆神经网络融合空间注意力机制的数据分类预测 目录 分类预测 | Matlab实现CNN-BiLSTM-SAM-Attention卷积双向长短期记忆神经网络融合空间注意力机制的数据分类预测分类效果基本描述程序设计参考资料 分类效果 基…

李廉洋:4.24-4.25现货黄金,WTI原油区间震荡,走势分析。

黄金消息面分析&#xff1a;金银近日回调。随着伊朗方面淡化以色列最新反击&#xff0c;中东地区局势没有进一步发酵下&#xff0c;风险溢价下降金银出现较大幅度调整。由于近期高于预期的通胀数据&#xff0c;降息预期持续降温。昨日疲软的美国PMI以及以色列在加沙攻击的加剧支…

数据结构系列-堆排序当中的T-TOK问题

&#x1f308;个人主页&#xff1a;羽晨同学 &#x1f4ab;个人格言:“成为自己未来的主人~” 之前我们讲到了堆排序的实现逻辑&#xff0c;那么接下来我们重点关注的就是其中的T-TOK问题 T-TOK说简单点&#xff0c;就是说&#xff0c;假如有10000个数据&#xff08;随机的…

记录一个hive中跑insert语句说没创建spark客户端的问题

【背景说明】 我目前搭建离线数仓&#xff0c;并将hive的执行引擎改成了Spark&#xff0c;在将ods层的数据装载到dim层&#xff0c;执行insert语句时报如下错误 【报错】 [42000][40000] Error while compiling statement: FAILED: SemanticException Failed to get a spark…

【C++】vector常用函数总结及其模拟实现

目录 一、vector简介 二、vector的构造 三、vector的大小和容量 四、vector的访问 五、vector的插入 六、vector的删除 简单模拟实现 一、vector简介 vector容器&#xff0c;直译为向量&#xff0c;实践中我们可以称呼它为变长数组。 使用时需添加头文件#include<v…

HFSS端口介绍1---集总端口

HFSS中可以设定多种激励端口,但在射频和SI领域使用集总端口(Lumped Port)和波端口(Wave Port)比较多,今天我们主要介绍集总端口。下面是HFSS仿真流程和端口设定说明。 端口定义 端口在电磁仿真中非常重要,它提供3维电磁场求解时的激励,进而求解S参数等信息,这相当于我们平…

网工内推 | 深圳网工专场,上市公司、国企,安全认证优先

01 深圳市同为数码科技股份有限公司武汉分公司 招聘岗位&#xff1a;网络工程师 职责描述&#xff1a; 1、负责网络设备的管理、调试、配置、维护等&#xff1b; 2、负责信息安全网络安全设备、系统的运维&#xff1b; 3、负责整体网络系统技术的相关工作&#xff0c;包括架构…

AI+BI第二弹:QuickBI已支持智能搭建智能问数

缘起&#xff1a;一场主题分享 吴恩达&#xff08;Andrew Ng&#xff09;教授&#xff0c;DeepLearning.AI和AI Fund的创始人&#xff0c;在美国红杉资本于2024年3月26日举办的AI Ascent活动中&#xff0c;谈到了人工智能代理工作流程的未来及其潜力&#xff0c;这些工作流程有…

面向对象三大特征(python)

目录 1. 封装 为什么使用封装&#xff1f; 如何实现封装&#xff1f; 一个简单的封装示例 二.继承 为什么使用继承&#xff1f; 如何实现继承&#xff1f; 一个简单的继承示例 使用继承的好处 三.多态 为什么使用多态&#xff1f; 如何实现多态&#xff1f; 一个简…

【已解决】电脑设置notepad++默认打开txt

1、以管理员的方式打开notepad 步骤&#xff1a;打开设置 -> 首选项 -> 文件关联 2、 设置Notepad默认打开 按照以下步骤将Notepad设置为默认打开.txt文件&#xff1a; 右键单击任何一个.txt文件。选择“属性”。在“常规”选项卡中&#xff0c;找到“打开方式”&#…

Windows SMBGhost CVE-2020-0796 Elevate Privileges

SMBGhost CVE-2020-0796 Microsoft Windows 10 (1903/1909) - ‘SMBGhost’ SMB3.1.1 ‘SMB2_COMPRESSION_CAPABILITIES’ Local Privilege Escalation https://www.exploit-db.com/exploits/48267 Github https://github.com/danigargu/CVE-2020-0796 修改载荷[可选] 生成 c# …

删除链表的倒数第n个节点的最优算法实现

给你一个链表&#xff0c;删除链表的倒数第 n 个结点&#xff0c;并且返回链表的头结点。 提示&#xff1a; 链表中结点的数目为 sz 1 < sz < 300 < Node.val < 1001 < n < sz 你能尝试使用一趟扫描实现吗&#xff1f; 具体实现 要删除链表的倒数第 n 个…

Linux动态追踪——eBPF

目录 摘要 1 什么是 eBPF 2 eBPF 支持的功能 3 BCC 4 编写脚本 5 总结 6 附 摘要 ftrace 和 perf 与 ebpf 同为 linux 内核提供的动态追踪工具&#xff0c;其中 ftrace 侧重于事件跟踪和内核行为的实时分析&#xff0c;perf 更侧重于性能分析和事件统计&#xff0c;与…

Json-server 模拟后端接口

json-server&#xff0c;模拟rest接口&#xff0c;自动生成增删改查接口。(官网地址&#xff1a;json-server - npm) 使用方法&#xff1a; 1. 安装json-server&#xff0c;npm i json-server -g 2. 创建json文件&#xff0c;文件中存储list数据&#xff0c;db.json {"…

路由器本地docker 下载node容器部署 thressjs文档

1. 每次启动本地文档太麻烦 &#xff0c;路由器刚好支持docker&#xff08;tp-link6088&#xff09; &#xff0c;部署上去自启动 2.

简述MASM宏汇编

Hello , 我是小恒不会java。今天写写x86相关底层的东西 寄存器 8086由BIU和EU组成 8088/8086寄存器有14个。8通用&#xff0c;4段&#xff0c;1指针&#xff0c;1标志 8个通用寄存器&#xff1a;这些寄存器可以用来存储任意类型的数据&#xff0c;包括整数、地址等。8086有8个…

PyTorch Conv2d 前向传递中发生了什么?

&#x1f49d;&#x1f49d;&#x1f49d;欢迎莅临我的博客&#xff0c;很高兴能够在这里和您见面&#xff01;希望您在这里可以感受到一份轻松愉快的氛围&#xff0c;不仅可以获得有趣的内容和知识&#xff0c;也可以畅所欲言、分享您的想法和见解。 推荐:「stormsha的主页」…

如何更好的管理个人财务?使用极空间部署私有记账系统Firefly III

如何更好的管理个人财务&#xff1f;使用极空间部署私有记账系统Firefly III 哈喽小伙伴们好&#xff0c;我是Stark-C~ 不知道屏幕前的各位“富哥”日常生活中是怎么管理自己巨额财富的&#xff0c;反正对于像我这样年薪过千的摸鱼族来说&#xff0c;请一个专业的理财顾问多多…

【论文阅读】互连网络的负载平衡路由算法 (RLB RLBth)

前言Oblivious Load Balancing 不经意路由负载平衡 1. oblivious routing 不经意/无关路由的背景知识 1. oblivious routing, adaptive routing & minimal/non-minimal routing algorithms 2. Balancing a 1-Dimensional ring: RLB and RLBth 一维 ring 的 RLB and RLBth 1…