【数据结构】双向带头循环链表的实现

这篇具有很好参考价值的文章主要介绍了【数据结构】双向带头循环链表的实现。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

前言:在前面我们学习了顺序表、单向链表,今天我们在单链表的基础上进一步来模拟实现一个带头双向链表。

💖 博主CSDN主页:卫卫卫的个人主页 💞
👉 专栏分类:数据结构 👈
💯代码仓库:卫卫周大胖的学习日记💫
💪关注博主和博主一起学习!一起努力!
【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言

双向链表

带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表(如下图所示)。通常我们会使用一个头节点head其并不存储数据只是作为一个哨兵位的作用负责指向下一个元素。【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言
双向链表的基本功能:

  1. 双向链表的初始化
  2. 双链表的销毁
  3. 创建一个新的节点
  4. 打印链表中的元素
  5. 双向链表尾插
  6. 双向链表尾删
  7. 双向链表头插
  8. 双向链表的头删
  9. 双链表元素查找
  10. 双向链表在pos的前面进行插入
  11. 双向链表删除pos位置的节点

双向链表的定义

//双向链表的定义
typedef int LTDatatype;

typedef struct ListNode
{
	struct ListNode* next;
	struct ListNode* prev;
	LTDatatype val;
}LTNode;


双向链表-创建新的节点

因为我们是动态开辟的链表,因此我们在对链表进行操作的时候,每插入一个节点时都要开辟一个节点,因此我们一样写一个接口函数来实现。
代码思路:我们只需要直接和前面单链表一样开辟思路即可,无非我们需要多管理一个prev指针,这里我们让其置为空即可。
代码实现

LTNode* ListCreate(LTDatatype x)//创建一个新的节点
{
	LTNode* newnode = malloc(sizeof(LTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return;
	}
	newnode->next = NULL;
	newnode->val = x;
	newnode->prev = NULL;
	return newnode;
}

双向链表-初始化

代码思路:因为双向链表中,我们需要一个哨兵位(头节点)来管理,因此我们在初始化的时候,需要开辟一个节点作为哨兵位,然后将其的prenext指针置为空即可。
代码实现:

LTNode* ListInit()//双链表的初始化
{
	LTNode* phead = ListCreate(-1);
	phead->next = phead;
	phead->prev = phead;
	return phead;
}


双向链表-打印链表中的元素

代码思路:由于我们是一个循环双向链表,在前面的图可以知道,我们不能够直接通过判断尾指针是否为空指针来判定是否是链表中的尾部元素,但是我们可以知道的是:链表中的最后一个节点的下一个节点,是该链表的头部,所以我们通过判定链表中当前节点的下一个节点是不是头节点,就可以知道是否是链表的尾部了。
代码实现:

void ListPrint(LTNode* pHead)//打印链表中的元素
{
	assert(pHead);
	LTNode* cur = pHead;
	printf("哨兵位-> ");
	while (pHead != cur->next)
	{
		printf("%d->", cur->next->val);
		cur = cur->next;
	}
	printf("\n");

双向链表-尾插

代码思路:由于双向循环链表的特性,我们可以知道哨兵位的pre指向的是尾部节点,因此我们在尾插的时候不用特意的去寻找尾节点,我们只需要,用哨兵位的前驱指针找到尾部节点,让其指向新开辟的空间。因此:

  1. 通过哨兵位的前驱指针找到尾部节点
  2. 让尾部节点指向新开辟的空间。
  3. 让新开辟的空间的前驱指针指向原理的尾部节点,再让其尾指针指向头节点,即可完成双向链表的尾插。(如下图所示)
    【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言

函数实现

void ListPushBack(LTNode* pHead, LTDatatype x)//双向链表尾插
{
	assert(pHead);
	LTNode* newnode = ListCreate(x);
	LTNode* cur = pHead->prev;//尾结点
	pHead->prev = newnode;
	newnode->next = pHead;
	newnode->prev = cur;
	cur->next = newnode;
}

函数测试:

void Test_ListPushBack()
{
	LTNode* sl = ListInit();
	ListPushBack(sl, 5);
	ListPushBack(sl, 2);
	ListPushBack(sl, 1);
	ListPushBack(sl, 8);
	ListPrint(sl);
}

运行结果:
【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言


双向链表-尾删

代码思路:这里我们依然要重复利用刚刚说到的循环双链表的性质,我们直接通过哨兵位的前驱指针来找到尾结点,来帮助我们进行尾删。

  1. 通过哨兵位前驱指针找到尾节点
  2. 找到尾结点的前驱指针,即倒数第二个节点,让其指向头节的前驱指针指向倒数第二个节点。
  3. 此时在将倒数第二个节点的尾部节点指向头节点,即可完成尾删,此时在释放掉原本的尾部节点即可。(如下图所示)
    【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言

代码实现:

void ListPopBack(LTNode* pHead)//双向链表尾删
{
	assert(pHead);
	assert(pHead->next);
	LTNode* tail = pHead->prev;//尾部节点
	pHead->prev = tail->prev;//让头节点指向倒数第二个节点
	tail->prev->next = pHead;//尾部节点指向头节点
	free(tail);
	tail = NULL;
}

函数测试:


void Test_ListPopBack()//双向链表尾删
{
	LTNode* sl = ListInit();
	ListPushBack(sl, 5);
	ListPushBack(sl, 2);
	ListPushBack(sl, 1);
	ListPushBack(sl, 8);
	printf("删除前\n");
	ListPrint(sl);
	ListPopBack(sl);
	printf("删除后\n");
	ListPrint(sl);
}

运行结果:
【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言


双向链表-头插

代码思路:

  1. 我们将哨兵位的next指针指向新开辟的节点。
  2. 将新节点的前驱指针指向原本的哨兵位
  3. 将新节点的next指向原本的第二个节点
  4. 将原本的第二个节点的前驱指针指向新节点,即可完成头插。(如下图)【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言

代码实现:

void ListPushFront(LTNode* pHead, LTDatatype x)//双向链表头插
{
	assert(pHead);
	LTNode* newnode = ListCreate(x);
	LTNode* tail = pHead->next;//记录头节点的下一个节点的位置
	pHead->next = newnode;//让头节点的下一个节点指向新节点
	newnode->prev = pHead;//让新节点的前驱指针指向头节点
	tail->prev = newnode;//让原本的第二个节点的前驱指针指向newnode
	newnode->next = tail;//新节点的尾部节点指针原本的第二个节点
}

函数测试:

void Test_ListPushFront()//双向链表头插
{
	LTNode* sl = ListInit();
	ListPushBack(sl, 5);
	ListPushBack(sl, 2);
	ListPushBack(sl, 1);
	ListPushBack(sl, 8);
	printf("头插前\n");
	ListPrint(sl);
	printf("头插后\n");
	ListPushFront(sl, 10);
	ListPrint(sl);
}

运行结果
【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言


双向链表-头删

代码思路:

  1. 我们直接通过哨兵位找到头节点,然后将哨兵位的后驱指针指向头节点的下一个节点。
  2. 将头节点的下一个节点的前驱指针指向哨兵位
  3. 在将原本的头节点释放掉即可完成头删(如下图)
    【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言

代码实现:

void ListPopFront(LTNode* pHead)//双向链表的头删
{
	assert(pHead);
	assert(pHead->next);
	LTNode* tail = pHead->next;
	pHead->next = tail->next;//找到第二个节点指向哨兵位后驱指针
	tail->next->prev = pHead;//让次节点指向哨兵位
	free(tail);
	tail = NULL;
}

函数测试:

void Test_ListPopFront()//双向链表的头删
{
	LTNode* sl = ListInit();
	ListPushBack(sl, 5);
	ListPushBack(sl, 2);
	ListPushBack(sl, 1);
	ListPushBack(sl, 8);
	printf("头删前\n");
	ListPrint(sl);
	printf("头删后\n");
	ListPopFront(sl);
	ListPrint(sl);
}

运行结果:
【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言


双向链表-元素查找

代码思路:

  1. 这里我们依然通过双向链表的性质来帮助我们判断是否走到了链表尾部。
  2. 我们定义一个cur指针去帮助我们查找
  3. cur碰到了尾部的时候说明查找完了,如果此时还没找到就返回空即可
  4. 在查找的过程中碰到了要查找的值直接返回此时cur的地址即可。(如下图)
    【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言

代码实现:

LTNode* ListFind(LTNode* pHead, LTDatatype x)//双链表查找
{
	assert(pHead);
	LTNode* cur = pHead->next;
	while (cur != pHead)
	{
		if (cur->val == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

函数测试


void Test_ListFind()//双向链表的查找
{
	LTNode* sl = ListInit();
	ListPushBack(sl, 5);
	ListPushBack(sl, 2);
	ListPushBack(sl, 1);
	ListPushBack(sl, 8);
	printf("%p\n", ListFind(sl, 2));
	printf("%p\n", ListFind(sl, 999));
}

运行结果:
【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言


双向链表-在pos的前面进行插入

代码思路:

  1. 我们找到要插入的pos的前一个节点,让其的next指向新开辟的节点
  2. 在让此时pos前驱指针所在的位置指向新开辟的节点
  3. 再让原本插入的节点的next指向新开辟的节点
  4. 再让新开辟的节点的前驱指针和next分别指向原本pos的前一个节点和指向pos。(如下图)

【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言
代码实现:

void ListInsert(LTNode* pos, LTDatatype x)// 双向链表在pos的前面进行插入
{
	LTNode* newnode = ListCreate(x);
	LTNode* cur = pos->prev;//找到pos前的一个节点
	pos->prev = newnode;//让其前一个结点指向新结点
	cur->next = newnode;
	newnode->prev = cur;
	newnode->next = pos;
}

函数测试:

void Test_ListInsert()
{
	LTNode* sl = ListInit();
	ListPushBack(sl, 5);
	ListPushBack(sl, 2);
	ListPushBack(sl, 1);
	ListPushBack(sl, 8);
	printf("插入前\n");
	ListPrint(sl);
	ListInsert(ListFind(sl, 2), 99);
	printf("插入后\n");
	ListPrint(sl);
}

运行结果:
【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言


双向链表-删除pos所在的位置

代码思路:

  1. 我们找到pos所在位置的下个一个节点让其指向pos所在位置的前一个结点
  2. 此时在释放掉pos所在的位置即可完成删除

代码实现:

void ListErase(LTNode* pos)// 双向链表删除pos位置的节点
{
	assert(pos);
	LTNode* tail = pos->next;
	tail->prev = pos->prev;
	pos->prev->next = tail;
	free(pos);
	pos = NULL;
}

函数测试:

void Test_ListErase()
{
	LTNode* sl = ListInit();
	ListPushBack(sl, 5);
	ListPushBack(sl, 2);
	ListPushBack(sl, 1);
	ListPushBack(sl, 8);
	printf("删除前\n");
	ListPrint(sl);
	printf("删除后\n");
	ListErase(ListFind(sl, 2));
	ListPrint(sl);
}

运行结果:
【数据结构】双向带头循环链表的实现,数据结构的学习,数据结构,链表,c语言

双向链表-链表的销毁

关于双向链表的销毁这里就不做过多的总结了,这个和前面的打印元素有比较像,因此不懂的可以参考一下即可。
代码实现:

void ListDestory(LTNode* pHead)//双链表的销毁
{
	assert(pHead);
	LTNode* cur = pHead->next;
	
	while (pHead != cur)
	{
		LTNode* tail = cur->next;
		free(cur);
		cur = tail;
	}
	free(pHead);

}

总结

到这里我们可以发现,当我们写了一个插入之后会发现,那双向链表的头插和尾插,我们可以直接用我们刚刚写的插入的函数直接来实现,就完全没必要单独写尾插和头插了,至于为什么放在最后才说,是因为作者想和大家一起锻炼一下自己的思维能力,这里直接放代码就不演示了。

void ListPushBack(LTNode* pHead, LTDatatype x)//双向链表尾插
{
	assert(pHead);
	ListInsert(pHead,x);//直接再phead之前插入即可
}

void ListPushFront(LTNode* pHead, LTDatatype x)//双向链表头插
{
	assert(pHead);
	ListInsert(pHead->next, x);
}

结语:今天的内容就到这里吧,谢谢各位的观看,如果有讲的不好的地方也请各位多多指出,作者每一条评论都会读的,谢谢各位。文章来源地址https://www.toymoban.com/news/detail-791786.html


🫵🫵🫵 这里提前祝各位新年快乐呀! 💞

到了这里,关于【数据结构】双向带头循环链表的实现的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】链表:带头双向循环链表的增删查改

    本篇要分享的内容是带头双向链表,以下为本片目录 目录 一、链表的所有结构 二、带头双向链表 2.1尾部插入 2.2哨兵位的初始化 2.3头部插入 2.4 打印链表 2.5尾部删除 2.6头部删除  2.7查找结点 2.8任意位置插入 2.9任意位置删除  在刚开始接触链表的时候,我们所学仅仅所学的

    2024年02月05日
    浏览(78)
  • 【数据结构】实现带头双向循环链表

    之前我们已经学习了单链表,有了单链表的基础,现在开始学习带头双向循环链表~ 结构最复杂 ,一般用在单独存储数据。 实际中使用的链表数据结构,都是带头双向循环链表 。另外这个结构虽然结构复杂,但是使用代码实现以后会发现 结构会带来很多优势 ,实现反而简单

    2024年02月10日
    浏览(37)
  • 【数据结构】带头双向循环链表及其实现

    目录 1.带头双向循环链表 2.带头双向循环链表实现 2.1初始化 2.2销毁 2.3头插 2.4链表打印 2.5头删数据 2.6尾插数据 2.7尾删数据 2.8链表判空  2.9查找一个数据 2.10在pos位置前插入数据 2.11删除pos位置 2.12求链表的长度 2.顺序表和链表的比较 我们已经实现了无头单向循环链表 带头双

    2024年02月10日
    浏览(31)
  • 数据结构: 线性表(带头双向循环链表实现)

    之前一章学习了单链表的相关操作, 但是单链表的限制却很多, 比如不能倒序扫描链表, 解决方法是在数据结构上附加一个域, 使它包含指向前一个单元的指针即可. 那么怎么定义数据结构呢? 首先我们先了解以下链表的分类 链表的结构非常多样, 以下情况组合起来就有 8 中链表

    2024年02月14日
    浏览(31)
  • 数据结构之双向带头循环链表函数功能实现与详细解析

    个人主页:点我进入主页 专栏分类:C语言初阶      C语言程序设计————KTV       C语言小游戏     C语言进阶 C语言刷题       数据结构初阶 欢迎大家点赞,评论,收藏。 一起努力,一起奔赴大厂。 目录 1.前言 2.带头双向循环链表函数实现 3.总结         在前面我

    2024年02月05日
    浏览(42)
  • 【数据结构和算法】实现带头双向循环链表(最复杂的链表)

    前文,我们实现了认识了链表这一结构,并实现了无头单向非循环链表,接下来我们实现另一种常用的链表结构,带头双向循环链表。如有仍不了解单向链表的,请看这一篇文章(7条消息) 【数据结构和算法】认识线性表中的链表,并实现单向链表_小王学代码的博客-CSDN博客

    2024年01月17日
    浏览(64)
  • 数据结构入门(C语言版)线性表带头双向循环链表接口实现

    在上一篇博客我们讲述了链表的概念和结构,还实现了无头单向非循环链表接口写法,那么这一章节,我们来实现另一种常用的链表组成结构——带头双向循环链表。 如果对前面的链表基本概念还是不了解,可以看作者的上一篇博客: 线性表中链表介绍及无头单向非循环链

    2023年04月12日
    浏览(37)
  • 追梦之旅【数据结构篇】——详解C语言动态实现带头结点的双向循环链表结构

        😎博客昵称:博客小梦 😊最喜欢的座右铭:全神贯注的上吧!!! 😊作者简介:一名热爱C/C++,算法等技术、喜爱运动、热爱K歌、敢于追梦的小博主! 😘博主小留言:哈喽! 😄各位CSDN的uu们,我是你的博客好友小梦,希望我的文章可以给您带来一定的帮助,话不

    2024年01月17日
    浏览(38)
  • 链接未来:深入理解链表数据结构(二.c语言实现带头双向循环链表)

    上篇文章简述讲解了链表的基本概念并且实现了无头单向不循环链表:链接未来:深入理解链表数据结构(一.c语言实现无头单向非循环链表)-CSDN博客 那今天接着给大家带来带头双向循环链表的实现 : 头文件DoubleList.h:用来基础准备(常量定义,typedef),链表表的基本框架

    2024年01月16日
    浏览(51)
  • 数据结构——带头双向循环链表

    在创建带头双向循环链表的节点中比之前单链表节点的创建多了一个struct ListNode* prev;结构体指针,目的在与存储前一个节点的地址,便于将整个链表连在一起。 动态申请内存结点,函数返回的是一个指针类型,用malloc开辟一个LTNode大小的空间,并用node指向这个空间,再判断

    2024年02月09日
    浏览(29)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包