我的编程空间,编程开发者的网络收藏夹
学习永远不晚

C语言超详细讲解双向带头循环链表

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

C语言超详细讲解双向带头循环链表

在上一篇所讲述的单链表中,存在一些缺陷:

1、在进行尾插和尾删时,需要遍历链表找到尾结点

2、在进行中间插入和删除时,也需要先遍历链表找到前一个结点

对于这些缺陷,可以采用一种结构更为复杂的链表 双向带头循环链表

双向带头循环链表结构虽然复杂,但在链表的操作上带来了很大的优势

一、双向带头循环链表的结构

//存储数据的类型,这里以 int 来举例
typedef int LTDataType;
//结点的类型
typedef struct ListNode
{
	LTDataType data;
	struct ListNode* prev;
	struct ListNode* next;
}LTNode;

二、双向带头循环链表的函数接口

1. 申请结点

在插入等操作时需要申请结点,为了避免麻烦重复的操作,这里将申请结点封装为一个函数

申请结点函数如下:

LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		//开辟空间失败,打印错误信息
		perror("malloc");

		//结束程序
		exit(-1);
	}
	newnode->data = x;
	newnode->prev = newnode->next = NULL;
	return newnode;
}

2. 初识化

在双向带头循环链表中,即使没有存储数据也 至少会包含一个哨兵位的头结点

初始化函数如下:

LTNode* InitLT()
{
	//申请头结点,头结点的数据存什么无关紧要
	LTNode* phead = BuyLTNode(-1);
	//改变指针指向,构成循环
	phead->prev = phead->next = phead;
	return phead;
}

3. 打印

为了验证插入、删除等得到的结果是否正确,提供打印函数,这里数据类型以 int 为例,当读者采用的类型不同时,自行更改函数即可

打印函数如下:

void LTPrint(LTNode* phead)
{
	//链表不能为空
	assert(phead);
	LTNode* cur = phead->next;
	printf("head->");
	while (cur != phead)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("head\n");
}

4. 尾插尾删

尾插:在链表的最后一个结点之后插入结点

尾插函数如下:

void LTPushBack(LTNode* phead, LTDataType x)
{
	//链表不能为空
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
	//找到尾结点
	LTNode* tail = phead->prev;
	//改变指针指向
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

尾删:删除链表最后一个结点

尾删函数如下:

void LTPopBack(LTNode* phead)
{
	assert(phead);	//链表不能为空
	assert(phead->next != phead);	//空链表不能删
	//找尾结点及尾结点的前一个结点
	LTNode* tail = phead->prev;
	LTNode* tailPrev = tail->prev;
	//改变指针指向
	tailPrev->next = phead;
	phead->prev = tailPrev;
	free(tail);
}

5. 头插头删

头插: 在第一个结点之前插入新结点

头插函数如下:

void LTPushFront(LTNode* phead, LTDataType x)
{
	//链表不能为空
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
	//找到头结点后的第一个结点
	LTNode* first = phead->next;
	//改变指针指向
	phead->next = newnode;
	newnode->prev = phead;
	newnode->next = first;
	first->prev = newnode;
}

头删:删除链表的第一个结点

头删函数如下:

void LTPopFront(LTNode* phead)
{
	assert(phead);	//链表不能为空
	assert(phead->next != phead);	//空链表不能删
	//找到头结点后的第一个和第二个结点
	LTNode* first = phead->next;
	LTNode* second = first->next;
	//改变指针指向
	phead->next = second;
	second->prev = phead;
	free(first);
}

6. 查找

查找:如果数据存在,返回该数据结点的指针,不存在返回 NULL

查找函数如下:

LTNode* LTFind(LTNode* phead, LTDataType x)
{
	//链表不能为空
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x) return cur;
		cur = cur->next;
	}
	return NULL;
}

7. 中间插入和删除

中间插入:通过查找函数 LTFind 获得指向结点的指针 pos,在 pos 指向的 结点之前 插入结点

在 pos 之前插入结点函数如下:

void LTInsert(LTNode* pos, LTDataType x)
{
	//pos 不能为空
	assert(pos);
	LTNode* newnode = BuyLTNode(x);
	//找到 pos 的前一个结点
	LTNode* posPrev = pos->prev;
	//改变指针指向
	posPrev->next = newnode;
	newnode->prev = posPrev;
	newnode->next = pos;
	pos->prev = newnode;
}

在调用中间插入函数 LTInsert 时

  • 如果在链表头结点之前插入数据,便和尾插函数的功能一样
  • 如果在链表头结点之后插入数据,便和头插函数的功能一样

因此在尾插和头插函数的实现中可以直接调用中间插入函数 LTInsert

尾插和头插函数更改如下:

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	//链表不能为空
	assert(phead);
	LTInsert(phead, x);
}
//头插
void LTPushFront(LTNode* phead, LTDataType x)
{
	//链表不能为空
	assert(phead);
	LTInsert(phead->next, x);
}

中间删除:通过查找函数 LTFind 获得指向结点的指针 pos,删除 pos 指向的结点

删除 pos 指向的结点函数如下:

void LTErase(LTNode* pos)
{
	//pos 不能为空
	assert(pos);
	//找到 pos 的前一个和后一个结点
	LTNode* posPrev = pos->prev;
	LTNode* posNext = pos->next;
	//改变指针指向
	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

在调用中间删除函数 LTErase 时

  • 如果删除链表头结点的前一个结点,便和尾删函数的功能一样
  • 如果删除链表头结点的后一个结点,便和头删函数的功能一样

因此在尾删和头删函数的实现中可以直接调用中间删除函数 LTErase

尾删和头删函数更改如下:

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);	//链表不能为空
	assert(phead->next != phead);	//空链表不能删
	LTErase(phead->prev);
}
//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);	//链表不能为空
	assert(phead->next != phead);	//空链表不能删
	LTErase(phead->next);
}

8. 判空及求链表长度

判空:判断链表是否为空

判空函数如下:

bool LTEmpty(LTNode* phead)
{
	//链表不能为空
	assert(phead);
	return phead->next == phead;
}

链表长度:链表有效数据个数

链表长度函数如下:

size_t LTSize(LTNode* phead)
{
	//链表不能为空
	assert(phead);
	size_t size = 0;
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		size++;
		cur = cur->next;
	}
	return size;
}

9. 销毁单链表

在链表中,存储数据的结点是由自己开辟的,当不使用链表时,应将其销毁

销毁链表函数如下:

void LTDestroy(LTNode* phead)
{
	//链表不能为空
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* curNext = cur->next;
		free(cur);
		cur = curNext;
	}
	free(phead);
}

到此这篇关于C语言超详细讲解双向带头循环链表的文章就介绍到这了,更多相关C语言双向带头循环链表内容请搜索编程网以前的文章或继续浏览下面的相关文章希望大家以后多多支持编程网!

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

C语言超详细讲解双向带头循环链表

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

C语言超详细讲解双向带头循环链表

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单
2023-02-14

C语言带头双向循环链表怎么实现

这篇“C语言带头双向循环链表怎么实现”文章的知识点大部分人都不太理解,所以小编给大家总结了以下内容,内容详细,步骤清晰,具有一定的借鉴价值,希望大家阅读完这篇文章能有所收获,下面我们一起来看看这篇“C语言带头双向循环链表怎么实现”文章吧。带
2023-06-30

C语言怎么实现带头双向循环链表

本篇内容主要讲解“C语言怎么实现带头双向循环链表”,感兴趣的朋友不妨来看看。本文介绍的方法操作简单快捷,实用性强。下面就让小编来带大家学习“C语言怎么实现带头双向循环链表”吧!创建链表存储结构我们需要创建一个结构体来存储一个链表结点的相关信
2023-06-30

C语言如何实现带头双向循环链表

这篇文章主要介绍了C语言如何实现带头双向循环链表,具有一定借鉴价值,感兴趣的朋友可以参考下,希望大家阅读完这篇文章之后大有收获,下面让小编带着大家一起了解一下。前言在实际生活中最常用的就是这两种链表。无头单向非循环链表。和带头双向循环链表。
2023-06-29

C语言带头双向循环链表的示例代码

这篇文章主要介绍了如何利用C语言实现带头双向循环链表,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
2022-11-13

C++实现带头双向循环链表的示例详解

这篇文章主要介绍了如何利用C++实现带头双向循环链表,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
2022-12-08

C++如何实现带头双向循环链表

这篇文章主要为大家展示了“C++如何实现带头双向循环链表”,内容简而易懂,条理清晰,希望能够帮助大家解决疑惑,下面让小编带领大家一起研究并学习一下“C++如何实现带头双向循环链表”这篇文章吧。什么是带头双向循环链表什么是带头?双向?循环?(
2023-06-29

C++带头双向循环链表怎么实现

这篇文章主要介绍“C++带头双向循环链表怎么实现”,在日常操作中,相信很多人在C++带头双向循环链表怎么实现问题上存在疑惑,小编查阅了各式资料,整理出简单好用的操作方法,希望对大家解答”C++带头双向循环链表怎么实现”的疑惑有所帮助!接下来
2023-06-29

C语言怎么实现线性表中的带头双向循环链表

这篇文章主要介绍了C语言怎么实现线性表中的带头双向循环链表的相关知识,内容详细易懂,操作简单快捷,具有一定借鉴价值,相信大家阅读完这篇C语言怎么实现线性表中的带头双向循环链表文章都会有所收获,下面我们一起来看看吧。一、本章重点带头双向循环链
2023-06-29

编程热搜

  • Python 学习之路 - Python
    一、安装Python34Windows在Python官网(https://www.python.org/downloads/)下载安装包并安装。Python的默认安装路径是:C:\Python34配置环境变量:【右键计算机】--》【属性】-
    Python 学习之路 - Python
  • chatgpt的中文全称是什么
    chatgpt的中文全称是生成型预训练变换模型。ChatGPT是什么ChatGPT是美国人工智能研究实验室OpenAI开发的一种全新聊天机器人模型,它能够通过学习和理解人类的语言来进行对话,还能根据聊天的上下文进行互动,并协助人类完成一系列
    chatgpt的中文全称是什么
  • C/C++中extern函数使用详解
  • C/C++可变参数的使用
    可变参数的使用方法远远不止以下几种,不过在C,C++中使用可变参数时要小心,在使用printf()等函数时传入的参数个数一定不能比前面的格式化字符串中的’%’符号个数少,否则会产生访问越界,运气不好的话还会导致程序崩溃
    C/C++可变参数的使用
  • css样式文件该放在哪里
  • php中数组下标必须是连续的吗
  • Python 3 教程
    Python 3 教程 Python 的 3.0 版本,常被称为 Python 3000,或简称 Py3k。相对于 Python 的早期版本,这是一个较大的升级。为了不带入过多的累赘,Python 3.0 在设计的时候没有考虑向下兼容。 Python
    Python 3 教程
  • Python pip包管理
    一、前言    在Python中, 安装第三方模块是通过 setuptools 这个工具完成的。 Python有两个封装了 setuptools的包管理工具: easy_install  和  pip , 目前官方推荐使用 pip。    
    Python pip包管理
  • ubuntu如何重新编译内核
  • 改善Java代码之慎用java动态编译

目录