C语言实现单链表

一、什么是单链表

单链表是 C 语言中最基础、最重要的数据结构之一,非常适合用来练习结构体、指针和动态内存管理。

单链表的每个结点由两部分组成:数据域和指针域。数据域用于存储实际数据,指针域用于保存下一个结点的地址,多个结点通过指针依次连接,最终以 NULL 结尾形成完整链表。

二、单链表头文件设计(SList.h)

在实现单链表之前,通常先定义好结点结构和对外提供的操作接口,代码如下。

java 复制代码
#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 SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(SLTNode** pphead, SLTDataType x);

void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

SLTNode* SLTFind(SLTNode* phead, SLTDataType x);
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);
void SLTErase(SLTNode** pphead, SLTNode* pos);
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
void SLTEraseAfter(SLTNode* pos);

SLTDataType 使用 typedef 定义为 int,方便后续统一修改链表中存储的数据类型。

SLTNode 结构体表示单链表的结点,其中 data 为数据域,next 为指针域,指向下一个结点。多个结点通过 next 串联形成完整链表。

SLTPrint 用于遍历并打印链表,主要用于调试和测试阶段查看链表结构是否正确。

SLTPushBackSLTPushFront 分别实现尾插和头插操作,由于它们需要修改头指针,因此参数采用二级指针 SLTNode** pphead

SLTPopBackSLTPopFront 用于删除链表尾结点和头结点,同样需要修改头指针,所以也使用二级指针作为参数。

SLTFind 用于在链表中查找指定值,若找到则返回该结点指针,否则返回 NULL,便于后续进行插入或删除等操作。

SLTInsert 用于在指定结点 pos 前插入新结点,如果 pos 为头结点,则等价于头插操作;SLTErase 用于删除指定结点 pos

SLTInsertAfterSLTEraseAfter 分别表示在指定结点之后插入新结点,以及删除指定结点之后的结点。这两个操作不需要修改头指针,因此参数只需使用一级指针。

三、单链表实现(SList.c)

在前文中已经完成了单链表头文件的接口设计,下面给出对应的 .c 文件实现,通过这些基础操作,可以完整实现单链表的增删查改功能。

在实现功能前,我们增加一个函数SLTNode* BuySLTNode(SLTDataType x),来动态申请内存。该函数使用 malloc 申请空间,并对结点数据域和指针域进行初始化。如果内存申请失败,则打印错误信息并返回。完整代码如下。

java 复制代码
#include"SList.h"

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

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

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = BuySLTNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != NULL)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

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

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* cur = *pphead;
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		while (cur->next->next != NULL)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}

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

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{

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

}

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		SLTNode* newnode = BuySLTNode(x);
		prev->next = newnode;
		newnode->next = pos;
	}
}

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}

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

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

四、测试代码(Test.c)

java 复制代码
#include"SList.h"

int main()
{
	SLTNode* plist = NULL;
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);

	SLTPrint(plist);

	SLTPushFront(&plist, 6);
	SLTPushFront(&plist, 7);
	SLTPushFront(&plist, 8);
	SLTPrint(plist);

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

	// 值为2那个节点  *2
	SLTNode* ret = SLTFind(plist, 2);
	ret->data *= 2;
	SLTPrint(plist);

	SLTInsert(&plist, ret, 20);
	SLTPrint(plist);

	/*SLTErase(&plist, ret);
	ret = NULL;
	SLTPrint(plist);*/


	SLTInsertAfter(ret, 666);
	SLTPrint(plist);

	SLTEraseAfter(ret);
	SLTPrint(plist);


}

结果如下。

相关推荐
We་ct28 分钟前
LeetCode 5. 最长回文子串:DP + 中心扩展
前端·javascript·算法·leetcode·typescript
JAVA面经实录9174 小时前
Java企业级工程化·终极完整版背诵手册(无遗漏、全覆盖、面试+落地通用)
java·开发语言·面试
王老师青少年编程4 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【哈夫曼贪心】:合并果子
c++·算法·贪心·csp·信奥赛·哈夫曼贪心·合并果子
周杰伦fans5 小时前
AutoCAD .NET 二次开发:深入理解 EntityJig 的工作原理与正确实现
开发语言·.net
叼烟扛炮5 小时前
C++第二讲:类和对象(上)
数据结构·c++·算法·类和对象·struct·实例化
天疆说5 小时前
【哈密顿力学】深入解读航天器交会最优控制中的Hamilton函数
人工智能·算法·机器学习
wuweijianlove6 小时前
关于算法设计中的代价函数优化与约束求解的技术7
算法
leoufung6 小时前
LeetCode 149: Max Points on a Line - 解题思路详解
算法·leetcode·职场和发展
样例过了就是过了6 小时前
LeetCode热题100 最长公共子序列
c++·算法·leetcode·动态规划
HXDGCL7 小时前
矩形环形导轨:自动化循环线的核心运动单元解析
运维·算法·自动化