当前位置: 首页 > article >正文

数据结构 之 【无头单向非循环链表】(C语言实现)

下面将 无头单向非循环链表 简称为 单链表

头指针:指向链表第一个节点的指针

链表为空时,头指针也为空

要实现单链表,就是要实现单链表的 增删查改

一、无头单向非循环链表的c语言实现

1.准备工作

#include <stdio.h>
#include <stdlib.h>
#include <assert.h>

typedef int SLTDataTypde;

typedef struct SLTNode
{
	SLTDataTypde data;
	struct SLTNode* next;
}SLTNode;

(1)重定义 数据类型 的名称

方便后续只改这一处,就可以实现数据类型的改变

(2)定义节点,并简化名称

(3)包含相应头文件(动态开辟、库函数、断言)

2.动态申请一个节点

SLTNode* BuyNewNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
		return NULL;

	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

(1)注意申请失败的情况

3.打印单链表

void SLTPrint(SLTNode* plist)
{
	SLTNode* cur = plist;
	while (cur)
	{
		printf("%d->", cur->data);

		cur = cur->next;
	}

	printf("NULL\n");
}

(1)传入头节点的地址

(2)创建一个新变量用以遍历链表

防止后续找不到头节点

(3)打印格式看需求

4.单链表尾插

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	//断言
	assert(pphead);
	//创建新节点尾插
	SLTNode* newnode = BuyNewNode(x);
	//尾插
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tail = tail->next;
		}

		tail->next = newnode;
	}

}

(1)当指针不能为空时,断言一下,减少调试成本

(2)考虑尾插的实际情况:

链表为空:直接赋值

此时需要改变头指针的值,传入 二级指针

链表至少有一个节点:先找尾,再尾插

5.单链表头插

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	//断言
	assert(pphead);
	//创建
	SLTNode* newnode = BuyNewNode(x);
	//头插
	newnode->next = *pphead;
	*pphead = newnode;
}

(1)显然,需要改变头指针的指向

即,改变头指针的值,所以传入二级指针

6.单链表尾删

void SLTPopBack(SLTNode** pphead)
{
	//断言
	assert(pphead);
	assert(*pphead);
	//尾删
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		/*SLTNode *tail = *pphead;
		SLTNode *prev = NULL;
		while (tail->next)
		{
			prev = tail;
			tail = tail->next;
		}

		prev->next = tail->next;
		free(tail);
		tail = NULL;*/
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

(1) 对于第二个断言:

链表不为空才可以尾删

(2) 分情况尾删:

只有一个节点:直接释放内存,然后置空头指针

要改变头指针,传入二级指针

至少两个节点

先找到倒数第二个节点,再释放尾节点,然后将倒数第二个节点的next置空

直接释放尾节点,会导致倒数第二个节点的next成为野指针

(3) 展示了两种找尾的方法

7.单链表头删

void SLTPopFront(SLTNode** pphead)
{
	//断言
	assert(pphead);
	assert(*pphead);
	//头删
	SLTNode* first = (*pphead)->next;
	free(*pphead);
	*pphead = first;
}

(1)直接释放头节点会找不到下一个节点

先存储下一个节点,再释放,然后再改变头指针的值

8.单链表查找

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
			return cur;

		cur = cur->next;
	}
	return NULL;
}

(1)只是查找到第一个,有局限

9.单链表在pos位置之后插入x 

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);

	SLTNode* newnode = BuyNewNode(x);

	newnode->next = pos->next;
	pos->next = newnode;
}

(1) 链表为空,直接头插就行,不用调用该函数

(2)不在pos位置之前插入,

第一,需要考虑头插和中间插的情况

第二,可能需要保存前一个节点

总体来说,效率不高

10.单链表删除pos位置之后的值

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);

	SLTNode* del = pos->next;
	SLTNode* next = pos->next->next;
	pos->next = next;
	free(del);
	del = NULL;
}

(1)pos为空或pos之后的第一个节点为空

肯定时不能进行删除的

(2)直接释放下个节点会导致找不到下下个节点

需要提前保存下下个节点

(3)如果删除pos位置的节点

第一,需要考虑头删和中间删的情况

第二,可能需要保存前一个节点

总体来说,这样的效率不高

二、链表面试题及其体会

1.203. 移除链表元素 - 力扣(LeetCode)

本题选用尾插的思路,

做题用到尾插时,带上哨兵位,减少情况的讨论

只是需要注意释放该内存

2.206. 反转链表 - 力扣(LeetCode) 

本题选用头插的思路

头插不用考虑哨兵位问题

3.876. 链表的中间结点 - 力扣(LeetCode)

本题选用快慢指针的思路

注意奇偶性的不同情况

4.面试题 02.02. 返回倒数第 k 个节点 - 力扣(LeetCode)

本题注意

倒数第k个节点与倒数第1个节点的距离是k-1

一样采用双指针的思路

快的先走k-1步,此时,快慢指针之间相差k-1步

再同时走,直到快指针到尾即可

5.21. 合并两个有序链表 - 力扣(LeetCode)

两两比较,小的弄下来尾插

 6.链表分割_牛客题霸_牛客网

小于x的节点尾插到一个链表

大于等于x的节点尾插到另一个链表

连接两个链表

注意使用哨兵位减少空链表的讨论

注意链表的尾节点指向空

7.链表的回文结构_牛客题霸_牛客网

找到中间节点,反转后半段

再进行比较

本题需运用第2题和第3题的思想方法

8.160. 相交链表 - 力扣(LeetCode)

判断链表相交,那么它们的尾节点一定相同

指向长的链表的指针先走差距步

然后再同时走,

直到指向长的链表的指针和指向短的链表的指针相同就结束

9.141. 环形链表 - 力扣(LeetCode)

采用快慢指针的方法

注意

最好让快指针一次走两步,

慢指针一次走一步,

如果有环,那么它们终会相遇

不然快指针就会先为空先到尾节点

其他情况,链表有环,但是,

快指针一次比慢指针多走x步

(x>=2)

这时需要考虑

1.慢指针刚进环时,与快指针的差距

2.如果错过,又比较第二次的差距

3.以此类推......

根据每次的差距与x的关系,才能得出

快慢指针究竟是错过还是相遇

10.142. 环形链表 II - 力扣(LeetCode)

依据

链表有环并且快指针一次走2步

慢指针一次走1步的前提,设

(1)链表从头节点到入环的第一个节点的距离为L

(L>=1)

(2)快慢指针相遇的位置与入环的第一个节点的距离为X

(3)环的周长为C

则,

(1)慢指针走的距离为:L+X

(0 <= X < C)

因为快慢指针最开始的差距最大为 C - 1

慢指针在起点,快指针多走一步

(2)慢指针走的距离为:L+X+n*C

(n >= 1)

假设 n 最小为 0,

那么此时快慢指针走的距离相等,这不符合逻辑,错误

那么快指针要走到相遇点至少要走1圈的长度

(3)快指针走的距离是慢指针的2

2 * (L+X) = L+X+n*C

L + X = n*C

L = n*C - X

L = (n - 1)*C + C - X

结论

一个指针从头开始走

一个指针从相遇点开始走

它们最终会在入环的第一个节点处相遇

11.138. 随机链表的复制 - 力扣(LeetCode)

1.拷贝相应原节点,并连接到原链表中

2.如果原节点random == NULL,拷贝节点的random == NULL

拷贝节点的random  == 原节点random->next

3.拆下复制的链表并连接起来,再还原原链表

总结

做题,画图是精髓!!!


http://www.kler.cn/a/566342.html

相关文章:

  • 深入浅出:Spring AI 集成 DeepSeek 构建智能应用
  • 【FL0093】基于SSM和微信小程序的微信点餐系统小程序
  • 大语言模型训练的目标(不同的结构和阶段)
  • ragflow-mysql 启动失败案例分析
  • 深度学习简介
  • pikachu
  • C++核心编程之文件操作
  • 无人机自主导航与避障技术!
  • 力扣3112. 访问消失节点的最少时间
  • FS800DTU联动OneNET平台数据可视化View
  • git从零学起
  • std::sort 排序算法本质
  • YOLOv11-ultralytics-8.3.67部分代码阅读笔记-ops.py
  • 软件工程复试专业课-能力成熟度模型CMM
  • 科技赋能!深圳市悠声科技有限公司荣获“GAS消费电子科创奖-技术进步奖”!
  • 理解 AI IDE 中的代码库索引:深入探讨 Cursor 的实现
  • 区块链的基本原理和优势
  • 刨析刷任务赚钱平台的算法(众包经济应用)
  • 服务器间免密登录
  • 单片机学习笔记——入门51单片机