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();
}
相关推荐
南行*4 小时前
C语言Linux环境编程
linux·c语言·开发语言·网络安全
闻缺陷则喜何志丹4 小时前
【回文 字符串】3677 统计二进制回文数字的数目|2223
c++·算法·字符串·力扣·回文
你怎么知道我是队长4 小时前
C语言---printf函数使用详细说明
c语言·开发语言
Tisfy4 小时前
LeetCode 0085.最大矩形:单调栈
算法·leetcode·题解·单调栈
消失的旧时光-19434 小时前
函数指针 + 结构体 = C 语言的“对象模型”?——从 C 到 C++ / Java 的本质统一
linux·c语言·开发语言·c++·c
mit6.8244 小时前
出入度|bfs|状压dp
算法
!停4 小时前
C语言栈和队列的实现
开发语言·数据结构
hweiyu004 小时前
强连通分量算法:Kosaraju算法
算法·深度优先
源代码•宸4 小时前
Golang语法进阶(定时器)
开发语言·经验分享·后端·算法·golang·timer·ticker