C语言数据结构——循环链表

news2024/11/17 15:25:55

如果人生会有很长,愿有你的荣耀永不散场。——《全职高手》

 


 一 . 循环单链表

循环单链表是单链表的另一种形式,其结构特点是,链表中最后一个结点的指针域不再是结束标记,而是指向整个链表的第一个结点,从而使链表形成一个环。

和单链表相同,循环单链表也有带头结点结构和不带头结点结构两种。带头结点的循环单链表实现插入和删除操作较为方便,且更为常用。

单链表的特点是,从链表头到链表尾操作比较方便,但无法从链表尾到链表头操作;与单链表相比,循环单链表的长处是从链表尾到链表头操作比较方便。

当要处理的元素序列具有环形结构特点时,适合采用循环单链表。

带头结点循环单链表的操作实现方法和带头结点单链表的操作实现方法类同,差别只是以
下两点。
1.在初始化函数中,把语句(*head) ->next-NULL改为(*head) ->next=*head,即把带头结点的循环单链表设置成如图(空链表)所示的形式。
2.在其他函数中,循环判断条件 p->next != NULLp->next->next != NULL 中的NULI
改为头指针 head即可

详细代码这里就不多做赘述。


 二 . 双向循环链表

(一)双向链表

双向链表与单链表不同,在双向链表中,每个结点除后继指针域外还有一个前驱指针域。

但又和单链表类同,双向链表也有带头结点结构和不带头结点结构两种,带头结点的双向链表更为常用。

当然,双向链表也有循环和非循环两种结构,在我们运用的过程中循环结构的双向链表更为常用。

这里讨论的是带头结点的双向环链表

(二)双向循环链表的实现

1. 定义

在双向循环链表中,每个结点包括三个域,分别是 data 域、next 域和 prev 域,其中data域为数据域,next 域为指向后继结点的指针域,prev域为指向前驱结点的指针域。

下图为链表的结点结构。

 

双向循环链表的定义如下

typedef struct ListNode {
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LT;

 


2.1 准备工作

首先我们要创建三个文件:

头文件:BoubleList.h : 用来包含一些必须的头文件,定义结构体,声明一些相关函数。

源文件:BoubleList.c : 用来实现函数具体功能,如:增,删,查,改。

                          Test.c : 用来测试函数功能。

2.2  申请新结点

尾新结点申请一份新内存空间,并将新结点指向next和prev,置空。

//申请新结点
LT* BuyListNode()
{
	LT* newNode = (LT*)malloc(sizeof(LT));

	newNode->next = NULL;

	newNode->prev = NULL;

	//返回新结点
	return newNode;
}

 

2.3 链表初始化

链表初始化即:将链表变为下雨所示结构。

//初始化
LT* ListInit()
{
	LT* phead = NULL;

	//生成哨兵结点
	phead = BuyListNode();

	//只有一个结点,这个结点自己成环
	phead->next = phead;
	phead->prev = phead;

	//返回头结点
	return phead;
}

 2.4 打印链表

在我们打印链表之前我们要先判断该链表是否初始化,若已初始化就遍历链表并打印,当走过了一个循环之后就停止打印。

//打印链表
LT* ListPrint(LT* phead)
{
	//断言,如果未初始化,报错
	assert(phead != NULL);

	//从有效头结点开始走,一直到哨兵位结束——即一个循环之后
	LT* cur = phead->next;

	while (cur != phead)
	{
		printf("%d ", cur->data);

		cur = cur->next;
	}
	printf("NULL");

	printf("\n");
}

 2.5 插入

由于尾插和头插可以直接通过指定位置插入数据的函数实现,所以我们这里先实现指定位置插入数据的函数,然后再将尾插与头插进行实现。

我们先生成一个新结点,然后记录后一个结点的位置,并新结点的next指向pos的后一个结点,新结点的prev指向pos,并且将后一个结点的prev指向新结点,再将pos结点的next指向新结点,最后存储数据即可。

//指定位置插入
void ListInsert(LT* phead, LT* pos, LTDataType x)
{
	//断言,如果未初始化,报错
	assert(phead != NULL);

	//生成新结点
	LT* newnode = BuyListNode();

	//记录后一个结点
	LT* beforenode = pos->next;

	//新结点的next指向pos的后一个结点
	newnode->next = beforenode;

	//新结点的prev指向pos
	newnode->prev = pos;

	//后一个结点的prev指向新结点
	beforenode->prev = newnode;

	//pos结点的next指向新结点
	pos->next = newnode;

	//存储数据
	newnode->data = x;
}

 尾插:第一个函数是用指定位置函数实现的,第二个是直接实现,头插代码也是一样的

如果要直接实现尾插,以下是基本思路

1.生成新结点。

2.尾结点的next指向新结点。

3.新结点的prev指向原尾结点,next指向头结点。

4.头结点的prev指向新结点。

//尾插
void ListPushBack(LT* phead, LTDataType x)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//复用指定插入
	ListInsert(phead, phead->prev, x);
}


void ListPushBack(LT* phead, LTDataType x)
{
	assert(phead);

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

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

 头插:

基本思路:

1 记录原有效头的结点。

2 原有效头的prev指向新有效头。

3 新有效头的prev指向哨兵位,next指向原有效头。

4 哨兵位的next指向新有效头。

//头插
void ListPushFront(LT* phead, LTDataType x)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//复用指定插入
	ListInsert(phead, phead, x);
}


void ListPushFront(LT* phead, LTDataType x)
{
	assert(phead);

	LT* newnode = BuyListNode(x);
	newnode->next = phead->next;
	phead->next->prev = newnode;

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

}

 2.6: 删除

与插入一样头删与尾删也可以由指定位置删除实现。

//指定删除
void ListErase(LT* phead, LT* pos)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//不能删除哨兵
	assert(pos != phead);

	//记录要删除的结点的后一个结点
	LT* beforepos = pos->next;

	//记录待删除结点的前一个结点
	LT* aheadpos = pos->prev;

	//后一个结点的prev指向待删除结点的前一个结点
	beforepos->prev = aheadpos;

	//前一个结点的next指向待删除结点的后一个结点
	aheadpos->next = beforepos;

	//释放结点
	free(pos);
}

尾删:

基本思路:

1 记录尾部的前一个结点。

2 头指针的prev指向新的尾结点。

3 新的尾结点的next指向头结点。

4 注意不要删除掉头结点。

//尾部删除
void LsitPopBack(LT* phead)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//不能删除哨兵结点
	assert(phead != phead->prev);

	//复用指定删除
	ListErase(phead, phead->prev);
}

void ListPopBack(LT* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LTe* tail = phead->prev;
	LT* prev = tail->prev;

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

	free(tail);
	tail = NULL;
}

头删:

基本思路:

1 记录要删除的原有效头结点。

2 新有效头结点的prev指向哨兵位。

3 哨兵位的next指向新有效头结点。

4 注意不要删除哨兵位。

//头删
void ListPopFront(LT* phead)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//不能删除哨兵位
	assert(phead->next != phead);

	//复用指定删除
	ListErase(phead, phead->next);
}

void ListPopFront(LT* phead)
{
	assert(phead);
	assert(phead->next != phead);

	LT* first = phead->next;
	LT* second = first->next;
	phead->next = second;
	second->prev = phead;

	free(first);
	first = NULL;

}

2.7:  查找结点

查找结点位置可以和打印链表一样从第一个有效结点开始遍历链表,找到了就返回结点位置,没找到就返回NULL。

LT* ListPopFind(LT* phead, LTDataType x)
{
	//断言,如果未初始化,报错
	assert(phead != NULL);

	//和打印一样从第一个有效结点开始遍历链表
	LT* cur = phead->next;

	while (cur != phead)
	{
		//查找到返回结点位置
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}

	//查找失败,返回null
	return NULL;
}

 2.8:销毁链表

遍历链表,释放上一个结点的内存即可。最后释放哨兵结点

void ListDestory(LT* phead)
{
	assert(phead);
	LT* cur = phead->next;

	while (cur != phead)
	{
		LT* next = cur->next;
		free(cur);
		cur = next;
	}

	free(phead);
	phead = NULL;
}

(三)最终代码

BoubleList.h

#define _CRT_SECURE_NO_WARNINGS 
#include <stdio.h>
#include <assert.h>
#include <stdlib.h>

//重定义,方便更改存储数据的类型
typedef int LTDataType;

typedef struct ListNode {
	LTDataType data;
	struct ListNode* next;
	struct ListNode* prev;
}LT;


//申请新结点
LT* BuyListNode();
//链表初始化
LT* ListInit();
//打印链表
LT* ListPrint(LT* phead);
//尾部插入
void ListPushBack(LT* phead, LTDataType x);
//尾部删除
void LsitPopBack(LT* phead);
//头部插入
void ListPushFront(LT* phead, LTDataType x);
//头部删除
void ListPopFront(LT* phead);
//查找
LT* ListPopFind(LT* phead, LTDataType x);
//指定插入
void ListInsert(LT* phead, LT* pos, LTDataType x);
//指定删除
void ListErase(LT* phead, LT* pos);

BoubleList.c

#define _CRT_SECURE_NO_WARNINGS
#include "BoubleList.h"

//申请新结点
LT* BuyListNode()
{
	LT* newNode = (LT*)malloc(sizeof(LT));

	//新结点的prev,next最好置空
	newNode->next = NULL;
	newNode->prev = NULL;

	//返回新结点
	return newNode;
}

//初始化
LT* ListInit()
{
	LT* phead = NULL;

	//生成哨兵结点
	phead = BuyListNode();

	//只有一个结点,这个结点自己成环
	phead->next = phead;
	phead->prev = phead;

	//返回头结点
	return phead;
}

//打印
LT* ListPrint(LT* phead)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//从有效头结点开始走,一直到哨兵位结束
	LT* cur = phead->next;
	while (cur != phead)
	{
		//打印
		printf("%d ", cur->data);

		//迭代
		cur = cur->next;
	}
	printf("NULL");

	printf("\n");
}


//尾插
void ListPushBack(LT* phead, LTDataType x)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//复用指定插入
	ListInsert(phead, phead->prev, x);
}


//尾部删除
void LsitPopBack(LT* phead)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//不能删除哨兵结点
	assert(phead != phead->prev);

	//复用指定删除
	ListErase(phead, phead->prev);
}

//头部插入
void ListPushFront(LT* phead, LTDataType x)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//复用指定插入
	ListInsert(phead, phead, x);
}

//头部删除
void ListPopFront(LT* phead)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//不能删除哨兵位
	assert(phead->next != phead);

	//复用指定删除
	ListErase(phead, phead->next);
}

//查找,返回结点位置
LT* ListPopFind(LT* phead, LTDataType x)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//和打印一样从第一个有效结点开始遍历
	LT* cur = phead->next;

	while (cur != phead)
	{
		//查找到返回结点位置
		if (cur->data == x)
			return cur;

		//迭代
		cur = cur->next;
	}

	//查找失败,返回null
	return NULL;
}


//指定插入(后)
void ListInsert(LT* phead, LT* pos, LTDataType x)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//生成新结点
	LT* newnode = BuyListNode();

	//记录后一个结点
	LT* beforenode = pos->next;

	//新结点的next指向pos的后一个结点
	newnode->next = beforenode;

	//新结点的prev指向pos
	newnode->prev = pos;

	//后一个结点的prev指向新结点
	beforenode->prev = newnode;

	//pos结点的next指向新结点
	pos->next = newnode;

	//存储数据
	newnode->data = x;
}

//指定删除
void ListErase(LT* phead, LT* pos)
{
	//如果未初始化,报错
	assert(phead != NULL);

	//不能删除哨兵
	assert(pos != phead);

	//记录要删除的结点的后一个结点
	LT* beforepos = pos->next;

	//记录待删除结点的前一个结点
	LT* aheadpos = pos->prev;

	//后一个结点的prev指向待删除结点的前一个结点
	beforepos->prev = aheadpos;

	//前一个结点的next指向待删除结点的后一个结点
	aheadpos->next = beforepos;

	//释放结点
	free(pos);
}

Test.c

#define _CRT_SECURE_NO_WARNINGS 
#include "BoubleList.h"

void text1()
{
	//初始化
	LT* SL = ListInit();
	//尾部插入
	ListPushBack(SL, 2);
	ListPushBack(SL, 7);
	ListPushBack(SL, 10);
	//打印
	ListPrint(SL);
	//头部插入
	ListPushFront(SL, 8);
	ListPushFront(SL, 112);
	//尾部删除
	LsitPopBack(SL);
	//打印
	ListPrint(SL);
	//头部删除
	ListPopFront(SL);
	ListPopFront(SL);
	ListPopFront(SL);
	//指定插入
	ListInsert(SL, ListPopFind(SL, 8), 76);
	ListPushBack(SL, 100);
	ListPrint(SL);
	ListErase(SL, ListPopFind(SL, 100));
	ListPrint(SL);
}

void text2()
{
	LT* SL = ListInit();

	ListPushFront(SL, 8);
	ListPushFront(SL, 112);

	ListPushBack(SL, 5);
	ListPushBack(SL, 8);

	ListPushFront(SL, 777);

	LsitPopBack(SL);

	ListPrint(SL);

}

int main()
{
	text1();
	//text2();
	return 0;
}


三. 结语

OK!我们的双向循环链表的实现就到这里了,感谢大家!

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

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

相关文章

PLC现场安装和维护的注意事项

虽然PLC是专门在现场使用的控制装置&#xff0c;在设计制造时已采取了很多措施&#xff0c;使它对工业环境比较适应&#xff0c;但是为了确保整个系统稳定可靠&#xff0c;还是应当尽量使PLC有良好的工作环境条件&#xff0c; 并采取必要的抗干扰措施。因此&#xff0c;PLC在安…

python中,unicode对象怎么转换成dict?

python中&#xff0c;unicode对象怎么转换成dict&#xff1f; 使用loads两次

「展会前线」易天光通信盛装亮相2023越南通讯展会

2023年6月7日&#xff0c;在历经了忙碌有序的前期准备工作后&#xff0c;易天光通信销售团队带着满满的信心踏上了越南通讯展会之旅&#xff01; “千呼万唤始出来&#xff0c;犹抱琵琶半遮面”。2023年6月8日&#xff0c;各方期待已久的2023越南通讯展会在越南胡志明市正式开…

肠道有害菌属——假单胞菌属(Pseudomonas),多变且适应性强

谷禾健康 假单胞菌属&#xff08;Pseudomonas&#xff09;是最多样化和普遍存在的细菌属之一&#xff0c;其物种存在于沉积物、临床样本、植物&#xff08;或植物根际&#xff09;、患病动物、水、土壤、海洋、沙漠等&#xff0c;这反映在它们多变的代谢能力和广泛的适应环境的…

3款好用的客户系统管理软件推荐,你用过哪款?

进行客户资料管理确实很重要。我本人在工作中也常常遇到客户关系管理的难题&#xff0c;有时候忘记填写客户信息&#xff0c;亦或是填错信息等场景&#xff0c;甚至会造成许多尴尬局面。为了解决这个问题&#xff0c;我也试用了很多个方法来提高效率。下面我想谈一谈我本人在摸…

十肽-4/Decapeptide-10, CG-IDP2——有效逆转皮肤衰老

简介----十肽-4 十肽-4可以穿透真皮增加胶原蛋白&#xff0c;通过从内至外的重建来逆转皮肤老化的过程&#xff1b;刺激胶原蛋白、弹力纤维和透明质酸增生&#xff0c;提高肌肤的含水量和锁水度&#xff0c;增加皮肤厚度以及减少细纹。 功效与应用----十肽-4 抗皱抗衰老 改善…

浪潮 KaiwuDB x 大数据中心 | 数据驱动政府治理能力快速提升

业务背景 我国工业互联网大数据资源存在孤立、分散、封闭等问题&#xff0c;数据价值未能得到有效利用&#xff0c;数据主权和数据安全面临重大威胁。 发挥数据对工业经济的基础支撑和创新引擎作用&#xff0c;可促进工业互联网的创新发展&#xff0c;加速数据驱动政府治理能…

Pycharm中的find usages有什么用?

问题描述&#xff1a;我们经常使用Pycharm作为开发工具&#xff0c;我们右键会发现有个find usages功能。 比如&#xff0c;我们以YOLOv8中的detect/train.py中的DetectionTrainer()类为例&#xff0c;右键之后如下图所示。 答案&#xff1a;全局搜索&#xff0c;查找类、变量…

「最新」Parallels Desktop 18 for Mac(Pd虚拟机) 18.3.1通用版

Parallels Desktop 18是一款虚拟机软件&#xff0c;能够让Mac电脑上运行Windows、Linux和其他操作系统的应用程序。 此版本的Parallels Desktop 18提供了多项功能增强和改进&#xff0c;包括更快的性能、更好的图形处理、更简单的导入和导出虚拟机等。该软件还支持Apple M1芯片…

QT使用按钮打开新窗口

需求说明&#xff1a;主窗口名为mainwindow&#xff0c;在主窗口添加一个按钮&#xff0c;通过点击按钮能打开一个新的窗口。 第一步&#xff1a;在主窗口添加按钮 找到左边菜单栏的按钮控件拖出置窗口上 第二步&#xff1a;在工程里新建窗口 1.右击最顶层项目文件名&#x…

Springcloud之Feign、Hystrix、Ribbon如何设置超时时间

一&#xff0c;概述 我们在微服务调用服务的时候&#xff0c;会使用hystrix、feign和ribbon&#xff0c;比如有一个实例发生了故障而该情况还没有被服务治理机制及时的发现和摘除&#xff0c;这时候客户端访问该节点的时候自然会失败。 所以&#xff0c;为了构建更为健壮的应…

Vue3系列--provide与inject

目录 Provide inject 在Vue3项目开发的过程中&#xff0c;会创建很多组件&#xff0c;那么避免不了组件之间的通信&#xff0c;在父子组件通信我们可以使用defineProps、defineEmits、defineExpose和Emit方法完成通信&#xff0c;在使用这些方法的前提是需要引用对应的组件。…

第36步 深度学习图像识别:TensorFlow-gpu环境配置

基于WIN10的64位系统演示 一、写在前面 从这一期开始分享基于深度学习图像识别的学习笔记和代码&#xff0c;相比于之前的ML分类模型&#xff0c;图像识别的门槛会更高&#xff0c;包括硬件方面、代码复杂度和基础理论知识等。同样&#xff0c;首先把必要的深度学习框架&…

Nginx 轻松搞定跨域问题

当你遇到跨域问题&#xff0c;不要立刻就选择复制去尝试&#xff0c;请详细看完这篇文章再处理&#xff0c;我相信它能帮到你。 分析前准备&#xff1a; 前端网站地址&#xff1a;http://localhost:8080 服务端网址&#xff1a;http://localhost:59200 首先保证服务端是没有…

微软将GitHub Copilot 与 Visual Studio 深度整合有助于便捷开发

近日对于很多的开发者来说将迎来一个好消息&#xff0c;据悉微软目前正在改善Visual Studio的开发体验&#xff0c;并将GitHub Copilot更深度融合入Visual Studio 中&#xff0c;以提升“AI 写代码”的准确性。 值得注意的是&#xff0c;在Copilot 1.84 版本之前&#xff0c;…

亚马逊养号系统之亚马逊批量养号如何操作?

亚马逊新注册的买家号都是需要先养一段时间才可以的&#xff0c;如果想要同时养大量的买家号那么需要借助软件进行辅助操作才行。 亚马逊鲲鹏系统可以批量养亚马逊买家号&#xff0c;养号方法有两种&#xff0c;一种是AI智能一键养号&#xff0c;一种是设置关键词搜索浏览后进行…

编译 Keras 模型

本篇文章译自英文文档 Compile Keras Models 作者是 Yuwei Hu 更多 TVM 中文文档可访问 →TVM 中文站。 本文介绍如何用 Relay 部署 Keras 模型。 首先安装 Keras 和 TensorFlow&#xff0c;可通过 pip 快速安装&#xff1a; pip install -U keras --user pip install -U …

代码随想录算法训练营第五十五天|392.判断子序列|115.不同的子序列

LeetCode392.判断子序列 动态规划五部曲&#xff1a; 1&#xff0c;确定dp数组&#xff08;dp table&#xff09;以及下标的含义&#xff1a;dp[i][j] 表示以下标i-1为结尾的字符串s&#xff0c;和以下标j-1为结尾的字符串t&#xff0c;相同子序列的长度为dp[i][j]。注意这里…

postman中级:导入文件数据,批量化参数

建议阅读对象&#xff1a;已掌握postman的基本使用&#xff08;参见&#xff1a;postman入门-主界面认识&#xff0c;模拟请求&#xff09; 本地安装的版本&#xff1a;Postman for Windows Version 10.14.9 1.创建csv文件 或 txt文件 文件数据格式&#xff1a; 第一行写下参…

python生成excel文件的三种方式

在我们做平常工作中都会遇到操作excel&#xff0c;那么今天写一篇&#xff0c;如何通过python操作excel。当然python操作excel的库有很多&#xff0c;比如pandas&#xff0c;xlwt/xlrd&#xff0c;openpyxl等&#xff0c;每个库都有不同的区别&#xff0c;具体的区别&#xff0…