C语言单链表

概念

单链表是 C 语言中一种线性、非连续存储的数据结构,它不像数组那样占用一块连续的内存空间,而是由一个个独立的「节点」通过指针串联而成,且只能从前往后单向遍历

单链表的结构

数据域:存储实际的业务数据(比如 int、char、结构体等);

指针域:存储一个指向「下一个节点」的指针(类型与节点结构体一致)

注意:

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

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

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

    struct SListNode
    {
    type data;
    struct SListNode* next;
    };

一个是我们要存储的数据,一个则是指向下一个节点的指针,我们再稍微对这个结构体改进一下

复制代码
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

第一行的typedef的作用就是给int换个名字叫做SLDataType,第二行的typedef的作用就是给struct SListNode这个结构体起一个新的名字叫做SLTNode,后续就可以用SLTNode表示这个结构体

在讲解顺序表的时候说了要动用模块化函数编程的思想,这里也一样

初始化

既然有了结构,我们就可以先初始化单链表的节点,首先链表由一个一个的节点组成,并且每一个节点都指向下一个节点,而最后一个节点就指向NULL,那我们就可以先开辟一块空间把节点存进去,是一块一块的开辟,所以不需要扩展空间

复制代码
/链表由一个一个的节点组成
	//创建几个节点
	SLTNode* node1 = (SLTNode*)malloc(sizeof(SLTNode));
	node1->data = 1;
	SLTNode* node2 = (SLTNode*)malloc(sizeof(SLTNode));
	node2->data = 2;
	SLTNode* node3 = (SLTNode*)malloc(sizeof(SLTNode));
	node3->data = 3;
	SLTNode* node4 = (SLTNode*)malloc(sizeof(SLTNode));
	node4->data = 4;

现在这里面就是先把数据给初始化好,再将节点连接起来

复制代码
//将四个节点连接起来
node1->next = node2;
node2->next = node3;
node3->next = node4;
node4->next = NULL;

现在就可以写一个打印输出的函数看一下效果

复制代码
void SLTPrint(SLTNode* phead);

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

SLTNode* plist = node1;
SLTPrint(plist);

这里定义一个plist的头指针,再根据这个头指针创建一个临时头指针pcur,循环打印

现在我们就把这个单链表初始化好了,现在来实现增删改查的功能

插入

尾插

复制代码
//尾插
void SLTPushBack(SLTNode* pphead, SLTDataType x);

在尾插之前,我们不管是不是尾插还是头插都会涉及到开辟空间,每一次插入都开辟空间就有点浪费时间和空间了,所以我们可以写一个函数专门用来开辟空间

复制代码
//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

//尾插
void SLTPushBack(SLTNode* pphead, SLTDataType x)
{
	assert(pphead);
	//空链表和非空链表

	SLTNode* newnode = SLTBuyNode(x);
	if (pphead == NULL)
	{
		pphead = newnode;
	}
	else
	{
		//找尾
		SLTNode* ptail = pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		//ptail指向尾节点
		ptail->next = newnode;
	}
}

 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(plist, 1);
}
int main()
{
    SListtTest02();
}

现在我们来调试一下

初始情况下置为NULL

但当我的代码走完尾插的模块的时候还是没有数据

但我已经传递数据过去为什么还是NULL?继续调试

此时代码已经调试到了走到尾插模块最后一行,现在跳出这个模块

此时pphead变了,plist没有变,形参发生了改变,而实参没有,说明这里我们传递的是值,而不是地址,所以我们这里需要传指针地址,就需要用二级指针接收,正确的代码是这样的

复制代码
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

//申请新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//空链表和非空链表

	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SLTNode* ptail = pphead;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		//ptail指向尾节点
		ptail->next = newnode;
	}
}

再调用一下打印函数

头插

复制代码
void SLTPushFront(SLTNode** pphead, SLTDataType x);

//头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

单链表的头插不需要把所有节点都往后移,只需要将头节点和头插的节点连接就可以了

复制代码
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}

删除

尾删

复制代码
void STLPopBack(SLTNode** pphead);

//尾删
void STLPopBack(SLTNode** pphead)
{
	assert(pphead);
	//链表不能为空
	assert(*pphead);
	//链表只有一个节点和多个节点
	if ((*pphead)->next == NULL)//->优先级高于*
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);
		ptail = NULL;
        prev->next=ptail;
	}

}

 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    
}
int main()
{
    SListtTest02();
}

头删

复制代码
void  STLPopFront(SLTNode** pphead);

//头删
void  STLPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* news = (*pphead)->next;
	free(*pphead);
	*pphead = news;
}

 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    
}
int main()
{
    SListtTest02();
}

查找

复制代码
SLTNode* SLTFind(SLTNode* phead, SLTDataType x);

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* news = phead;
	while (news)
	{
		if (news->data == x)
		{
			return news;
		}
		news = news->next;
	}
	return -1;
}

 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
if (news == NULL)
{
	printf("没找到\n");
}
else
{
	printf("找到了\n");
}
}
int main()
{
    SListtTest02();
}

在指定位置之前插入

复制代码
void  STLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

三个参数,一个是传递的地址,一个是指定的位置,一个是要插入的在值,这里我就以上面查找的值的位置来插入

复制代码
//在指定位置之前插入
void  STLInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	SLTNode* newnode = SLTBuyNode(x);
	assert(pphead && *pphead);
	assert(pos);
	//若pos==*pphead 说明是头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}

}

 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
    STLInsert(&plist,news,12);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}

在指定位置之后插入

复制代码
void STLInsertAfter(SLTNode* pos, SLTDataType x);

//在指定位置之后插入
void STLInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
    STLInsert(&plist,news,12);
    SLTPrint(plist);
    STLInsertAfter(news, 333);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}

删除pos节点

复制代码
void SLTErase(SLTNode** pphead, SLTNode* pos);

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead && pphead);
	assert(pos);
	//pos是头节点和不是头结点
	if (pos == *pphead)
	{
		STLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

还是以上面查找到的2来举例

复制代码
 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
    STLInsert(&plist,news,12);
    SLTPrint(plist);
    STLInsertAfter(news, 333);
    SLTPrint(plist);
    SLTErase(&plist,news);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}

删除pos之后的节点

复制代码
void SLTEraseAfter(SLTNode* pos);

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
    STLInsert(&plist,news,12);
    SLTPrint(plist);
    STLInsertAfter(news, 333);
    SLTPrint(plist);
    /*SLTErase(&plist,news);
    SLTPrint(plist);*/
    SLTEraseAfter(news);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}

因为上一个模块将2删除了,所以就把上一个模块注释掉

销毁链表

复制代码
void SListDesTroy(SLTNode** pphead);

//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

 void SListtTest02()
{
    SLTNode* plist = NULL;
    SLTPushBack(&plist, 1);
    SLTPushBack(&plist, 2);
    SLTPushBack(&plist, 3);
    SLTPushBack(&plist, 4);
    SLTPushFront(&plist, 5);
    STLPopBack(&plist);
    SLTPrint(plist);
    STLPopFront(&plist);
    SLTPrint(plist);
    SLTNode * news=SLTFind(plist, 2);
    STLInsert(&plist,news,12);
    SLTPrint(plist);
    STLInsertAfter(news, 333);
    SLTPrint(plist);
    /*SLTErase(&plist,news);
    SLTPrint(plist);*/
    SLTEraseAfter(news);
    SLTPrint(plist);
    SListDesTroy(&plist);
    SLTPrint(plist);
}
int main()
{
    SListtTest02();
}
相关推荐
毕设源码-钟学长几秒前
【开题答辩全过程】以 基于协同过滤推荐算法的小说漫画网站设计与实现为例,包含答辩的问题和答案
算法·机器学习·推荐算法
u0109272713 分钟前
代码覆盖率工具实战
开发语言·c++·算法
懈尘9 分钟前
深入理解Java的HashMap扩容机制
java·开发语言·数据结构
We་ct14 分钟前
LeetCode 73. 矩阵置零:原地算法实现与优化解析
前端·算法·leetcode·矩阵·typescript
天赐学c语言14 分钟前
2.1 - 反转字符串中的单词 && 每个进程的内存里包含什么
c++·算法·leecode
程序员泠零澪回家种桔子16 分钟前
OpenManus开源自主规划智能体解析
人工智能·后端·算法
请注意这个女生叫小美19 分钟前
C语言 实例20 25
c语言·开发语言·算法
好学且牛逼的马20 分钟前
【Hot100|22-LeetCode 206. 反转链表 - 完整解法详解】
算法·leetcode·矩阵
hans汉斯22 分钟前
国产生成式人工智能解决物理问题能力研究——以“智谱AI”、“讯飞星火认知大模型”、“天工”、“360智脑”、“文心一言”为例
大数据·人工智能·算法·aigc·文心一言·汉斯出版社·天工
枫叶丹424 分钟前
【Qt开发】Qt系统(十一)-> Qt 音频
c语言·开发语言·c++·qt·音视频