【数据结构】C语言实现单链表(带头结点)

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


一、带头结点单链表

Single linked list with leading nodes

关于不带头结点的单链表:不带头结点的单链表

二、结点与接口定义

结点定义:

typedef int SLLWDataType;
typedef struct SLLWNode
{
	SLLWDataType data;
	struct SLLWNode* next;
}SLLWNode;

接口定义:

void SLLWInit(SLLWNode** phead);

void SLLWPrint(SLLWNode* phead);

void SLLWPushFront(SLLWNode* phead, SLLWDataType x);
void SLLWPushBack(SLLWNode* phead, SLLWDataType x);

void SLLWPopFront(SLLWNode* phead);
void SLLWPopBack(SLLWNode* phead);

SLLWNode* SLLWFind(SLLWNode* phead, SLLWDataType x);

// 在pos之前插入
void SLLWInsert(SLLWNode* phead, SLLWNode* pos, SLLWDataType x);
// 在pos之后插入
void SLLWInsertAfter(SLLWNode* pos, SLLWDataType x);

// 删除pos位置的值
void SLLWErase(SLLWNode* phead, SLLWNode* pos);
// 删除pos位置后面的值
void SLLWEraseAfter(SLLWNode* pos);

// 销毁
void SLLWDestroy(SLLWNode* phead);

三、接口实现

3.1 Init初始化与申请节点

初始化需要申请头结点,让头指针指向空的头结点。

void SLLWInit(SLLWNode** phead)
{
	assert(phead);
	*phead = SLLWMalloc((SLLWDataType)230504);
}

将申请结点的代码进行封装:

SLLWNode* SLLWMalloc(SLLWDataType x)
{
	SLLWNode* newnode = (SLLWNode*)malloc(sizeof(SLLWNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

3.2 打印

需要越过头结点

void SLLWPrint(SLLWNode* phead)
{
	assert(phead);
	SLLWNode* cur = phead->next;
	while (cur)
	{
		print("[%d]->", cur->data);
		cur = cur->next;
	}
	print("NULL\n");
}

3.3 尾插PushBack

找到尾结点,然后插入到尾结点的后面。

void SLLWPushBack(SLLWNode* phead, SLLWDataType x)
{
	assert(phead);

	// Find the tail node 
	SLLWNode* tail = phead;
	while (tail->next)
	{
		tail = tail->next;
	}

	// insert it after the tail node
	SLLWNode* newnode = SLLWMalloc(x);
	tail->next = newnode;
}

对比不带头结点的单链表的尾插:

void SLLPushBack(SLLNode** pphead, SLLDataType x)
{
	assert(pphead); // 链表为空,pphead也不为空

	SLLNode* newnode = CreateSLLNode(x);
	
	if (*pphead == NULL)
	{
		// 1、空链表
		*pphead = newnode;
	}
	else
	{
		// 2、非空链表
		SLLNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newnode;
	}
}

我们发现相比于不带头结点的单链表尾插,带头结点的尾插的代码要更简洁,不用判断是否是空链表对插入第一个元素的单独处理。

此外,在函数的接口定义时,不带头结点的单链表还要传入二级指针,让函数外的头指针指向第一个节点,这也同样是对插入第一个元素的单独处理,而带头结点的单链表不用这样做,因为带头结点的链表在初始化时就有头结点,函数外的头指针始终指向头结点。

3.4 头插PushFront

void SLLWPushFront(SLLWNode* phead, SLLWDataType x)
{
	assert(phead);
	SLLWNode* newnode = SLLWMalloc(x);
	newnode->next = phead->next;
	phead->next = newnode;
}

3.5 头删PopFront

无论是头删还是尾删,链表中至少有一个数据元素才能进行删除:

void SLLWPopFront(SLLWNode* phead)
{
	assert(phead);
	assert(phead->next); // At least one element in the linked list can be deleted
	
	SLLWNode* del = phead->next;
	phead->next = del->next;
	free(del);
}

3.6 尾删PopBack

同头删一样,链表中要求至少有一个数据元素。

找到尾结点的前一个节点,然后将尾结点删除,其前一个节点的next域置空。

void SLLWPopBack(SLLWNode* phead)
{
	assert(phead);
	assert(phead->next);

	// Find the node before the tail node
	SLLWNode* pretail = phead;
	while (pretail->next->next)
	{
		pretail = pretail->next;
	}

    // Delete the tail node
	free(pretail->next);
	pretail->next = NULL;
}

对比不带头结点的单链表,还要对链表中只有一个元素时的PopBack进行单独处理,因为对头的处理。

3.7 查找Find

遍历链表,找到返回该节点,找不到返回空。

SLLWNode* SLLWFind(SLLWNode* phead, SLLWDataType x)
{
	assert(phead);

	SLLWNode* cur = phead->next;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}

	return NULL;
}

3.8 插入insert-在指定结点前插入

找到该结点前一个结点,插入到其后面。

如果pos节点没找到,在下面while循环遍历过程中,会产生空指针异常,直接报错。

void SLLWInsert(SLLWNode* phead, SLLWNode* pos, SLLWDataType x)
{
	assert(phead);
	assert(pos);

	// Find the node before the pos node
	SLLWNode* prev = phead;
	// The pos node is not found, a null pointer exception is raised
	while (prev->next != pos)
	{
		prev = prev->next;
	}

	// The pos node is found
	SLLWNode* newnode = SLLWMalloc(x);
	prev->next = newnode;
	newnode->next = pos;
}

对比不带头结点的单链表的在pos之前进行插入,还要单独判断pos是否是第一个元素节点。而带头结点的单链表在实现时,不需要判断。

另一种偷梁换柱方法:

void SLLWInsert1(SLLWNode* phead, SLLWNode* pos, SLLWDataType x)
{
	assert(phead);
	assert(pos);

	// The consumer calls find first, then insert, 
	// so pos must be in the linked list

	SLLWNode* newnode = SLLWMalloc(pos->data);
	newnode->next = pos->next;
	pos->data = x;
	pos->next = newnode;
}

这里不需要判断pos是否在链表中,因为从使用者的角度来看,一般是先Find找到x所在的pos,然后Insert插入到找到的pos的位置之前。所以pos必在链表中。

3.9 指定pos后插

void SLLWInsertAfter(SLLWNode* pos, SLLWDataType x)
{
	assert(pos);

	SLLWNode* newnode = SLLWMalloc(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

3.10 删除Erase-在指定pos处插入

删除时,链表中至少有一个元素结点。

找到pos的前一个节点,然后将pos删除即可。

void SLLWErase(SLLWNode* phead, SLLWNode* pos)
{
	assert(phead);
	assert(phead->next);
	assert(pos);

	// Find the node before the pos node
	SLLWNode* prev = phead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	// The pos node is not found, a null pointer exception is raised
	
	// The pos node is found, delete it
	prev->next = pos->next;
	free(pos);
}

对比不带头结点的单链表,删除时还需要对只有一个元素时的链表进行单独处理。

3.11 指定pos后删

void SLLWEraseAfter(SLLWNode* pos)
{
	assert(pos);

	SLLWNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

3.12 销毁Destroy

头结点也要进行销毁。

void SLLWDestroy(SLLWNode* phead)
{
	assert(phead);

	SLLWNode* cur = phead;
	while (cur)
	{
		SLLWNode* del = cur;
		cur = cur->next;
		free(del);
	}
}

源码

gitee-SingleLinkedListWithLeadingNode


总结

带头结点的单链表,只要跳过头结点就变成了不带头结点的单链表,链表永远有一个头结点,所以代码写起来更简洁,特别是尾插PushBack、尾删PopBack、插入insert、删除Erase的代码。

事实上也确实如此,在实际的链表OJ题中,题目的要求是不带头结点的单链表,我们人为加上头结点,然后将逻辑代码写完后,再将添加的头结点删除,这样代码的逻辑会变得更简单。如 链表分割 、合并两个有序链表文章来源地址https://www.toymoban.com/news/detail-433526.html

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

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

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

相关文章

  • 追梦之旅【数据结构篇】——详解C语言动态实现带头结点的双向循环链表结构

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

    2024年01月17日
    浏览(38)
  • 【数据结构与算法分析】使用C语言实现队列的两种(带头结点与不带头结点)链式存储,并且给出一种循环队列的设计思想

      当我们编写程序时,经常需要处理各种数据结构。队列是一种常见的数据结构,它有着广泛的应用场景。队列的基本操作包括入队和出队,应用于模拟等待队列、消息队列、计算机缓存等场合。   在实际编程中,我们可以用不同的数据结构来实现队列。本文主要介绍了

    2024年02月08日
    浏览(47)
  • 『初阶数据结构 • C语言』⑨ - 基于结点的数据结构——链表(单链表&&双向循环链表)附完整源码

      本章内容 1.什么是链表 2.链表常见几种形式 3.无头单向非循环链表的实现 3.1结点结构的定义 3.2函数接口的实现 3.2.1尾插 3.2.2尾删 4. 带头双向循环链表的实现 4.1结点结构的定义 4.2函数接口的实现 5.两种链表的差异 ①尾插与尾删的时间复杂度 ②头插与头删的时间复杂度 ③

    2024年02月16日
    浏览(60)
  • 数据结构之带头节点的单链表增删改查操作实现

            单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。       单链表的各个数据元素在物理上可以是离散存放的,每个结点除了存放数据元素外,还要存储指向下一个节点的指针。而顺序表是连续存放的,每个结点中只存放数据元

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

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

    2024年02月10日
    浏览(30)
  • 数据结构——单链表(不带头节点)

    链表是一种物理存储结构上非联系,非顺序的存储结构,但数据元素的逻辑顺序是通过链表中的指针链接实现的 逻辑结构: 实际物理结构: 每个链表节点都有自己的地址,链表的物理结构实际上是前一个结点保存着下一个结点的地址 所以从上面图中可以看出: 1.链表在逻辑

    2024年02月06日
    浏览(36)
  • 带头结点和尾指针的循环单链表(C语言)

    头结点的定义 头结点是链表中的 第一个结点 ,其 数据域 一般无意义(有时可存放链表长度等)。 头结点目的 统一链表的操作 。使得 空表 时的操作不用 特殊处理 ,简化了链表的操作。 尾指针的定义 尾指针是 指向链表尾结点 的指针。 尾指针的作用 用来 找到整个链表

    2024年02月13日
    浏览(33)
  • 7-数据结构-(带头节点)单链表的增删改查

    问题:          单链表带头结点的创建以及输出,以及带与不带头节点的区别 思路: 单链表,逻辑上是线性结构,由许多单链表结点,串成一串。其单链表结构体中,数据项由data数据域和结点指针域。 带头节点是为了使在空表时的操作更统一。如果不带头节点,空表插

    2024年02月14日
    浏览(31)
  • 【数据结构 -- 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日
    浏览(43)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包