数据结构 - 线性表 - 单链表

一.定义

一种线性存储结构,元素不连续存放,每个节点包括两部分:数据域 + 后继指针,指针指向下一个结点;最后一个节点指针为NULL。

二.节点结构和性质

**补充-头节点:**可以设置虚拟头节点,简化边界处理;头指针指向链表开头。

单链表的性质

1.链表在逻辑上是连续的,物理结构上不一定连续。

2.结点一般是从堆上申请的。

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

三.链表新节点的创建

复制代码
SLTNode* SLTbuyNode(SLTDataType x)
{
	//根据x创建节点
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

四.单链表结点的定义

复制代码
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;//存储的结构
	struct SLTNode* next;//指向下一个结点
}SLTNode;

五.单链表的打印

复制代码
void SLTPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)//pcur != NULL
	{
		printf("%d ->", pcur->data);
		pcur = pcur->next;

	}
	printf("NULL\n");
}

六.单链表的尾插 - 时间复杂度O(N)

直接把新节点插到链表最后面即可:

复制代码
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->next = newnode;
	}
}

七.单链表的头插 - 时间复杂度O(1)

把新结点插到链表最开头,新节点变头节点。

复制代码
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	//创建新节点,存入数据x。
	SLTNode* newnode = SLTbuyNode(x);
	//新节点next指向原来链表的第一个节点。
	//这里的*phead是原始头指针,代表链表旧首节点
	newnode->next = *pphead;
	//更新头指针,让头指针指向新节点,新节点成为新的表头
	*pphead = newnode;
}

八.单链表的尾删 - 时间复杂度O(N)

删除链表的最后一个结点,单链表只能从头往后走,不能从尾巴往回走,不光要删除尾结点,还要删除倒数第二个节点,把它的next置为NULL。

复制代码
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	//只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}
	SLTNode* prev = NULL;
	SLTNode* ptail = *pphead;
	while (ptail->next)
	{
		prev = ptail;
		ptail = ptail->next;
	}
	prev->next = NULL;
	free(ptail);
	ptail = NULL;
}

九.单链表的头删 - 时间复杂度O(1)

删除链表第一个结点(头结点)。

复制代码
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

十.单链表的查找

复制代码
void SLTPopFront(SLTNode** pphead)
{
	assert(pphead && *pphead);
	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

十一.在指定位置pos前插入数据

在 pos 结点前面插入新结点。单链表没有向前访问的能力,无法直接拿到 pos 的前一个结点,所以必须从链表头部开始循环遍历,找到 pos 的前驱结点 prev。如果 pos 本身就是链表的头结点,前面没有结点,直接复用头插逻辑。找到前驱后,让prev->next指向新结点,再让新结点的next指向 pos,完成链表衔接。整段操作需要遍历链表,时间复杂度 O (N)。如果不单独处理 pos 等于头结点的情况,循环会找不到前驱,代码直接出错。

复制代码
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead && pos);
	//当pos指向第一个节点,是头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead,x);
		return;
	}
	SLTNode* newnode = SLTbuyNode(x);
	//找到pos的前一个节点
	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = newnode;
	newnode->next = pos;
}

十二.在指定位置后插入数据.

在 pos 结点后面插入新结点,不需要寻找前驱,效率更高。首先将新结点的 next 指向 pos 原本的后继结点,保存住 pos 后面整条链表;再修改 pos 的 next 指向新结点,完成连接。指针顺序不能调换,如果先修改 pos 的 next,pos 原本后面结点的地址就会丢失,造成链表断裂。该操作没有循环,时间复杂度 O (1),也不存在特殊边界情况,是单链表更推荐的插入方式。

复制代码
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTbuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

十三.单链表删除pos位置的结点

先找pos的前一个节点吗,让pos结点的前一个结点的next指针指向pos结点的下一个节点。

复制代码
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead && pos);
	//pos就是头节点 - 头删
	if (pos == *pphead)
	{
		SLTPopFront(pphead);
		return;
	}
	SLTNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

十四.删除链表pos位置之后的节点

该函数删除 pos 后面的结点,无需寻找前驱,没有循环,时间复杂度 O (1)。断言保证 pos 不为空,且 pos 后面确实存在结点。先用 del 保存待删除的后继结点,让 pos 直接连接到 del 的下一个结点,跳过被删节点,再释放 del 内存。只能删掉 pos 后面的节点,不能删除 pos 自身;若 pos 是倒数第二个结点,就可以删掉链表尾结点。函数内部把 del 置空,只作用于局部变量。

复制代码
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos && pos->next);
	SLTNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

十五.销毁链表

复制代码
void SListDestroy(SLTNode** pphead)
{
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
}
相关推荐
1000世界小札1 小时前
排序算法全景总结:从 O(n²) 到 O(n log n),再到线性时间(附完整对比与选型指南)
数据结构·算法·排序算法
玉树临风ives2 小时前
atcoder ABC 472 题解
数据结构·c++·算法
李可以量化3 小时前
Redis Client 从入门到精通(一)下:redis-py 核心数据结构操作与连接管理
数据结构·redis·python·bootstrap·量化交易·qmt
疯狂打码的少年3 小时前
【数据结构】复习日:树 + 图 + 排序(整理对比表)
数据结构·笔记·算法
星星.7224 小时前
2026河南萌新联赛第六场(郑州大学)补题B、D、L、J、I
数据结构·c++·算法
心仪久了会沦陷4 小时前
数据结构入门系列——顺序表详解:概念、分类与动态实现
c语言·数据结构·经验分享·笔记·开源
smj2302_796826524 小时前
解决leetcode第4033题有效K个不同元素子数组I
数据结构·python·算法·leetcode
旖旎夜光5 小时前
LCR 173:在点名(二分查找) —— 题解
数据结构·c++·算法·leetcode·二分查找