C语言实现单链表 && 双向链表

这篇博客详细介绍了如何使用C语言实现单链表和双向循环链表,包括它们的概念、特点、基本操作接口、结构定义、动态申请节点、增删查改操作。特别讨论了为什么在某些操作中需要使用二级指针,并提供了完整的代码实现。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


一、单链表

1.单链表的概念

链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接在一起的
链表用节点的方式存储了数据以及其指向的下一个节点的地址,分别为存储有效数据数据的数据域,存储下一节点的地址的指针域。单链表只有一个指针域,而双向链表有两个。

2.单链表与顺序表的比较

结构上:
顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,即顺序表相邻元素之间的物理位置和逻辑位置皆相邻
链表则是用一段物理地址非连续的存储结构存储数据,即链表存储结构逻辑上是相邻的,但物理上不相邻

在这里插入图片描述
顺序表的优缺点:
优点:
1.空间连续,支持随机访问。
缺点:
1.中间或前面部分的插入删除时间复杂度O(N);
2.增容代价比较大;
单链表的优缺点:
优点:
1.进行头部插入和头部删除的时间复杂度为O(1);
2.没有增容问题,每次插入一个开辟一个空间;
缺点:
1.以节点为单位存储,不支持随机访问;

3.单链表的基本操作接口

以下实现的单链表皆无哨兵位头节点。

//单链表基本操作的接口
//动态申请一个节点
SListNode* BuySListNode(SLDataType val);
//单链表的打印
void SListPrint(SListNode* phead);
//单链表的尾插
void SListPushBack(SListNode** pphead, SLDataType val);
//单链表的尾删
void SListPopBack(SListNode** pphead);
//单链表的头插
void SListPushFront(SListNode** pphead, SLDataType val);
//单链表的头删
void SListPopFront(SListNode** pphead);
//单链表的查找
SListNode* SListFind(SListNode* phead, SLDataType val);
//单链表在pos位置之后插入
void SListInsertAfter(SListNode* pos, SLDataType val);
//单链表删除pos位置之后的节点
void SListEraseAfter(SListNode* pos);
//单链表的销毁
void SListDestory(SListNode** pphead);

4.单链表的结构定义

typedef int SLDataType;
typedef struct SListNode
{
   
	struct SListNode* next;//存储下一个节点的地址
	SLDataType val;//存储有效数据
}SListNode;

5.动态申请一个节点

//动态申请一个节点
SListNode* BuySListNode(SLDataType val)
{
   
	//申请节点
	SListNode* new_node = (SListNode*)malloc(sizeof(SListNode));
	assert(new_node);//判断节点是否申请失败
	//对数据域和指针域赋值
	new_node->next = NULL;
	new_node->val = val;
}

6.单链表的打印

//单链表的打印
void SListPrint(SListNode* phead)
{
   
	SListNode* cur = phead;
	while (cur != NULL)
	{
   
		printf("%d->", cur->val);
		cur = cur->next;
	}
	printf("NULL\n");
}

7.为什么要传送二级指针???

为什么要传送二级指针,是无头单链表的最难点,这考察你对指针和结构体的知识掌握是否熟练。
在部分情况下,如在链表为空的情况下插入节点,在链表只有一个节点时删除节点,都需要你改变指向头节点的指针的指向,但由于函数的形参的只是实参的一份临时拷贝,改变形参不会影响实参的改变,所以你需要传送实参的指针,也就是头节点的指针的指针。
传送一级指针的作用:
如果你传送的是节点的指针,那么你可以修改节点中的指针域和数据域,但无法修改你传送的一级指针的指向;如:打印链表,在pos位置后插入数据;这些都不需要你修改头节点的指针的指向,所以使用一级指针便够了。
总结:
传送一级指针,允许你修改节点中的内容。
传送二级指针,允许你修改指向节点的指针的指向,并且也允许你修改节点中的内容。

8.单链表的增删查改

8.1单链表的尾插

注意:
无头单链表的尾插需要划分两种情况:1.链表为空;2.链表不为空;
当链表为空时,我们需要修改头节点指针的指向,让其指向新开辟的节点,所以需要使用二级指针;
当链表不为空时,我们需要遍历链表,直到链表的尾节点,再让其指针域指向新开辟的节点,此操作便与二级指针无关。

//单链表的尾插
void SListPushBack(SListNode** pphead, SLDataType val)
{
   
	assert(pphead);//pphead不可能为空
	
	//创建节点
	SListNode* new_node = BuySListNode(val);
	//尾插数据
	//1.链表为空
	if ((*pphead) == NULL)
	{
   
		//修改头节点的指向
		*pphead = new_node;
	}
	else
	{
   
		//2.链表不为空
		SListNode* tail = *pphead;
		//寻找尾节点
		while (tail->next != NULL)
		{
   
			tail = tail->next;
		}
		//插入数据
		tail->next = new_node;
	}
}

8.2 单链表的尾删

注意:
无头单链表的尾删划分为两种情况:1.链表中只有一个节点;2.链表中有两个及以上节点;
当链表只有一个节点时,先释放掉唯一的节点,再将头节点的指针指向NULL,此时需要使用二级指针,才能将外部实参同样修改为指向NULL;
当链表有两个及两个以上的节点时,只需要遍历链表,寻找到尾节点的前一个节点,然后释放掉尾节点,让尾节点的前一个节点的指针域指向NULL

void SListPopBack(SListNode** pphead)
{
   
	assert(pphead);
	assert(*pphead != NULL);//确保有节点可删

	//删除节点
	//链表只有一个节点
	if ((*pphead)->next == NULL)
	{
   
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
   
		//链表有两个及以上节点
		//寻找尾节点的前一个节点
		SListNode* cur = *pphead;
		while (cur->next->next != NULL)
		{
   
			cur = cur->next;
		}
		//释放尾节点,让尾节点的前一个节点指针域指向NULL
		SListNode* tail = cur->next;
		cur->next = NULL;
		free(tail);
	}
}

8.3 单链表的头插

注意:在无头单链表中头插节点,任何情况下都需要改变头节点指针的指向,让其指向新的头节点;
在这里插入图片描述

//单链表的头插
void SListPushFront(SListNode** pphead, SLDataType val)
{
   
	assert(pphead);

	//创建新节点
	SListNode* new_node = BuySListNode(val);
	assert(new_node);//检查申请是否成功
	
	SListNode* next = *pphead;//记录原链表的头节点
	//在头节点前插入新节点
	*pphead = new_node;//让头节点的指针指向新节点
	new_node->next = next;
}

8.4 单链表的头删

注意:单链表的头删与头插一样,任何情况下都需要改变头节点指针的指向;让头节点指针指向原链表中头节点的下一个节点。
在这里插入图片描述

//单链表的头删
void SListPopFront(SListNode** pphead)
{
   
	assert(pphead);
	assert(*pphead != NULL);//确保有节点可删

	SListNode* next = (*pphead)->next;//记录下头节点的下一个节点
	free(*pphead);
	*pphead = next;
}

8.5 单链表的查找

单链表的查找只需要依次遍历链表,找到值相等的节点便可访问该节点,找不到则返回NULL;
由于不需要改变头节点的指向,所以只需要传递一级指针。

//单链表的查找
SListNode* SListFind(SListNode* phead, SLDataType val)
{
   
	assert(phead);
	
	SListNode* pos = phead;
	while (pos != NULL)
	{
   
		if (pos->val == val)
		{
   
			return pos;
		}
		pos = pos->next;
	}
	return 
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值