笔记六:单链表链表介绍与模拟实现

在他一生中,从来没有人能够像你们这样,以他的视角看待这个世界。

                                                                                                             ---------《寻找天堂》    

目录

文章目录

一、什么是链表?

二、为什么要使用链表?

三、 单链表介绍与使用

        3.1 单链表

         3.1.1 创建单链表节点

        3.1.2 单链表的头插、尾插

         单链表的尾插

         单链表的头插

3.1.3  单链表的头删、尾删

         单链表的尾删​编辑

         单链表的头删

 3.1.4 链表节点的查找

3.1.5 在指定位置插入数据

        在指定位置前插入数据

         在指定位置之后插入数据

 3.1.6 删除pos之后的节点

3.1.7 销毁链表

 四、完整代码

SList.h

SList.c


一、什么是链表?

        链表是一种物理存储上非连续,数据元素的逻辑顺序通过链表中的指针链接次序,实现的一种线性存储结构。链表由一系列节点(链表中每一个元素称为节点)组成,节点在运行时动态生成 (malloc),每个节点包括两个部分:

  •      一个是存储数据元素的数据域
  •      另一个是存储下一个节点地址的指针域

二、为什么要使用链表?

         使用链表用于解决动态数量的数据存储。比如说,我们要管理一堆票据,可能有一张,但也可能有一亿张。怎么办呢?申请一个几十G的大数组存储着吗?万一用户只有一百张票据要处理呢?内存较小拒绝运行?可申请少了又不够用啊……

        而且,用数组的话,删除然后添加票据,是每次删除让后面五百万张往前移一格呢、还是每次添加从头搜索空闲格子?如何区分空闲格子和值为0的数据?要进行区分的话是多占用空间呢还是占用数据值域?占用了值域会不会使得数据处理变得格外复杂?会不会一不小心就和正常数据混淆?万一拿来区分的字段在某个版本后废弃不用、或者扩充值域了呢?

        那么,链表这种东西就是个很有效的数据结构,可以很有效的管理这类不定量的数据。

三、 单链表介绍与使用

        3.1 单链表

        单链表的每个节点除了存放数据元素外,还要存储指向下一个节点的指针。与顺序表进行对比:

优点缺点
顺序表可随机存储,存储密度较高要求大片连续空间,改变容量不方便
单链表不要求大片连续空间,改变容量方便不可随机存取,要耗费一定空间存放指针

         3.1.1 创建单链表节点

typedef int SLTDataType;
//链表是由节点构成
//逻辑结构:一定连续;物理结构:不一定连续
//不会造成空间的浪费,由独立的节点构成
typedef struct SListNode {  //SList : single linked list 单链表
	SLTDataType data;  //  一个是存储数据元素的数据域
	struct SListNode* next;  //   另一个是存储下一个节点地址的指针域
}SLTNode;

        上面的结构体仅是单链表节点的定义,要创建一新的节点需要对里面的数据进行初始化:插入数值,节点指向的下一个节点为空

//创建一个节点
struct SListNode* SLTBuyNode(SLTDataType x) {
	SLTNode* newnode = (struct SListNode*)malloc(sizeof(struct SListNode));
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

        3.1.2 单链表的头插、尾插

         单链表的尾插

        这里传递的链表节点的参数,为什么是二级指针呢?

        在初始化过程中,需要修改头指针,因此要用到二级指针传递头指针的地址,这样才能修改头指针。这与普通变量类似,当需要修改普通变量的值,需传递其地址。使用二级指针,很方便就修改了传入的结点一级指针的值。 如果用一级指针,则只能通过指针修改指针所指内容,却无法修改指针的值,也就是指针所指的内存块。

//链表的尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x) { //一级指针传递的为结构体变量地址的值,二级指针接收的是指向结构体变量的指针的地址
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	//链表为空,新节点作为pphead
	if (*pphead == NULL) {
		*pphead = newnode;
		return;
	}
	//链表不为空,链表的尾指向新节点
	SLTNode* ptail = *pphead;
	while (ptail->next) {
		ptail = ptail->next;
	}
	ptail->next = newnode;
}
         单链表的头插

void SLTPushFront(SLTNode** pphead, SLTDataType x) {
	assert(pphead);
	//链表为空,新节点指向pphead,pphead再指向新节点;链表不为空,新节点指向pphead,pphead再指向新节点
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

         插入完数据后,想要打印以一下链表的数据,通过从头开始一个一个节点地访问数据,并打印,便实现了链表数据的打印。然后看看插入的情况;

//打印链表
void SLTPrint(SLTNode* phead) {
	SLTNode* pcur = phead;
	while (pcur) {
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

         尾插部分数据,观察打印的结果是否符合预期结果;发现运行结果与预期相符,所以链表的插入操作是没什么大问题的

3.1.3  单链表的头删、尾删

        如果链表为空,不需要删除;如果删除的是第一个结点,则需要将保存链表首地址的指针保存第一个结点的下一个结点的 地址 如果删除的是中间结点,则找到中间结点的前一个结点,让前一个结点的指针域保存这个结 点的后一个结点的地址即可 

         单链表的尾删
void SLTPopBack(SLTNode** pphead) {
	assert(pphead);
	//链表为空,不删除
	assert(*pphead); //指向第一个节点的地址
	//链表不为空,只有一个节点
	if ((*pphead)->next = NULL) {
		free(*pphead);
		*pphead = NULL;
		return;
	}
	//多个节点,释放最后一个节点,其前驱节点指向空
	SLTNode* ptail = *pphead;
	SLTNode* prev = NULL;
	while (ptail->next) {
		prev = ptail;
		ptail = ptail->next;
	}
	prev->next = NULL;
	free(ptail);
	ptail = NULL;
}
         单链表的头删

void SLTPopFront(SLTNode** pphead) {
	assert(pphead);
	//链表为空,不删除
	assert(*pphead);
	//链表不为空,释放最后一个节点,其前驱节点指向空
	SLTNode* pcur = *pphead;
	*pphead = pcur->next;
	pcur->next = NULL;
	free(pcur);
	pcur = NULL;
}

 3.1.4 链表节点的查找

         先对比第一个结点的数据域是否是想要的数据,如果是就直接返回,如果不是则继续查找下 一个结点,如果到达最后一个结点的时候都没有匹配的数据,说明要查找数据不存在

//查找
SLTNode* STLFind(SLTNode** pphead, SLTDataType x) {
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur) {
		if (pcur->data == x) {
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

3.1.5 在指定位置插入数据

        在指定位置前插入数据
//在指定位置前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
	assert(pphead);
	assert(pos);
	//头节点不能为空
	assert(*pphead);
	SLTNode* newnode = SLTBuyNode(x);
	//当pos节点指向 头节点时,进行头插
	if (*pphead == pos) {
		SLTPushFront(pphead, x);
		return;
	}
	//pos pphead不指向同一节点
	SLTNode* prev = *pphead;
	while (prev->next != pos) { //找到pos节点的前驱
		prev = prev->next;
	}
	newnode->next = pos;
	prev->next = newnode;
}
         在指定位置之后插入数据
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

 3.1.6 删除pos之后的节点

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos) {
	assert(pos);
	assert(pos->next);//pos后不为空
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	free(del);
	del = NULL;
}

3.1.7 销毁链表

        重新定义一个指针next,保存pcur指向节点的地址,然后next后移保存下一个节点的地址,然后释放pcur对应的节点,以此类推,直到pcur为NULL为止 

//销毁链表,由独立的节点构成,需要一个个销毁
void SListDestroy(SLTNode** pphead) {
	assert(pphead);
	assert(*pphead);
	SLTNode* pcur = *pphead;
	while (pcur) {
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

 四、完整代码

SList.h

 

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

//顺序表存在的一定的问题:
//1.顺序表的中间/头部的插入需要挪动数据
//2.扩容存在性能的消耗
//3.会有空间的浪费

typedef int SLTDataType;
//链表是由节点构成
//逻辑结构:一定连续;物理结构:不一定连续
//不会造成空间的浪费,由独立的节点构成
typedef struct SListNode {  //SList : single linked list 单链表
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

//链表的头插、尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);
void SLTPushFront(struct SListNode** pphead, SLTDataType x);

//链表的头删、尾删
void SLTPopBack(SLTNode** pphead);
void SLTPopFront(SLTNode** pphead);

//打印链表
void SLTPrint(SLTNode* phead);

//查找
SLTNode* STLFind(SLTNode** phead, SLTDataType x);

//在指定位置前插入数据
void SLTInsert(SLTNode** phead, SLTNode* pos,SLTDataType x);

//删除pos节点
void SLTErase(SLTNode** phead, SLTNode* pos);
//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);
//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);
//销毁链表
void SListDestroy(SLTNode** phead);

SList.c

#include"SList.h"

//创建一个节点
struct SListNode* SLTBuyNode(SLTDataType x) {
	SLTNode* newnode = (struct SListNode*)malloc(sizeof(struct SListNode));
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}


//打印链表
void SLTPrint(SLTNode* phead) {
	SLTNode* pcur = phead;
	while (pcur) {
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

//链表的头插、尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x) { //一级指针传递的为结构体变量地址的值,二级指针接收的是指向结构体变量的指针的地址
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	//链表为空,新节点作为pphead
	if (*pphead == NULL) {
		*pphead = newnode;
		return;
	}
	//链表不为空,链表的尾指向新节点
	SLTNode* ptail = *pphead;
	while (ptail->next) {
		ptail = ptail->next;
	}
	ptail->next = newnode;
}

void SLTPushFront(SLTNode** pphead, SLTDataType x) {
	assert(pphead);
	//链表为空,新节点指向pphead,pphead再指向新节点;链表不为空,新节点指向pphead,pphead再指向新节点
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}


//链表的头删、尾删
void SLTPopBack(SLTNode** pphead) {
	assert(pphead);
	//链表为空,不删除
	assert(*pphead); //指向第一个节点的地址
	//链表不为空,只有一个节点
	if ((*pphead)->next = NULL) {
		free(*pphead);
		*pphead = NULL;
		return;
	}
	//多个节点,释放最后一个节点,其前驱节点指向空
	SLTNode* ptail = *pphead;
	SLTNode* prev = NULL;
	while (ptail->next) {
		prev = ptail;
		ptail = ptail->next;
	}
	prev->next = NULL;
	free(ptail);
	ptail = NULL;
}
void SLTPopFront(SLTNode** pphead) {
	assert(pphead);
	//链表为空,不删除
	assert(*pphead);
	//链表不为空,释放最后一个节点,其前驱节点指向空
	SLTNode* pcur = *pphead;
	*pphead = pcur->next;
	pcur->next = NULL;
	free(pcur);
	pcur = NULL;
}

//查找
SLTNode* STLFind(SLTNode** pphead, SLTDataType x) {
	assert(pphead);
	SLTNode* pcur = *pphead;
	while (pcur) {
		if (pcur->data == x) {
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

//在指定位置前插入数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x) {
	assert(pphead);
	assert(pos);
	//头节点不能为空
	assert(*pphead);
	SLTNode* newnode = SLTBuyNode(x);
	//当pos节点指向 头节点时,进行头插
	if (*pphead == pos) {
		SLTPushFront(pphead, x);
		return;
	}
	//pos pphead不指向同一节点
	SLTNode* prev = *pphead;
	while (prev->next != pos) { //找到pos节点的前驱
		prev = prev->next;
	}
	newnode->next = pos;
	prev->next = newnode;
}

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos) {
	assert(pphead);
	assert(pos);
	//当pos节点指向 头节点时,进行头删
	if (*pphead == pos) {
		SLTPopFront(pphead);
		return;
	}
	//pos pphead不指向同一节点
	SLTNode* prev = *pphead;
	while (prev->next != pos) { //找到pos节点的前驱
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}


//在指定位置之后插入数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x) {
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos) {
	assert(pos);
	assert(pos->next);//pos后不为空
	SLTNode* del = pos->next;
	pos->next = pos->next->next;
	//pos->next = del->next;
	free(del);
	del = NULL;
}

//销毁链表,由独立的节点构成,需要一个个销毁
void SListDestroy(SLTNode** pphead) {
	assert(pphead);
	assert(*pphead);
	SLTNode* pcur = *pphead;
	while (pcur) {
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

使用Modelsim手动仿真

FPGA设计流程 在设计输入之后,设计综合前进行 RTL 级仿真,称为综合前仿真,也称为前仿真或 功能仿真。前仿真也就是纯粹的功能仿真,主旨在于验证电路的功能是否符合设计要求,其特点是不考虑电路门延迟与线延迟。在完成一个设计的代码编写工作之后,可以直接对代码进行仿真,…

Docker搭建Redis哨兵模式【一主两从三哨兵】

Docker搭建Redis哨兵模式 系统: CentOS 7 Dockder 版本: VMware虚拟机 网络适配器 网络连接 桥接模式:直接连接物理网络查看IP命令 ip addr一、哨兵模式概述 1. 官方文档与关联博客 官方文档:https://redis.io/docs/latest/operate/oss_and_stack/management/sentinel关联博…

(更新完)LPZero: Language Model Zero-cost Proxy Search from Zero

LPZero代码 摘要 神经架构搜索 (NAS) 有助于自动执行有效的神经网络搜索&#xff0c;同时需要大量的计算资源&#xff0c;尤其是对于语言模型。零样本 NAS 利用零成本 (ZC) 代理来估计模型性能&#xff0c;从而显着降低计算需求。然而&#xff0c;现有的 ZC 代理严重依赖于深…

【互联网性能指标】QPS/TPS/PV/UV/IP/GMV/DAU/MAU/RPS

&#x1f4d5;我是廖志伟&#xff0c;一名Java开发工程师、《Java项目实战——深入理解大型互联网企业通用技术》&#xff08;基础篇&#xff09;、&#xff08;进阶篇&#xff09;、&#xff08;架构篇&#xff09;清华大学出版社签约作家、Java领域优质创作者、CSDN博客专家、…

【Linux docker】关于docker启动出错的解决方法。

无论遇到什么docker启动不了的问题 就是 查看docker状态sytemctl status docker查看docker日志sudo journalctl -u docker.service查看docker三个配置文件&#xff08;可能是配置的时候格式错误&#xff09;&#xff1a;/etc/docker/daemon.json&#xff08;如果存在&#xf…

拉取gitlab项目时出现500的错误的权限问题

title: 拉取gitlab项目时出现500的错误的权限问题 date: 2025-03-10 18:09:08 tags: gitlabgit拉取gitlab项目时出现500的错误的权限问题 Gitlab克隆代码**我遇到的问题错误**:**问题解决步骤**:1、确定你可以浏览器访问到项目页面2、确定你的邮箱或账号已添加,有权限可以拉…

MobileBERT: 一种适用于资源有限设备的紧凑型任务无关BERT

摘要 近年来&#xff0c;自然语言处理&#xff08;NLP&#xff09;通过使用具有数亿参数的巨大预训练模型取得了巨大成功。然而&#xff0c;这些模型存在模型体积庞大和延迟高的问题&#xff0c;使得它们无法部署到资源有限的移动设备上。在本文中&#xff0c;我们提出了Mobil…

【C】初阶数据结构9 -- 直接插入排序

前面我们学习了数据结构二叉树&#xff0c;接下来我们将开启一个新的章节&#xff0c;那就是在日常生活中经常会用到的排序算法。 所谓排序算法就是给你一堆数据&#xff0c;让你从小到大&#xff08;或从大到小&#xff09;的将这些数据排成一个有序的序列&#xff08;这些数据…

OpenPose初体验

最近机器人的热度有点高&#xff0c;想着要做些应用技术储备&#xff0c;偶然的机会发现了OpenPose&#xff0c;就从它开始吧&#xff01;OpenPose是由卡内基梅隆大学开发的开源实时多人姿态估计库。它基于深度学习算法&#xff0c;能精确识别图像或视频中的人体姿态&#xff0…

从0开始的操作系统手搓教程33:挂载我们的文件系统

目录 代码实现 添加到初始化上 上电看现象 挂载分区可能是一些朋友不理解的——实际上挂载就是将我们的文件系统封装好了的设备&#xff08;硬盘啊&#xff0c;SD卡啊&#xff0c;U盘啊等等&#xff09;&#xff0c;挂到我们的默认分区路径下。这样我们就能访问到了&#xff…

游戏辅助技术培训班教程【A001-初级班】

课程概述&#xff1a; 本教程为游戏辅助技术培训班的初级班课程&#xff0c;本章为第二阶段&#xff0c;旨在帮助学员系统掌握游戏辅助技术的核心技能。课程内容从C/C编程基础到高级内存操作、代码注入、DLL注入及MFC编程&#xff0c;全面覆盖游戏辅助开发的关键知识点。 课程…

day1 redis登入指令

[rootlocalhost data]# redis-cli -h ip -p 6379 -a q123q123 Warning: Using a password with -a or -u option on the command line interface may not be safe. ip:6379> 以上&#xff0c; Bigder

vue3深入组件——依赖注入

一、场景介绍:一般父子间信息传递是通过props,但是一个多层嵌套的组件,必须将其沿着组件逐级的传递下去,这就是props的逐级透传。 二、上述情况下,就需要用到provide 和 inject;一个父组件相对于其所有的后代组件,会作为依赖提供者。任何后代的组件树,无论层级有多…

VUE3开发-9、axios前后端跨域问题解决方案

VUE前端解决跨域问题 前端页面需要改写 如果无效&#xff0c;记得重启服务器 后端c#解决跨域问题 前端js取值&#xff0c;后端c#跨域_c# js跨域-CSDN博客

国产编辑器EverEdit - 设置文件类型关联为EverEdit

1 设置-文件关联 1.1 应用场景 文件关联是指在文件管理器中双击某类型的文件&#xff0c;操作系统自动调用可以打开该文件的应用程序&#xff0c;比如&#xff1a;用户双击XXXX.txt文件&#xff0c;系统默认会使用记事本打开该文件。   由于各行各业都会定义特有的文件类型&…

【测试框架篇】单元测试框架pytest(4):assert断言详解

一、前言 用例三要素之一就是对预期结果的断言。 何为断言&#xff1f;简单来说就是实际结果和期望结果去对比&#xff0c;符合预期就测试pass&#xff0c;不符合预期那就测试 failed。断言内容就是你要的预期结果。断言包含对接口响应内容做断言、也包含对落DB的数据做断言。…

十七、从0开始卷出一个新项目之瑞萨RZN2L定时器(GPT)+DMA生成PWM的运动控制

一、概述 嵌入式科普(34)通过对比看透DMA的本质 分享瑞萨RZN2L使用DMA生成PWM的运动控制的例程源码 rzn2l必要的外设资源&#xff1a; rzn2l拥有32-bit timer General PWM Timer (GPT) with 18 channels CPU、GPT最高频率400Mhz DMAC0 and DMAC1 8 channels 8 channels 还…

CI/CD—Jenkins配置Poll SCM触发自动构建

Poll SCM简介 在 Jenkins 等持续集成工具中&#xff0c;“Poll SCM” 是一种用于轮询软件配置管理&#xff08;SCM&#xff09;系统以检查代码变更的机制&#xff0c;以下是对它的详细介绍&#xff1a; 作用 “Poll SCM” 允许 Jenkins 定期检查指定的 SCM 系统&#xff08;如 …

Javaweb后端文件上传@value注解

文件本地存储磁盘 阿里云oss准备工作 阿里云oss入门程序 要重启一下idea&#xff0c;上面有cmd 阿里云oss案例集成 优化 用spring中的value注解

命名管道的创建和通信实现

目录 命名管道的创建 使用函数创建命名管道的通信 预备创建 makefile设计 server.hpp设计 clent.hpp设计 comm.hpp设计 server.cc设计 clent.cc设计 测试运行 今天我们来学习命名管道 由于匿名管道&#xff08;pipe()&#xff09;无法在两个毫不相干的进程之间进行通…