【数据结构 C语言版】第二篇 链表(1)单链表

写在前面

更新情况记录:

最近更新时间更新次数
2022/10/32

参考博客以及链接:
(非常感谢这些博主们的文章,将我的一些疑问得到解决。)

参考博客链接与书籍
《数据结构》陈越
总目录:

【数据结构】第二篇 链表(1)单链表


0.前言

本节专讲单链表。


1.链表的概念及结构

概念:链表是一种物理存储结构上的非连续、非结构的存储结构,数据元素的逻辑顺序是通过链表中指针链接次序实现的。

逻辑结构

image-20220930191333795

物理结构

image-20220930190716727

注意点:

1.从上图可以看出,链式结构在逻辑上是连续的,但是在物理上不一定连续

2.现实中的节点一般是从堆上申请出来的

3.从堆上申请的空间,是按照一定的策略来分配的,两次申请的空间可能连续,也可能不连续


2.带哨兵或者不带哨兵

image-20220930205534865


3.单链表的实现(无头、单向、非循环)

温馨小提示:其余链表形式的基础是单链表,先学会这个再写其他形式。

3.2单链表的结构代码
typedef int STDataType;
typedef struct SListNode
{
    SLTDataType data;
    struct SListNode* next;
}SListNode;
3.3单链表的接口
//1.动态申请一个节点
SListNode* BuySListNode(SLTDataType x);
//2.单链表打印
void SListPrint(SListNode* phead);
//3.单链表尾插
void SListPushBack(SListNode** pphead,SLTDataType x);
//4.单链表头插
void SListPushFront(SListNode** pphead,SLTDataType x);
//5.单链表的尾删
void SListPopFront(SListNode** pphead);
//6.单链表的头删
void SListPopBack(SListNode** pphead);
//7.单链表查找
SListNode* SListFind(SListNode* pphead,SLTDataType x);
//8.单链表在pos位置之后插入x
void SListInsertAfter(SListNode* pos,SLTDataType x);
//9.单链表删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
3.4单链表的实现

1.动态申请一个节点

image-20221001101707467

SLTNode* BuySListNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1); 
	}
	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

2.单链表打印

image-20221001102316952

void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

3.单链表尾插

image-20221001104002406

void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySListNode(x);
	SLTNode* tail = *pphead;
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	while (tail->next)
	{
		tail = tail->next;
	}
	tail->next = newnode;
}

4.单链表头插

image-20221001164908926

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySListNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

5.单链表的尾删

image-20221001170604197

void SListPopBack(SLTNode** pphead)
{
	assert(pphead);

	// 温柔的检查
	if (*pphead == NULL)
	{
		return;
	}

	// 暴力检查
	//assert(*pphead != NULL);


	// 1、一个节点
	// 2、多个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		// 找尾
		/*SLTNode* prev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
		prev = tail;
		tail = tail->next;
		}

		prev->next = NULL;
		free(tail);
		tail = NULL;*/

		SLTNode* tail = *pphead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}
}


6.单链表的头删

image-20221001170934437


void SListPopFront(SLTNode** pphead)
{
	assert(pphead);
	
	// 温柔的检查
	if (*pphead == NULL)
	{
		return;
	}

	// 暴力检查
	//assert(*pphead != NULL);

	SLTNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;
}

7.单链表查找

语言描述:不断迭代,假如有返回,如果没有就返回NULL。

SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data==x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

8.单链表在pos位置之后插入x

注:pos是通过SListFind函数找到的。

语言描述:将newnode指向 pos的下一个,然后将pos的下一个更新为newnode。

image-20221001172044696

void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

9.单链表删除pos位置之后的值

注:pos是通过SListFind函数找到的。

语言描述:定义一个cur指针,然后让cur指向pos的下一个,让pos下一个 指向 cur的下一个

将cur直接释放(free),保险一点可以让cur=NULL;

image-20221001172435256

void SListEraseAfter(SLTNode* pos)
{
	assert(pos);
	SLTNode* cur = pos->next;
	pos->next = cur->next;
	free(cur);
	cur = NULL;
}

4.关于带哨兵的链表

image-20221001173106227

为链表增加一个空的“头结点”,真正的元素链接在这个空结点之后。

4.1 带头结点的作用

为了可以方便修改第一个结点的数据,可以在链表的第一个结点前面,再增加一个空的“头结点”,也叫哨兵结点,这样做的好处是,无论在哪里插入(比如头)或者删除,phead的值一直指向固定的空结点,不会改变。

4.2 带头结点插入与删除的实现

1.单链表在pos位置之后插入x

注:这样就能头插了。查找的开始是从哨兵结点开始的,这样pos才能指向哨兵,实现头插的功能,下面的删除也一样。

void SListInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = BuySListNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

2.单链表删除pos位置之后的值

注:这样就能实现头删了。

void SListEraseAfter(SLTNode* pos)
{
	assert(pos);
	SLTNode* cur = pos->next;
	pos->next = cur->next;
	free(cur);
	cur = NULL;
}
4.3 解题技巧

如果写题目的时候遇到不带头的链表,可以自己额外申请一个结点作为哨兵结点,实现头插与头删的简化,结束时应该释放头结点。详细请看《数据结构(基础C语言实现)题目之线性表》。

5.单链表题目与解题技巧

这部分将写在《数据结构(基础C语言实现)题目之线性表》一文中。

链接(待更新中):

  • 11
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 14
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 14
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

潮.eth

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值