手撕单链表(C语言)

目录

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 posnode->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.hqwc.cn/news/190422.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

无损音频播放软件 Colibri mac中文版特点介绍

Colibri for mac是一款轻量级的音频播放器软件。它具有简洁的界面设计和快速启动速度&#xff0c;能够提供流畅的音频播放体验。Colibri支持多种常见的音频格式&#xff0c;包括MP3、FLAC、ALAC、AAC等。它还提供了一些实用的功能&#xff0c;如音频均衡器、音频增益控制、播放…

js/jQuery 的一些常用操作(js/jQuery获取表单元素值 以及 清空元素值的各种实现方式)——附测试例子,拿来即能实现效果

js/jQuery 的一些常用操作&#xff08;js/jQuery获取表单元素值 以及 清空元素值的各种实现方式&#xff09;——附测试例子&#xff0c;拿来即能实现效果 1. 前言2. 获取表单元素的值2.1 简单获取元素中的值2.1.1 根据 id 简单取值2.2.2 根据name 简单取值2.1.3 获取单选按钮的…

机器学习第8天:SVM分类

文章目录 机器学习专栏 介绍 特征缩放 示例代码 硬间隔与软间隔分类 主要代码 代码解释 非线性SVM分类 结语 机器学习专栏 机器学习_Nowl的博客-CSDN博客 介绍 作用&#xff1a;判别种类 原理&#xff1a;找出一个决策边界&#xff0c;判断数据所处区域来识别种类 简单…

LLM大模型4位量化实战【GPTQ】

权重量化方面的最新进展使我们能够在消费类硬件上运行大量大型语言模型&#xff0c;例如 RTX 3090 GPU 上的 LLaMA-30B 模型。 这要归功于性能下降最小的新型 4 位量化技术&#xff0c;例如 GPTQ、GGML 和 NF4。 在上一篇文章中&#xff0c;我们介绍了简单的 8 位量化技术和出…

【开源】基于JAVA的校园失物招领管理系统

项目编号&#xff1a; S 006 &#xff0c;文末获取源码。 \color{red}{项目编号&#xff1a;S006&#xff0c;文末获取源码。} 项目编号&#xff1a;S006&#xff0c;文末获取源码。 目录 一、摘要1.1 项目介绍1.2 项目录屏 二、研究内容2.1 招领管理模块2.2 寻物管理模块2.3 系…

机器学习第4天:模型优化方法—梯度下降

文章目录 前言 梯度下降原理简述 介绍 可能的问题 批量梯度下降 随机梯度下降 基本算法 存在的问题 退火算法 代码演示 小批量梯度下降 前言 若没有机器学习基础&#xff0c;建议先阅读同一系列以下文章 机器学习第1天&#xff1a;概念与体系漫游-CSDN博客 机器学习…

企业视频数字人有哪些应用场景

来做个数字人吧&#xff0c;帮我干点活吧。 国内的一些数字人&#xff1a; 腾讯智影 腾讯智影数字人是一种基于人工智能技术的数字人物形象&#xff0c;具有逼真的外观、语音和行为表现&#xff0c;可以应用于各种场景&#xff0c;如新闻播报、文娱推介、营销、教育等。 幻…

基于蛾群算法优化概率神经网络PNN的分类预测 - 附代码

基于蛾群算法优化概率神经网络PNN的分类预测 - 附代码 文章目录 基于蛾群算法优化概率神经网络PNN的分类预测 - 附代码1.PNN网络概述2.变压器故障诊街系统相关背景2.1 模型建立 3.基于蛾群优化的PNN网络5.测试结果6.参考文献7.Matlab代码 摘要&#xff1a;针对PNN神经网络的光滑…

Redis篇---第二篇

系列文章目录 文章目录 系列文章目录前言一、为什么 使用 Redis 而不是用 Memcache 呢?二、为什么 Redis 单线程模型效率也能那么高?三、说说 Redis 的线程模型前言 前些天发现了一个巨牛的人工智能学习网站,通俗易懂,风趣幽默,忍不住分享一下给大家。点击跳转到网站,这…

android适配鸿蒙系统开发

将一个Android应用迁移到鸿蒙系统需要进行细致的工作&#xff0c;因为两者之间存在一些根本性的差异&#xff0c;涉及到代码、架构、界面等多个方面的修改和适配。以下是迁移工作可能涉及的一些主要方面&#xff0c;希望对大家有所帮助。北京木奇移动技术有限公司&#xff0c;专…

贝叶斯AB测试

AB测试是用来评估变更效果的有效方法&#xff0c;但很多时候会运行大量AB测试&#xff0c;如果能够在测试中复用之前测试的结果&#xff0c;将有效提升AB测试的效率和有效性。原文: Bayesian AB Testing[1] 随机实验&#xff0c;又称AB测试&#xff0c;是行业中评估因果效应的既…

PyTorch DataLoader整理函数详解【collate_fn】

DataLoader 是 PyTorch 中最常用的类之一。 而且&#xff0c;它是你首先学习的内容之一。 该类有很多参数&#xff0c;但最有可能的是&#xff0c;你将使用其中的大约三个参数&#xff08;dataset、shuffle 和 batch_size&#xff09;。 今天我想解释一下 collate_fn 的含义—根…