【数据结构】C语言实现双向链表(带头结点、循环)

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


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

【数据结构】C语言实现双向链表(带头结点、循环)

二、结点与接口定义

结点定义:

typedef int ListDataType;
typedef struct LinkedListNode
{
	struct LinkedListNode* next;
	struct LinkedListNode* prev;
	ListDataType data;
}LinkedListNode;

接口定义:

LinkedListNode* LinkedListInit();
void LinkedListPrint(LinkedListNode* phead);

bool LinkedListEmpty(LinkedListNode* phead);
void LinkedListPushBack(LinkedListNode* phead, ListDataType x);
void LinkedListPushFront(LinkedListNode* phead, ListDataType x);
void LinkedListPopBack(LinkedListNode* phead);
void LinkedListPopFront(LinkedListNode* phead);

LinkedListNode* LinkedListFind(LinkedListNode* phead, ListDataType x);

// 在pos之前插入
void LinkedListInsert(LinkedListNode* pos, ListDataType x);
// 删除pos位置的值
void LinkedListErase(LinkedListNode* pos);

void LinkedListDestroy(LinkedListNode* phead);

三、实现

3.1 申请节点

我们将申请结点的代码封装成函数,方便后续使用

LinkedListNode* CreateLinkedListNode(ListDataType x)
{
	LinkedListNode* newnode = (LinkedListNode*)malloc(sizeof(LinkedListNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}

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

3.2 初始化

由于是带头结点的双向链表,因此在使用链表前,我们需要对链表进行初始化。

LinkedListNode* LinkedListInit()
{
	LinkedListNode* phead = CreateLinkedListNode(230510);
	phead->next = phead;
	phead->prev = phead;

	return phead;
}

3.3 打印

遍历链表,值得说的是,带头结点的双向链表的循环结束条件是 cur != phead

void LinkedListPrint(LinkedListNode* phead)
{
	assert(phead);

	LinkedListNode* cur = phead->next;

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

3.4 尾插

尾插时,需要先找到尾结点,然后将新结点插入到尾结点后面。

void LinkedListPushBack(LinkedListNode* phead, ListDataType x)
{
	assert(phead);

	// 1.找到尾结点
	LinkedListNode* tail = phead->prev;

	// 2.插入到尾结点后面
	LinkedListNode* newnode = CreateLinkedListNode(x);
	tail->next = newnode;
	newnode->prev = tail;
	newnode->next = phead;
	phead->prev = newnode;
}

3.5 头插

第一种写法,要注意现将newnode后面的结点进行链接,然后再讲newnode链接到phead后面。

void LinkedListPushFront(LinkedListNode* phead, ListDataType x)
{
	assert(phead);

	LinkedListNode* newnode = CreateLinkedListNode(x);
	// 与原来的第一个数据结点链接
	newnode->next = phead->next;
	phead->next->prev = newnode; // newnode->next->prev = newnode;

	// newnode变为新的第一个数据结点
	phead->next = newnode;
	newnode->prev = phead;
}

第二种写法(推荐写法),我们使用变量记录phead的next,记为first,这样newnode插入到phead和first之间,这样逻辑比较清楚。

void LinkedListPushFront(LinkedListNode* phead, ListDataType x)
{
	assert(phead);

	LinkedListNode* newnode = CreateLinkedListNode(x);
	// 用变量记录第一个结点
	LinkedListNode* first = phead->next;

	// 与原来的第一个数据结点链接
	newnode->next = first;
	first->prev = newnode; 

	// newnode变为新的第一个数据结点
	phead->next = newnode;
	newnode->prev = phead;
}

3.6 尾删

phead的prev是尾tail,tail的prev是tailPrev。

void LinkedListPopBack(LinkedListNode* phead)
{
	assert(phead);

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

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

上面代码的问题是链表为空的情况报错,于是我们在该函数内部对空链表进行断言:

void LinkedListPopBack(LinkedListNode* phead)
{
	assert(phead);
	assert(!LinkedListEmpty(phead)); // 删除时链表不能为空

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

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

3.7 判断链表为空断言

判断链表为空逻辑:

bool LinkedListEmpty(LinkedListNode* phead)
{
	assert(phead);

	return phead->next == phead;
}

使用链表为空的断言:

assert(!LinkedListEmpty(phead)); // 链表为空时error

3.8 头删

头删时需要将第一个结点删除,很容易便想到以下代码:

void LinkedListPopFront(LinkedListNode* phead)
{
	assert(phead);
	assert(!LinkedListEmpty(phead)); // 删除时链表不能为空

	LinkedListNode* next = phead->next;

	phead->next = next->next;
	phead->next->prev = phead; // next->next->prev = phead;
	free(next); 
}

为了提高可读性,推荐使用以下代码,定义first和second两个变量指向第一个和第二个:

void LinkedListPopFront(LinkedListNode* phead)
{
	assert(phead);
	assert(!LinkedListEmpty(phead)); // 删除时链表不能为空

	LinkedListNode* first = phead->next;
	LinkedListNode* second = first->next;

	phead->next = second;
	second->prev = phead;
	free(first);
}

3.9 查找find

查找的本质就是遍历链表

LinkedListNode* LinkedListFind(LinkedListNode* phead, ListDataType x)
{
	assert(phead);
	
	LinkedListNode* cur = phead->next;
	while (cur != phead)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

3.10 插入insert-在pos之前插入

pos的来源一般是find的结果

void LinkedListInsert(LinkedListNode* pos, ListDataType x)
{
	assert(pos);

	LinkedListNode* prev = pos->prev;
	LinkedListNode* newnode = CreateLinkedListNode(x);

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

3.11 头插尾插复用insert

有了上面的insert在任意位置插入,我们可以修改尾插代码:

void LinkedListPushBack(LinkedListNode* phead, ListDataType x)
{
	assert(phead);

	// 在phead之前插入,也就是尾插
	LinkedListInsert(phead, x);
}

同理也可以修改头插代码:

void LinkedListPushFront(LinkedListNode* phead, ListDataType x)
{
	assert(phead);

	LinkedListInsert(phead->next, x);
}

3.12 删除erase-删除pos位置

同insert一样,pos也应该是调用者通过find返回的结果。

void LinkedListErase(LinkedListNode* pos)
{
	assert(pos);

	LinkedListNode* posPrev = pos->prev;
	LinkedListNode* posNext = pos->next;

	posPrev->next = posNext;
	posNext->prev = posPrev;
	free(pos);
}

3.13 头删尾删复用erase

有了上面的erase在任意位置删除,我们可以修改尾删的代码:

void LinkedListPopBack(LinkedListNode* phead)
{
	assert(phead);
	assert(!LinkedListEmpty(phead));
	LinkedListErase(phead->prev);
}

同理也可以修改头删的代码:

void LinkedListPopFront(LinkedListNode* phead)
{
	assert(phead);
	assert(!LinkedListEmpty(phead));

	LinkedListErase(phead->next);
}

3.14 销毁destroy

记得释放头结点phead:

void LinkedListDestroy(LinkedListNode* phead)
{
	assert(phead);

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

	free(phead);
}

源码

gitee-LinkedList

总结

带头结点、双向、循环链表的实现都非常的简单,需要注意判空条件与遍历终止的条件。

在代码写法上,对于某个节点的前一个或后一个的问题,我们最好分别使用变量去记录,这样代码的逻辑更清晰,可读性更高。例如尾插时的tail,尾删时的tail和tailPrev,以及头删时的first与second,erase中的posPrev与posNext,这些变量的使用,提高了代码的可读性。文章来源地址https://www.toymoban.com/news/detail-438544.html

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

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

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

相关文章

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

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

    2024年02月02日
    浏览(36)
  • 玩转带头双向链表——【数据结构】

    W...Y的主页 今天我们接着来说数据结构——带头双向链表 目录 带头双向链表的实现 结构体的创建 初始化兵创建哨兵节点 释放链表所以内容  打印链表函数 尾插 尾删 头插 ​编辑 头删 计数函数实现 查找数据相应位置函数 在pos位置之前插入  在pos位置删除  顺序表与链表的

    2024年02月14日
    浏览(27)
  • 数据结构之带头双向链表(易学版)

    目录 1.问题引入 2.结构实现 2.3.1接口实现 2.3.2函数实现 3.总结 ,又和大家见面了,今天要给大家分享的是双链表的知识,跟着我的脚步,包学包会哦~ 规矩不乱,先赞后看! ps:(孙权劝学) 前期学习了单链表,我们尝到了它的甜头,但是容易越界访问这一个问题也是时有出

    2024年03月22日
    浏览(33)
  • 【数据结构 -- 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日
    浏览(37)
  • 数据结构 - 链表详解(二)—— 带头双向循环链表

    链表的结构一共有 八种 :带头单向循环链表、带头单向非循环链表、带头双向循环链表、带头双向非循环链表、无头单向循环链表、无头单向非循环链表、无头双向循环链表、无头双向非循环链表。 今天我们来详解带头双向循环链表 带头双向循环链表是一种数据结构,它通

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

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

    2023年04月12日
    浏览(33)
  • 【数据结构】链表:带头双向循环链表的增删查改

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

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

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

    2024年01月17日
    浏览(36)
  • 双向链表--C语言实现数据结构

    本期带大家一起用C语言实现双向链表🌈🌈🌈 链表是一种物理存储单元上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的;简单来说,线性表的链式存储结构生成的表,称作“链表”。 每个元素本身由两部分组成: 1、本身的信息,称

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

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

    2024年02月08日
    浏览(50)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包