手撕单链表(C语言)

news2024/9/28 13:20:56

目录

1.单链表的物理结构

2.头文件的实现

3.SList.c文件的实现

3.1尾插、创建节点

3.2打印

3.3头插

3.4尾删

3.5头删

3.6查找

3.7指定位置之前插入数据

3.8指定位置之后插入数据

3.9删除指定位置节点

3.10删除pos之后的节点

3.11销毁链表

4 所有的代码


1.单链表的物理结构

众所周知单链表是线性表的一种,线性表是逻辑结构连续、物理结构不一定连续的结构,我们的单链表就是逻辑上连续但物理结构上不一定连续的结构。

那么它的逻辑图长什么样呢?(如下图所示)

上面的图片中我们展示的是一种无头单向不循环链表,plist指针指向的就是单链表头节点的空间的地址。我们将每一个小方格称为节点,每个节点的结构中包括数据值和指向下一个节点的地址。

上面的代码就是我们这无头单向不循环链表的结构,我们重命名int型是为了以后改数据类型的时候不要改太多次,只用在这一行把int换掉就可以了。然后我们把单链表结构体重命名也是为了我们书写方便。我们这里使用三个文件来实现无头单向不循环链表的内容。分别是test.c源文件(用来测试代码的文件)、SList.c源文件(用来实现接口的文件)、SList.h头文件(用来进行接口的声明,各种头文件的调用,定义数据结构)。最后我会把代码全部放上来,那么我们开始来体会这个过程。

2.头文件的实现

#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

//定义链表节点的结构
typedef int SLDataType;
typedef struct SListNode
{
	SLDataType data;//要保存的数据
	struct SListNode* next;
}SLNode;

//创建几个节点组成一个链表,并打印链表
void SLNPrint(SLNode* phead);
//尾插
void SLPushBack(SLNode** pphead, SLDataType x);
//头插
void SLPushFront(SLNode** pphead, SLDataType x);
//删除
void SLPopBack(SLNode** pphead);
void SLPopFront(SLNode** pphead);


SLNode* SLFind(SLNode** pphead, SLDataType x);


//指定位置的插入和删除

//在指定位置之前插入数据
void SLInsert(SLNode** pphead,SLNode* pos,SLDataType x);
//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x);

//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos);
//删除pos之后节点
void SLEraseAfter(SLNode* pos);
//销毁链表
void SListDesTroy(SLNode** pphead);

引用头文件我们就不多说了,单链表的结构我们上面也讲过了,接下来就来讲解一下声明这些接口的时候我们要注意的事项:由于在test.c文件中我们定义的是SLNode*指针,我们知道使用函数传参时,形参是实参的一份临时拷贝,所以为了修改我们的链表我们要传二级指针才能修改链表。

3.SList.c文件的实现

3.1尾插、创建节点

我们执行尾插前要先进行断言一下,这是为了防止传进来一个空指针,接下来就是创建节点:

创建节点我们只需要传一个x值就可以了,然后把返回值类型定位SLNode*型,在这个接口中我们先用malloc开辟出一块空间,然后再把x的值给到结构体中的data,之后我们再把next指针置为NULL,接下来就可以把节点指针返回了。

我们再回到上面,创建好节点后我们还要看一看是否传进来的是空链表,如果是的话我们把新的节点给头节点然后返回头节点,如果不是空链表的话,我们用pcur指针来遍历整个链表进行找尾操作,找到尾指针之后我们把尾指针的next指向新的节点。

3.2打印

我们的尾插操作有没有成功可以通过两种方式,一种是调试,一种是打印出来看看(粗略检查)。

我们还是让pcur指针来进行遍历,每遍历一个节点我们就打印一个节点,最后打印一个NULL,如果是空链表我们就会直接打印NULL。

这样我们就可以看出确实是尾插成功了。

3.3头插

头插的操作跟尾插差不多,我们先断言指针是否为NULL,不是的话我们创建一个新的节点,这里我们注意一下顺序,我们要先把新节点的next指向原来的头节点,再把头节点指针指向新的节点,倘若我们先进行把头节点指针指向新的节点我们就会发现我们找不到原来的头节点了,这就是我们要注意的一个地方。

好,让我们来看看效果:

3.4尾删

尾删的操作我们不需要传入x值了,只需要二级指针就行了,第一步还是要进行断言,我们这里我们还需要断言一下它的一级指针,因为我们既然要进行删除操作,我们的链表总不能是空链表吧,接下来我们处理只有一个节点的情况,只有一个节点的话,我们直接把头节点空间释放,地址指向空就可以了,如果节点不只有一个,我们就需要找尾节点和尾节点的前一个节点,我们先创建一个ptail进行遍历,ptail指向的是最后一个节点,prev则是前一个节点,我们把我们把将要成为尾节点的next指向原尾节点的next,再把原尾节点的空间释放掉,我们就完成了尾删的操作。

3.5头删

头删操作要简单的多,它不需要考虑只有一个节点的情况,跟尾删一样,我们先断言两次,然后创建一个del指针指向头节点,然后将头节点指向新的头节点,也就是原头节点的下一个节点,然后把del释放掉,我们再出于规范把这个野指针置为NULL。

3.6查找

这里我们查找的标准就定为查找是否有x这个元素,然后我们的第一步还是断言,之后创建一个pcur来遍历整个链表,如果找到了就返回这个地址,如果没有找到就返回NULL。

3.7指定位置之前插入数据

这里我们要多传一个参数pos,因为pos就是我们的指定位置的节点。好我们开始断言,断言之后我们创建新的节点,然后处理只有一个节点的情况,如果只有一个头节点我们就把新节点的next指向头节点,然后再让头节点的指针指向新节点;如果不只有一个节点,那么我们就创建一个指针prev来找到pos节点的前一个,然后把新节点的next指向pos,再把原来的前一个节点的next指向新的节点。

3.8指定位置之后插入数据

这个操作我们就要简单的多,我们先进行断言,然后创建一个新的节点,让新的节点的next指向pos节点的下一个节点,再把pos的next指向新的节点。

3.9删除指定位置节点

老操作,先进行断言如果我们删除的是头节点,那么我们直接把头节点指向头节点的下一个节点,然后释放pos,返回无值就可以了;如果不是头节点,那么我们就要先创建一个prev指针来找pos的前一个节点,把prev的next指向pos的下一个,再释放掉pos就可以了,我们也可以为了规范再把pos置为NULL。

3.10删除pos之后的节点

这个就要节点的多,我们只需要断言一下pos和pos的下一个节点就行了,然后我们再创建一个del指针指向pos的下一个节点,然后把pos的next指向pos的下一个的下一个节点,我们再释放掉del。

3.11销毁链表

我们进行一下断言,再创建一个指针进行遍历,每遍历一次就销毁一个节点。

4 所有的代码

//SList.h头文件


#pragma once
#include <stdio.h>
#include <stdlib.h>
#include <assert.h>
#include <stdbool.h>

//定义链表节点的结构
typedef int SLDataType;
typedef struct SListNode
{
	SLDataType data;//要保存的数据
	struct SListNode* next;
}SLNode;

//创建几个节点组成一个链表,并打印链表
void SLNPrint(SLNode* phead);
//尾插
void SLPushBack(SLNode** pphead, SLDataType x);
//头插
void SLPushFront(SLNode** pphead, SLDataType x);
//删除
void SLPopBack(SLNode** pphead);
void SLPopFront(SLNode** pphead);


SLNode* SLFind(SLNode** pphead, SLDataType x);


//指定位置的插入和删除

//在指定位置之前插入数据
void SLInsert(SLNode** pphead,SLNode* pos,SLDataType x);
//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x);

//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos);
//删除pos之后节点
void SLEraseAfter(SLNode* pos);
//销毁链表
void SListDesTroy(SLNode** pphead);

//SList.c源文件

#define _CRT_SECURE_NO_WARNINGS 1
#include "SList.h"

void SLNPrint(SLNode* phead)
{
	//循环打印
	SLNode* pcur = phead;
	while (pcur)
	{
		printf("%d ->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

SLNode* SLBuyNode(SLDataType x)
{
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	node->data = x;
	node->next = NULL;
	return node;
}

//尾插
void SLPushBack(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* node = SLBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = node;
		return;
	}
	//说明链表不为空,找尾
	SLNode* pcur = *pphead;
	while (pcur->next)
	{
		pcur = pcur->next;
	}
	pcur->next = node;
}
//头插
void SLPushFront(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* node = SLBuyNode(x);
	//新节点跟头节点连接起来
	node->next = *pphead;
	//让新的节点成为头节点
	*pphead = node;
}
//删除
void SLPopBack(SLNode** pphead)
{
	assert(pphead);
	//第一个节点不能为空
	assert(*pphead);
	//只有一个节点的情况
	if ((*pphead)->next==NULL)
	{
		//直接把头节点删除
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//找尾节点和尾节点的前一个节点
		SLNode* prev = NULL;
		SLNode* ptail = *pphead;
		while (ptail->next != NULL)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		//prev的next指针不再指向ptail,指向下一个节点
		prev->next = ptail->next;
		free(ptail);
		ptail = NULL;
	}
	
}
void SLPopFront(SLNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	del = NULL;//出于代码规范
}

SLNode* SLFind(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}


//在指定位置之前插入数据
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	assert(pphead);
	//约定链表不为空,pos也不能为空
	assert(pos);
	assert(*pphead);
	SLNode* node = SLBuyNode(x);
	//处理只有一个节点+pos指向第一个节点
	if (pos==*pphead)
	{
		node->next = *pphead;
		*pphead = node;
		return;
	}
	//找pos的前一个节点
	SLNode* prev = *pphead;
	while(prev->next!=pos)
	{
		prev = prev->next;
	}
	//prev node pos
	node->next = pos;
	prev->next = node;
}

//在指定位置之后插入数据
void SLInsertAfter(SLNode* pos, SLDataType x)
{
	assert(pos);
	SLNode* node = SLBuyNode(x);
	node->next = pos->next;
	pos->next = node;
}

//删除pos节点
void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);

	if (pos == *pphead)
	{
		*pphead = (*pphead)->next;
		free(pos);
		return;
	}
	//找pos的前一个节点
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}
//删除pos之后节点
void SLEraseAfter(SLNode* pos)
{
	assert(pos&&pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
}

//销毁链表
void SListDesTroy(SLNode** pphead)
{
	assert(pphead);
	SLNode* pcur = *pphead;
	
	while (pcur)
	{
		SLNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	pcur = NULL;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/1222927.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

6.8完全二叉树的节点个数(LC222-E)

算法&#xff1a; 如果不考虑完全二叉树的特性&#xff0c;直接把完全二叉树当作普通二叉树求节点数&#xff0c;其实也很简单。 递归法&#xff1a; 用什么顺序遍历都可以。 比如后序遍历&#xff08;LRV&#xff09;&#xff1a;不断遍历左右子树的节点数&#xff0c;最后…

小程序授权获取昵称

wxml: <form bindsubmit"formsubmit"><view style"width: 90%;display: flex;margin-left: 5%;"><view class"text1">昵称&#xff1a;</view><input style"width: 150px;margin-left: 30px;margin-top: 30px;…

【论文阅读笔记】Supervised Contrastive Learning

【论文阅读笔记】Supervised Contrastive Learning 摘要 自监督批次对比方法扩展到完全监督的环境中&#xff0c;以有效利用标签信息提出两种监督对比损失的可能版本 介绍 交叉熵损失函数的不足之处&#xff0c;对噪声标签的不鲁棒性和可能导致交叉的边际&#xff0c;降低了…

face_recognition:高准确率、简单易用的人脸识别库 | 开源日报 No.79

ageitgey/face_recognition Stars: 49.8k License: MIT 这个项目是一个使用 Python 编写的人脸识别库&#xff0c;可以从图片中识别和操作人脸。它基于 dlib 开发&#xff0c;并采用深度学习技术构建了最先进的人脸识别模型&#xff0c;在 Labeled Faces in the Wild 数据集上…

Redis(消息队列Stream)

Stream是一个轻量级的消息队列。 Redis中Stream的作用是提供一种高效的消息传递机制&#xff0c;允许多个消费者并行地消费消息&#xff0c;并且不会重复消费已经处理过的消息。它可以用于实现分布式任务队列、日志收集、实时数据处理等场景。Redis中的Stream支持多个消费者组…

【LeetCode刷题-滑动窗口】--992.K个不同整数的子数组

992.K个不同整数的子数组 思路&#xff1a; class Solution {public int subarraysWithKDistinct(int[] nums, int k) {return atMostKDistinct(nums,k) - atMostKDistinct(nums,k-1);}//最多包含K个不同整数的子区间个数private int atMostKDistinct(int[] a,int k){int len …

PS学习笔记——新建文档/修改文档

文章目录 新建文档文档属性像素/分辨率颜色模式背景内容高级选项存储预设 修改文档 新建文档 方法一&#xff1a;ctrlN快捷键可直接打开新建文档界面 方法二&#xff1a;点击菜单栏中 文件->新建&#xff0c;即可打开新建文档界面 文档参数可按需调节(标题可以提前设定或者…

反激变压器计算方法_笔记

反激变压器计算方法_笔记 匝数比原边电感选定磁芯线圈匝数线径 原视频链接 匝数比 5V 是想要得到的输出电压 0.7V为二极管导通的压降 185Vx根号2是有效值 最大占空比取0.4。得出最小匝数为30。 更改某些值可能得出来的匝数比就不一定是30了&#xff0c; 这其实也是反激变压器…

mac苹果电脑需要安装杀毒软件吗?

随着数字时代的发展&#xff0c;计算机安全问题变得越来越重要。而在计算机安全领域中&#xff0c;杀毒软件是一个被广泛讨论的话题。苹果电脑需要安装杀毒软件吗&#xff1f;对于苹果电脑用户来说&#xff0c;他们常常会疑惑自己是否需要安装杀毒软件来保护自己的电脑。本文将…

力扣每日一题-数位和相等数对的最大和-2023.11.18

力扣每日一题&#xff1a;数位和相等数对的最大和 开篇 这道每日一题还是挺需要思考的&#xff0c;我绕晕了好久&#xff0c;根据题解的提示才写出来。 题目链接:2342.数位和相等数对的最大和 题目描述 代码思路 1.创建一个数组存储每个数位的数的最大值&#xff0c;创建一…

kubernetes学习笔记-概念

参考&#xff1a;https://kubernetes.io/zh-cn/docs/concepts/overview/ 概述 Kubernetes 是一个可移植、可扩展的开源平台&#xff0c;用于管理容器化的工作负载和服务&#xff0c;可促进声明式配置和自动化。 Kubernetes 拥有一个庞大且快速增长的生态&#xff0c;其服务、…

【Linux】C文件系统详解(二)——什么是fd文件描述符以及理解“一切皆文件“

文章目录 fd-文件描述符如何深度理解"一切皆文件"**我们使用OS的本质:**FILEFILE是什么?谁提供的?和我们刚刚讲的内核的struct有关系吗FILE是一个结构体.该结构体内部一定要有以下字段:FILE是C语言标准库提供的.FILE和我们刚刚讲的内核的struct没有关系,最多就是上…

医院绩效考核系统源码 医院绩效考核系统方案

医院绩效考核系统源码 医院绩效考核系统是现代医院管理的重要方法和科学的管理工具。良好的绩效管理&#xff0c;有助于带动全院职工的工作积极性&#xff0c;有助于提高工作效率、提高医疗质量、改善服务水平、降低运营成本&#xff0c;全面提升医院的精细化管理水平。 医院绩…

不允许你还没有了解哈希表、哈希桶、哈希冲突的解决,如何避免冲突

✏️✏️✏️今天给各位带来的是哈希桶、哈希冲突方面的知识。 清风的CSDN博客 &#x1f61b;&#x1f61b;&#x1f61b;希望我的文章能对你有所帮助&#xff0c;有不足的地方还请各位看官多多指教&#xff0c;大家一起学习交流&#xff01; 动动你们发财的小手&#xff0c;点…

springMVC学习笔记-请求映射,参数绑定,响应,restful,响应状态码,springMVC拦截器

目录 概述 springMVC做了什么 springMVC与struts2区别 springMVC整个流程是一个单向闭环 springMVC具体的处理流程 springMVC的组成部分 请求映射 RequestMapping 用法 属性 1.value 2.method GET方式和POST方式 概述 HTTP给GET和POST做了哪些规定 GET方式&…

IDEO也不行了吗?设计正在变革#实时设计

2023 年 8 月&#xff0c;在与宜家品牌合作近 10 年之后&#xff0c;SPACE10 关门了。 最近&#xff0c;IDEO&#xff0c;设计思维的早期倡导者和践行者&#xff0c;宣布裁员1/3。 介绍下这两家设计公司&#xff1a; SPACE10 由宜家全额资助&#xff0c;于 2015 年落户哥本哈根…

生成式AI模型量化简明教程

在不断发展的人工智能领域&#xff0c;生成式AI无疑已成为创新的基石。 这些先进的模型&#xff0c;无论是用于创作艺术、生成文本还是增强医学成像&#xff0c;都以产生非常逼真和创造性的输出而闻名。 然而&#xff0c;生成式AI的力量是有代价的—模型大小和计算要求。 随着生…

DevSeo Studio设置中文界面

安装好DevSeo Studio后默认打开是欢迎页。 左下角Configure点击展开&#xff0c;选择plugins 弹出页面选择“installed”,然后输入chinese,默认是关闭的&#xff0c;点击enable将它启用&#xff0c;然后点击OK。 弹出页面点击“restart”重启即可。

Draco Win10编译

1. 工具 CMake 3.2.8&#xff0c;Visual Studio 2019 2. 步骤 2.1 拷贝代码 git clone https://github.com/google/draco.gitgit clone https://github.com/google/draco.git 下载第三方依赖 git submodule sync git submodule update --init --recursive 2.2 CMake编译…

一个UE无法注册的问题

问题场景是环境中只有一个小区&#xff0c;UE在找到这个小区&#xff0c;收到MIB SIB1后一直不发起注册。我想这大概是和S准则不满足有关系了&#xff0c;这个问题基本是又没啥好看的了&#xff0c;太简单了&#xff0c;在SIB1周围找找就解决了&#xff0c;于是我发现了以下log…