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

一.定义

一种线性存储结构,元素不连续存放,每个节点包括两部分:数据域 + 后继指针,指针指向下一个结点;最后一个节点指针为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;
	}
}
相关推荐
h_a_o777oah16 小时前
【树论】树链剖分:将树形结构转化为线性结构以处理树上路径问题(洛谷 P3384 附C++代码模板)
数据结构·算法·线段树·acm·树链剖分·树论·重链剖分
HEJOO916 小时前
指针的算术运算详解
java·数据结构·算法
Coder-magician16 小时前
《代码随想录》刷题打卡day47:并查集-part01
数据结构·算法·图论
垆边人似月.16 小时前
VLAN 资源池(100~200 分档)
数据结构·算法
程序员清风18 小时前
算法与数据结构入门:复杂度、数组与链表
数据结构·算法·链表
Logic10121 小时前
C语言/数据结构排序题解:三个数的最大乘积——排序后比较两种候选情况
c语言·数据结构·数组·排序·时间复杂度·算法题·负数处理
殷色玫瑰1 天前
C++ string类详解:常用接口、字符串操作与模拟实现
java·linux·c语言·开发语言·数据结构·c++
写后端的胖头鱼1 天前
时间复杂度 & 空间复杂度
java·数据结构·算法·时间复杂度·空间复杂度
多弗朗皮卡丘1 天前
数据结构番外:环形队列
数据结构·c
50万马克的面包1 天前
顺序表和链表_精华知识点_CSDN博客
数据结构·链表