【数据结构】单链表---C语言版(全网最最最最细!小白必必必必看!!!有图有真相!)

本文详细介绍了单链表与顺序表在存储形式、空间和时间上的区别,并提供了C语言实现的单链表接口,包括创建、打印、释放、插入、删除、查找和修改等操作。同时,文章通过示例代码展示了这些接口的使用,帮助读者理解和掌握单链表的基本操作。
摘要由CSDN通过智能技术生成

在这里插入图片描述


在这里插入图片描述

🐸一、前言

终于放假啦!🤩🤩停更了两个月,在假期要把欠下的补回来&有规律的学习!🤓
本篇文章来自《数据结构与算法》 专栏,本篇的内容是单链表的学习,也是数据结构的基础,希望烙铁们可以理解消化哦🥰!!!

🐸二、单链表与顺序表的区别

在这里插入图片描述

🐵1.存储形式上的区别

顺序表 在物理上和逻辑上都是连续的
单链表 在物理上是不连续的,在逻辑上是连续的

在这里插入图片描述

🐵2.空间上的区别

顺序表一般有固定的空间大小,当空间不够时需要进行扩容,扩容时往往不能准确知道需要扩容的空间大小,很容易会造成空间浪费例如当前容量为100,满了以后增容到200,我们再继续插入了5个数据,后面没有数据插入了,那么就浪费了95个数据空间。

单链表 用一个空间申请一个空间,相比顺序表不那么浪费空间(并不是完全不浪费)

总结在存储数据时,如果知道具体空间大小,就用顺序表进行存储,不知道具体空间大小,就用单链表

🐵3.时间上的区别

1️⃣随机插入、删除元素的时间复杂度:

顺序表时间复杂度为 O(n),因为其空间是连续的,在某个位置插入时,这个位置后面的元素都要往后挪动位置;
单链表时间复杂度为 O(1),只需要改变前驱元素及插入删除元素的指向;

2️⃣查找指定元素,修改指定位置元素的时间复杂度:

顺序表时间复杂度为 O(1),依靠下标直接可以找到指定元素,对其操作;
单链表时间复杂度为 O(n),需要从链表头结点往下遍历;

🐸三、单链表详解

🍎创建单链表

🥰这里先创建三个文件:
1️⃣:SList.h文件用于函数的声明
2️⃣:SList.c文件用于函数的定义
3️⃣:Test.c文件用于测试函数
建立三个文件的目的: 将单链表作为一个项目来进行编写,方便我们的学习与观察。

⭕接口1:定义结构体(SLTNode)

🥰请看代码与注释👇

//自定义类型
typedef int SLTDataType;

//创建单链表
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

⭕接口2:打印(SLTPrint)

🥰请看代码与注释👇

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

⭕接口3:释放(SLTDestroy)

🥰请看代码与注释👇

//释放
void SLTDestroy(SLTNode** pphead)
{
	assert(pphead);

	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

⭕接口4:创建新结点(BuyLTNode)

🥰请看代码与注释👇

//创建新结点
SLTNode* BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("mallic fail");
		return;
	}

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

	return newnode;
}

⭕接口5:头插(SLTPushFront)

在这里插入图片描述
🥰请看代码与注释👇

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{ 
	assert(pphead);   //链表为空,pphead也不为空,因为他是头指针plist的地址
	                  // *pphead 不能断言,链表为空,也需要能插入
	SLTNode* newnode = BuyLTNode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

⭕接口6:尾插(SLTPushBack)

🚨主要思想就是 找尾,要注意尾插有两种情况:空链表和非空链表
很多烙铁容易找错,这里要找的是尾结点,而不是
尾结点(tail):next为空的
在这里插入图片描述
🥰请看代码与注释👇

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuyLTNode(x);

	//1.空链表
	//2.非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;    //改变的结构体的指针plist(用结构体指针的地址)
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
	                           
		tail->next = newnode;    //改变的结构体尾结点
	}
}

⭕接口7:头删(SLTPopFront)

🚨要注意头删有三种情况:1.没有结点(空链表)、2.一个结点、3.多个结点
链表为空不能头删,所以要进行assert断言
在这里插入图片描述
🥰请看代码与注释👇

//头删
void SLTPopFront(SLTNode** pphead)
{
	//没有结点(空链表)
	assert(*pphead);  //链表为空,不能头删
	assert(pphead);
	
	//一个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多个结点
	else
	{
		SLTNode* cur = *pphead;
		*pphead = cur->next;
		free(cur);
	}
}

可以有更简洁高效的写法👇
🥰请看代码与注释👇

//头删
void SLTPopFront(SLTNode** pphead)
{
	//没有结点(空链表)
	assert(*pphead);
	assert(pphead);

	//一个结点
	//多个结点
	SLTNode* cur = *pphead;
	*pphead = (*pphead)->next;
	free(cur);
}

⭕接口8:尾删(SLTPopBack)

🚨要注意尾删同样有三种情况:1.没有结点(空链表)、2.一个结点、3.多个结点
链表为空不能尾删,所以要进行assert断言
在这里插入图片描述
🥰请看代码与注释👇

//尾删
void SLTPopBack(SLTNode** pphead)
{
	//没有结点(空链表)
	assert(*pphead);
	assert(pphead);

	//一个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多个结点
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

⭕接口9:查找(SLTFind)

通过遍历进行查找,通常与修改结合联系使用
🥰请看代码与注释👇

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);

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

	return NULL;
}

⭕接口10:修改(SLTModify)

🥰请看代码与注释👇

//修改
void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
{
	assert(phead);
	assert(pos);
	pos->data = x;
}

⭕接口11:在pos位置之前插入数据(SLTInsert)

🥰请看代码与注释👇

//在pos位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);  //防止给的位置为 空

	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

⭕接口12:在pos位置之后插入数据(SLTInsertAfter)

🥰请看代码与注释👇

//在pos位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);  //防止给的位置为 空
	
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

⭕接口13:删除pos位置的值(SLTErase)

在这里插入图片描述

🥰请看代码与注释👇

//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);  //防止给的位置为 空

	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

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

⭕接口14:删除pos位置之后的值(SLTEraseAfter)

在这里插入图片描述

🥰请看代码与注释👇

//删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

🐸四、完整代码

🥝SList.h

#pragma once

#include<stdio.h>
#include<stdlib.h>
#include<assert.h>


//自定义类型
typedef int SLTDataType;

//创建单链表
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;


//打印
void SLTPrint(SLTNode* phead);
//释放
void SLTDestroy(SLTNode** pphead);
//创建新结点
SLTNode* BuyLTNode(SLTDataType x);
//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
//头删
void SLTPopFront(SLTNode** pphead);
//尾删
void SLTPopBack(SLTNode** pphead);
//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
//修改
void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x);
//在pos位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
//在pos位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos);
//删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos);

🥝SList.c

#include"SList.h"

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

//释放
void SLTDestroy(SLTNode** pphead)
{
	assert(pphead);

	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

//创建新结点
SLTNode* BuyLTNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("mallic fail");
		return;
	}

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

	return newnode;
}

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{ 
	assert(pphead);   //链表为空,pphead也不为空,因为他是头指针plist的地址
	                  // *pphead 不能断言,链表为空,也需要能插入
	SLTNode* newnode = BuyLTNode(x);

	newnode->next = *pphead;
	*pphead = newnode;
}

//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuyLTNode(x);

	//1.空链表
	//2.非空链表
	if (*pphead == NULL)
	{
		*pphead = newnode;    //改变的结构体的指针plist(用结构体指针的地址)
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
	                           
		tail->next = newnode;    //改变的结构体尾结点
	}
}

//头删
void SLTPopFront(SLTNode** pphead)
{
	//没有结点(空链表)
	assert(*pphead);  //链表为空,不能头删
	assert(pphead);
	
	//一个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多个结点
	else
	{
		SLTNode* cur = *pphead;
		*pphead = cur->next;
		free(cur);
	}
}

//头删
//void SLTPopFront(SLTNode** pphead)
//{
//	//没有结点(空链表)
//	assert(*pphead);
//	assert(pphead);
//
//	//一个结点
//	//多个结点
//	SLTNode* cur = *pphead;
//	*pphead = (*pphead)->next;
//	free(cur);
//}

//尾删
void SLTPopBack(SLTNode** pphead)
{
	//没有结点(空链表)
	assert(*pphead);
	assert(pphead);

	//一个结点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	//多个结点
	else
	{
		SLTNode* tail = *pphead;
		//找尾
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	assert(phead);

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

	return NULL;
}

//修改
void SLTModify(SLTNode* phead, SLTNode* pos, SLTDataType x)
{
	assert(phead);
	assert(pos);
	pos->data = x;
}

//在pos位置之前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);  //防止给的位置为 空

	if (*pphead == pos)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

		SLTNode* newnode = BuyLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

//在pos位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);  //防止给的位置为 空
	
	SLTNode* newnode = BuyLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

//删除pos位置的值
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);  //防止给的位置为 空

	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}

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

//删除pos位置之后的值
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLTNode* next = pos->next;
	pos->next = next->next;
	free(next);
}

🥝Test.c

#include"SList.h"

//头插测试
void TestSList01()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);

	SLTPrint(plist);

	SLTDestroy(&plist);
}

//尾插测试
void TestSList02()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);

	SLTPushBack(&plist, 5);
	SLTPushBack(&plist, 6);
	SLTPushBack(&plist, 7);

	SLTPrint(plist);

	SLTDestroy(&plist);
}

//头删测试
void TestSList03()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);
	SLTPopFront(&plist);
	SLTPrint(plist);

	SLTDestroy(&plist);
}

//尾删测试
void TestSList04()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);
	SLTPopBack(&plist);
	SLTPrint(plist);

	SLTDestroy(&plist);
}

//查找修改测试
void TestSList05()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = SLTFind(plist, 3);
	if (pos)
	{
		SLTModify(plist, pos, 8);
	}

	SLTPrint(plist);
	SLTDestroy(&plist);
	
}

//在pos位置之前插入数据测试
void TestSList06()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = SLTFind(plist, 3);
	if (pos)
	{
		SLTInsert(&plist, pos, 30);
	}

	SLTPrint(plist);
	SLTDestroy(&plist);
}

//在pos位置之后插入数据测试
void TestSList07()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = SLTFind(plist, 2);
	if (pos)
	{
		SLTInsertAfter(pos, 20);
	}

	SLTPrint(plist);
	SLTDestroy(&plist);
}


//删除pos位置的值测试
void TestSList08()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = SLTFind(plist, 3);
	if (pos)
	{
		SLTErase(&plist,pos);
	}

	SLTPrint(plist);
	SLTDestroy(&plist);
}

//删除pos位置之后的值测试
void TestSList09()
{
	SLTNode* plist = NULL;
	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SLTNode* pos = SLTFind(plist, 2);
	if (pos)
	{
		SLTEraseAfter(pos);
	}

	SLTPrint(plist);
	SLTDestroy(&plist);
}

int main()
{
	//TestSList01();
	//TestSList02();
	//TestSList03();
	//TestSList04();
	//TestSList05();
	//TestSList06();
	//TestSList07();
	//TestSList08();
	//TestSList09();

	return 0;
}

😍这期内容些许复杂,需要慢慢理解消化哦!

总结🥰
以上就是 【数据结构】单链表—C语言版 的全部内容啦🥳🥳🥳🥳
本文章所在【数据结构与算法】专栏,感兴趣的烙铁可以订阅本专栏哦🥳🥳🥳
前途很远,也很暗,但是不要怕,不怕的人面前才有路。💕💕💕
小的会继续学习,继续努力带来更好的作品😊😊😊
创作写文不易,还多请各位大佬uu们多多支持哦🥰🥰🥰

请添加图片描述

评论 87
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

C-调战士

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

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

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

打赏作者

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

抵扣说明:

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

余额充值