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

一.定义

一种线性存储结构,元素不连续存放,每个节点包括两部分:数据域 + 后继指针,指针指向下一个结点;最后一个节点指针为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;
	}
}
相关推荐
不会就选b9 小时前
数据结构之排序
数据结构
我不会起名字32211 小时前
一天一道算法题(34):回溯法的经典例题(子集)
java·数据结构·python·算法·golang·深度优先·力扣
Logic10114 小时前
C语言/数据结构位运算题解:异或XOR找出货船中的“独特载货量“——只出现一次的数字
c语言·数据结构·数组·位运算·时间复杂度·算法题·异或性质
彧azz15 小时前
DFS与BFS:图遍历的两大核心算法
数据结构·学习·算法·深度优先·广度优先
青山木16 小时前
Hot 100 --- 最长递增子序列
java·数据结构·算法·leetcode·动态规划
土司大王16 小时前
LeetCode hot100——33.搜索旋转排序数组:Java 二分模板与 O(log n) 实现
数据结构·算法·leetcode
程序员阿鹏17 小时前
如何实现MySQL分库分表?
数据结构·数据库·sql·mysql·缓存
ysu_031418 小时前
08-二叉树遍历:四种方式详解
c语言·数据结构·算法·leetcode
Lyyaoo.19 小时前
【二分查找】【中等】搜索二维矩阵/排序数组的第一位和最后位置/搜索旋转排序数组/旋转排序数组中的最小值
java·数据结构·算法
Lyyaoo.20 小时前
【二分查找】【困难】寻找两个有序数组的中位数
java·数据结构·算法