数据结构之带头双向循环链表

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

目录

链表的分类

带头双向循环链表的实现

带头双向循环链表的结构

带头双向循环链表的结构示意图

空链表结构示意图

单结点链表结构示意图

 多结点链表结构示意图

链表创建结点

双向链表初始化

销毁双向链表

打印双向链表

 双向链表尾插

尾插函数测试

双向链表头插

头插函数测试

 双向链表尾删

尾删函数测试

双向链表头删

头删函数测试

双向链表查找

双向链表pos位置前插

插入函数测试

 双向链表删除pos位置的结点

删除函数测试

利用 ListInsert()函数改造头插尾插函数

尾插函数改造版本

头插函数改造版本

利用ListEarse()函数改造头删 尾删函数

头删函数改造版本

尾删函数改造版本

计算双向链表长度


链表的分类

  • 单向/双向

单向列表:每一个结点结构中只保存下一结点的地址,所以很难从后一结点找到前一节点;

双向列表:每一个结点结构中不仅保存下一结点的地址,还保存上一节点的地址;方便寻找前一节点和后一节点;

数据结构之带头双向循环链表,数据结构,链表,c语言,算法 

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

  • 带头/不带头

带头:在头结点之前有一个哨兵位结点,哨兵位的数据域不存储有效数据,指针域指向头结点

不带头:没有哨兵位结点,尾插尾删考虑头结点情况;

 数据结构之带头双向循环链表,数据结构,链表,c语言,算法

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

  • 循环/非循环

循环:头结点与尾结点相连;

非循环:头结点与尾结点不相连;

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

上述情况相互组合,共有8种情况,  实际中使用的链表数据结构,都是带头双向循环链表,带头双向循环链表虽然结构复杂,但是其结构具有很多优势,实现反而简单;

带头双向循环链表的实现

带头双向循环链表的结构

typedef int LTDataType;
typedef struct ListNode
{
	struct ListNode* prev;//前址域-存放前一个结点的地址
	LTDataType data;//数据域
	struct ListNode* next;//后址域-存放后一个结点的地址
}ListNode;

逻辑图:

数据结构之带头双向循环链表,数据结构,链表,c语言,算法 物理图:

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

带头双向循环链表的结构示意图

  • 空链表结构示意图

由图可知,head->prev=head; head->next=head;

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

  • 单结点链表结构示意图

由图可知:

head->next=FirstNode;

head->prev=FirstNode;

FirstNode->prev=head;

FirstNode->next=head;

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

  •  多结点链表结构示意图

由图可知:

head->next=firstnode;

head->prev=tail;

tail->next=head;

firstnode->prev=head;

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

链表创建结点

//创建链表结点,返回链表结点地址
ListNode* BuyListNode(LTDataType x)
{
	ListNode* newnode = (ListNode*)malloc(sizeof(ListNode));
	if (newnode == NULL)
	{
		perror("malloc failed:");
		exit(-1);
	}

	newnode->data = x;
	newnode->next = NULL;
	newnode->prev = NULL;

	return newnode;
}

双向链表初始化

 注:函数调用时得到动态开辟的链表空间起始地址的两种方案如下

方案一: 当传参时为链表结点的地址,函数的形参设计为二级指针,只有通过传址调用,可以将动态开辟的链表的起始地址带出函数;

方案二: 设计函数的返回类型为结点指针,返回动态开辟的链表结点指针,如此可以得到链表空间的起始地址;

//初始化链表(空链表)
ListNode* ListInit()
{
	//创建哨兵位结点
	ListNode* head = BuyListNode(0);//0不是有效数据

	//初始化哨兵位结点的指针域
	head->next = head;
	head->prev = head;

	return head;
}

销毁双向链表

  • 循环遍历释放结点,包含哨兵位结点;
  • 释放前保存下一结点地址,避免地址丢失;
//销毁链表,包含哨兵位结点
void DestoryList(ListNode* phead)
{
	assert(phead);

	//创建寻址指针
	ListNode* cur = phead;
	//断开循环链表
	phead->prev->next = NULL;
	while (cur != NULL)
	{
		//记录下一结点地址
		ListNode* next = cur->next;
        //释放当前结点
		free(cur);
		//寻找下一节点
		cur = next;
	}
	return;
}

打印双向链表

  • 循环遍历链表打印数据,不显示哨兵位结点的数据域;
  • 以哨兵位头结点作为结束标志;
void PrintList(ListNode* phead)
{
	assert(phead != NULL);

	ListNode* cur = phead->next;

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

 双向链表尾插

  •  尾插先找尾,哨兵位的前址域即为尾结点即tail=head->prev;
  • 当链表为空时,连接的逻辑关系相同(创建三个指针变量,按照新结点的前址域指向谁,谁指向新结点,新结点的后址域指向谁,谁指向新结点进行连接);
void ListPushBack(ListNode* phead, LTDataType x)
{
	assert(phead);
    
	//寻找尾结点
	ListNode* tail = phead->prev;
    //创建新结点
	ListNode* newnode = BuyListNode(x);

   //尾插
	newnode->prev = tail;
	tail->next = newnode;

	newnode->next = phead;
	phead->prev = newnode;
}
尾插函数测试
void Test1()
{
	ListNode* plist=ListInit();
	
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	PrintList(plist);
}
int main()
{
	Test1();
	return 0;
}

运行结果:

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

双向链表头插

  • 头插前先保存哨兵位结点的下一节点即原先真正的首节点;
  • 按照按照新结点的前址域指向谁,谁指向新结点,新结点的后址域指向谁,谁指向新结点进行连接从而实现头插,链表为空时,头插逻辑仍然相同;
//链表头插
void ListPushFront(ListNode* phead, LTDataType x)
{
	assert(phead);

	//保存原先的首节点
	ListNode* firstnode = phead->next;
	//创建新结点
	ListNode* newnode = BuyListNode(x);

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

	newnode->next = firstnode;
	firstnode->prev = newnode;
}
头插函数测试
void Test2()
{
	ListNode* plist = ListInit();
	ListPushFront(plist, 10);
	ListPushFront(plist, 20);
	ListPushFront(plist, 30);
	ListPushFront(plist, 40);
	ListPushFront(plist, 50);
	PrintList(plist);

}
int main()
{
	Test2();
	return 0;
}

运行结果:

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

 双向链表尾删

  • 链表中只剩哨兵位结点,此时链表为空,不再进行尾删;
  • 尾删前记录前一节点的地址,方便修改逻辑关系;
//链表尾删
void ListPopBack(ListNode* phead)
{
	assert(phead);

	//链表中只剩哨兵位的情况
	assert(phead->next != phead);

	//查找尾结点
	ListNode* tail = phead->prev;
	//保存尾结点的上一节点
	ListNode* tailprev = tail->prev;

   //尾删
	free(tail);
   //建立链接关系
	tailprev->next = phead;
	phead->prev = tailprev;

}
尾删函数测试
void Test3()
{
	ListNode* plist = ListInit();

	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	PrintList(plist);

	ListPopBack(plist);
	PrintList(plist);

	ListPopBack(plist);
	PrintList(plist);

	ListPopBack(plist);
	PrintList(plist);

}
int main()
{
	Test3();
	return 0;
}

运行结果:

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

双向链表头删

  • 链表中只剩哨兵位结点,此时链表为空,不再进行头删;
  • 头删前记录下一节点的地址,方便修改逻辑关系;
//链表头删
void ListPopFront(ListNode* phead)
{
	assert(phead);
	//只剩哨兵位,不再头删
	assert(phead->next != phead);

	//保存原先的首节点
	ListNode* head = phead->next;

	//保存首结点的下一节点
	ListNode* headnext = phead->next->next;

	//头删
	free(head);
	//建立链接关系
	headnext->prev = phead;
	phead->next = headnext;

}
头删函数测试
void Test4()
{
	ListNode* plist = ListInit();

	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	PrintList(plist);

	ListPopFront(plist);
	PrintList(plist);

	ListPopFront(plist);
	PrintList(plist);

	ListPopFront(plist);
	PrintList(plist);
}
int main()
{
	Test4();
	return 0;
}

运行结果:

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

双向链表查找

  • 循环遍历链表,从首节点开始遍历,以哨兵位头结点作为结束标志;
  • 根据数据域进行查找,找到返回数据域的结点地址,找不到返回空指针;
ListNode* ListFind(ListNode* phead, LTDataType x)
{
	assert(phead);

	//创建遍历指针
	ListNode* cur = phead->next;

	//遍历链表
	while (cur != phead)
	{
		if ((cur->data) == x)
		{
			//找到返回下标
			return cur;
		}
		cur = cur->next;
	}
	//没找到返回空指针
	return NULL;
}

双向链表pos位置前插

  • 前插时保存pos位置的前一个节点,方便修改逻辑关系;
  • 按照按照新结点的前址域指向谁,谁指向新结点,新结点的后址域指向谁,谁指向新结点进行链接;
void ListInsert(ListNode* pos, LTDataType x)
{
	assert(pos != NULL);

	//创建新结点
	ListNode* newnode = BuyListNode(x);
	//保存pos位置的前一个结点
	ListNode* posprev = pos->prev;

	//前插
	newnode->prev = posprev;
	posprev->next = newnode;
	newnode->next = pos;
	pos->prev = newnode;
}
插入函数测试
void Test5()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);

	int x = 0;
	printf("请输入查找的数值:");
	scanf("%d", &x);
	ListNode* pos = ListFind(plist, x);
	if (pos == NULL)
	{
		printf("要查找的值不存在\n");
		return;
	}
	//在查找到数值前插入100
	ListInsert(pos, 100);
	PrintList(plist);

}
int main()
{
	Test5();
	return 0;
}

运行结果:

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

 双向链表删除pos位置的结点

  • 链表删除pos位置处的结点前先保存前结点和后结点的地址,方便处理链接关系;
//双向链表删除pos位置
void ListEarse(ListNode* pos)
{
	assert(pos);

	//保存pos位置处的前一个和后一个结点;
	ListNode* posprev = pos->prev;
	ListNode* posnext = pos->next;
	//删除pos位置结点
	free(pos);

	//建立前后节点的链接关系
	posprev->next = posnext;
	posnext->prev = posprev;

}
删除函数测试
void Test6()
{
	ListNode* plist = ListInit();
	ListPushBack(plist, 1);
	ListPushBack(plist, 2);
	ListPushBack(plist, 3);
	ListPushBack(plist, 4);
	ListPushBack(plist, 5);
	PrintList(plist);

	int x = 0;
	printf("请输入删除的数值:");
	scanf("%d", &x);
	ListNode* pos = ListFind(plist, x);
	if (pos == NULL)
	{
		printf("要删除的值不存在\n");
		return;
	}
	
	ListEarse(pos);
	PrintList(plist);
}
int main()
{
	Test6();
	return 0;
}

运行结果:

数据结构之带头双向循环链表,数据结构,链表,c语言,算法

利用 ListInsert()函数改造头插尾插函数

  • 尾插函数改造版本
void Listpushback(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListInsert(phead, x);
}
  • 头插函数改造版本
void Listpushfront(ListNode* phead, LTDataType x)
{
	assert(phead);
	ListInsert(phead->next, x);
}

利用ListEarse()函数改造头删 尾删函数

  • 头删函数改造版本
void Listpopfront(ListNode* phead)
{
	assert(phead);
	//只剩哨兵位,不再头删
	assert(phead->next != phead);

	ListEarse(phead->next);
}
  • 尾删函数改造版本
void Listpopback(ListNode* phead)
{
	assert(phead);

	//链表中只剩哨兵位的情况
	assert(phead->next != phead);

	ListEarse(phead->prev);
}

计算双向链表长度

int ListLength(ListNode* phead)
{
	assert(phead);

	int size = 0;
	ListNode* cur = phead->next;
	while (cur != phead)
	{
		++size;
		cur = cur->next;
	}
	return size;
}

 文章来源地址https://www.toymoban.com/news/detail-713320.html

 

 

 

 

 

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

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

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

相关文章

  • 数据结构:链表基础OJ练习+带头双向循环链表的实现

    目录 一.leetcode剑指 Offer II 027. 回文链表 1.问题描述 2.问题分析与求解 (1) 快慢指针法定位链表的中间节点 (2) 将链表后半部分进行反转 附:递归法反转链表 (3) 双指针法判断链表是否回文 二.带头双向循环链表的实现 1.头文件 2.节点内存申请接口和链表初始化接口 3.链表的打

    2024年02月02日
    浏览(51)
  • 数据结构课程设计题目——链表综合算法设计、带头双向循环链表、插入、显示、删除、修改、排序

      课程设计题目1–链表综合算法设计   一、设计内容   已知简单的人事信息系统中职工记录包含职工编号(no)、职工姓名(name)、部门名称(depname)、职称(title)和工资数(salary)等信息(可以增加其他信息),设计并完成一个简单的人事信息管理系统,要求完成但不

    2024年02月08日
    浏览(64)
  • 【数据结构 -- C语言】 双向带头循环链表的实现

    目录 1、双向带头循环链表的介绍 2、双向带头循环链表的接口 3、接口实现 3.1 开辟结点 3.2 创建返回链表的头结点 3.3 判断链表是否为空 3.4 打印 3.5 双向链表查找 3.6 双向链表在pos的前面进行插入 3.6.1 头插 3.6.2 尾插 3.6.3 更新头插、尾插写法 3.7 双向链表删除pos位置的节点

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

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

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

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

    2024年01月17日
    浏览(61)
  • 【数据结构】带头双向循环链表---C语言版(单链表我们分手吧,不要再找我玩了!!!)

    在前面我们学习实现了单链表(无头单向不循环链表),这里我们引入带头双向循环链表 很明显这两种链表的结构截然不同,但都是作为链表最常使用链表结构 前者因其结构上的缺点而作为面试考题的常驻嘉宾,而且复杂麻烦 后者则是以结构最优著称,实现起来也是非常的

    2024年02月10日
    浏览(50)
  • 【数据结构】C语言实现双向链表(带头结点、循环)

    结点定义: 接口定义: 我们将申请结点的代码封装成函数,方便后续使用 由于是带头结点的双向链表,因此在使用链表前,我们需要对链表进行初始化。 遍历链表,值得说的是,带头结点的双向链表的循环结束条件是 cur != phead 尾插时,需要先找到尾结点,然后将新结点插

    2024年02月03日
    浏览(72)
  • 带头双向循环链表--数据结构

    😶‍🌫️😶‍🌫️😶‍🌫️😶‍🌫️Take your time ! 😶‍🌫️😶‍🌫️😶‍🌫️😶‍🌫️ 💥个人主页:🔥🔥🔥大魔王🔥🔥🔥 💥所属专栏:🔥魔王的修炼之路–数据结构🔥 如果你觉得这篇文章对你有帮助,请在文章结尾处留下你的 点赞 👍和 关注 💖,支持一

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

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

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

      🧑‍🎓 个人主页:简 料   🏆 所属专栏:C++   🏆 个人社区:越努力越幸运社区   🏆 简       介: 简料简料,简单有料~在校大学生一枚,专注C/C++/GO的干货分享,立志成为您的好帮手 ~ C/C++学习路线 (点击解锁) ❤️ C语言阶段(已结束) ❤️ 数据结构与算法(ing) ❤

    2024年01月16日
    浏览(78)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包