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

news2025/4/22 1:40:03

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

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

目录

文章目录

一、什么是链表?

二、为什么要使用链表?

三、 单链表介绍与使用

        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;
}

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

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

相关文章

坐落于杭州的电商代运营公司品融电商

坐落于杭州的电商代运营公司品融电商 在中国电商行业蓬勃发展的浪潮中&#xff0c;品融电商&#xff08;PINKROON&#xff09;作为一家扎根杭州的新锐品牌管理公司&#xff0c;凭借其独特的全域增长方法论和实战经验&#xff0c;迅速崛起为行业标杆。自2020年成立以来&#x…

【算法学习之路】8.栈和队列

栈和队列 前言一.简介二.题目12 前言 我会将一些常用的算法以及对应的题单给写完&#xff0c;形成一套完整的算法体系&#xff0c;以及大量的各个难度的题目&#xff0c;目前算法也写了几篇&#xff0c;题单正在更新&#xff0c;其他的也会陆陆续续的更新&#xff0c;希望大家点…

OpenMCU(三):STM32F103 FreeRTOS移植

概述 本文主要描述了STM32F103移植FreeRTOS的简要步骤。移植描述过程中&#xff0c;忽略了Keil软件的部分使用技巧。默认读者熟练使用Keil软件。本文的描述是基于OpenMCU_RTOS这个工程&#xff0c;该工程已经下载放好了移植STM32F103 FreeRTOS的所有文件 OpenMCU_RTOS工程的愿景…

大数据 spark hive 总结

Apache Spark 简介 是一个开源的统一分析引擎&#xff0c;专为大规模数据处理而设计。它提供了高级API&#xff0c;支持Java、Scala、Python和R语言&#xff0c;并且包含了一个优化过的执行引擎&#xff0c;该引擎支持循环计算&#xff08;如机器学习算法&#xff09;和交互式…

小程序开发总结

今年第一次帮别人做小程序。 从开始动手到完成上线&#xff0c;一共耗时两天。AI 让写代码变得简单、高效。 不过&#xff0c;小程序和 Flutter 等大厂开发框架差距实在太大&#xff0c;导致我一开始根本找不到感觉。 第一&#xff0c;IDE 不好用&#xff0c;各种功能杂糅在…

QLoggingCategory类使用

QLoggingCategory类使用 QLoggingCategory的概述 QLoggingCategory是Qt的日志策略类&#xff1b;可以通过声明不同的日志策略对象来输出不同的日志信息。打印信息类型如下&#xff1a;宏 Q_DECLARE_LOGGING_CATEGORY(name) 定义一个返回QLoggingCategory对象函数&#xff0c;…

GPU加速生信分析-宏基因组MAG去污染

Deepurify利用多模态深度语言模型来过滤污染的基因组&#xff0c;从而提高了宏基因组组装基因组&#xff08;MAGs&#xff09;的质量&#xff0c;并且可以利用GPU加速。 宏基因组组装的基因组 &#xff08;MAG&#xff09; 为使用宏基因组测序数据探索微生物暗物质提供了有价值…

数据结构(蓝桥杯常考点)

数据结构 前言&#xff1a;这个是针对于蓝桥杯竞赛常考的数据结构内容&#xff0c;基础算法比如高精度这些会在下期给大家总结 数据结构 竞赛中&#xff0c;时间复杂度不能超过10的7次方&#xff08;1秒&#xff09;到10的8次方&#xff08;2秒&#xff09; 空间限制&#x…

从0到1入门Linux

一、常用命令 ls 列出目录内容 cd切换目录mkdir创建新目录rm删除文件或目录cp复制文件或目录mv移动或重命名文件和目录cat查看文件内容grep在文件中查找指定字符串ps查看当前进程状态top查看内存kill终止进程df -h查看磁盘空间存储情况iotop -o直接查看比较高的磁盘读写程序up…

React:类组件(中)

dangerouslySetInnerHTML React写进{}内的东西&#xff0c;不允许被当作代码块解析&#xff0c;是为了防止xss攻击和代码注入 XSS&#xff08;跨站脚本攻击&#xff0c;Cross-Site Scripting&#xff09; 是一种常见的安全漏洞&#xff0c;攻击者通过注入恶意脚本到网页中&…

第六次CCF-CSP认证(含C++源码)

第六次CCF-CSP认证 数位之和&#xff08;easy&#xff09;思路及AC代码遇到的问题 开心消消乐&#xff08;easy&#xff09;思路及AC代码 画图&#xff08;mid&#xff09;思路及AC代码 数位之和&#xff08;easy&#xff09; 题目链接 思路及AC代码 既然题目要求我们输出各位…

SpringBoot 如何调用 WebService 接口

前言 调用WebService接口的方式有很多&#xff0c;今天记录一下&#xff0c;使用 Spring Web Services 调用 SOAP WebService接口 一.导入依赖 <!-- Spring Boot Web依赖 --><dependency><groupId>org.springframework.boot</groupId><artifactId…

算法 之 树形dp 树的中心、重心

文章目录 重心实践题目小红的陡峭值 在树的算法中&#xff0c;求解树的中心和重心是一类十分重要的算法 求解树的重心 树的重心的定义&#xff1a;重心是树中的一个节点&#xff0c;如果将这个点删除后&#xff0c;剩余各个连通块中点数的最大值最小&#xff0c;那么这个节点…

Docker 配置镜像源

》》Daemon {"registry-mirrors": ["https://docker.1ms.run","https://docker.xuanyuan.me"] }》》》然后在重新 docker systemctl restart docker

Linux 离线部署Ollama和DeepSeek-r1模型

都在复制粘贴联网状态下linux部署deepseek&#xff0c;离线状态下需要下载Ollama和DeepSeek模型&#xff0c;然后将下载包上传到linux中。 1、下载Ollama https://github.com/ollama/ollama/releases 注意&#xff1a;如果CentOS7建议安装V0.5.11版本&#xff0c;V0.5.13需要…

SQLAlchemy系列教程:如何执行原生SQL

Python中的数据库交互提供了高级API。但是&#xff0c;有时您可能需要执行原始SQL以提高效率或利用数据库特定的特性。本指南介绍在SQLAlchemy框架内执行原始SQL。 在SQLAlchemy中执行原生SQL SQLAlchemy虽然以其对象-关系映射&#xff08;ORM&#xff09;功能而闻名&#xff…

RuleOS:区块链开发的“新引擎”,点燃Web3创新之火

RuleOS&#xff1a;区块链开发的“新引擎”&#xff0c;点燃Web3创新之火 在区块链技术的浪潮中&#xff0c;RuleOS宛如一台强劲的“新引擎”&#xff0c;为个人和企业开发去中心化应用&#xff08;DApp&#xff09;注入了前所未有的动力。它以独特的设计理念和强大的功能特性&…

【编译器】VSCODE烧录ESP32-C3——xiaozhi智能聊天机器人固件

【编译器】VSCODE烧录ESP32-C3——xiaozhi智能聊天机器人固件 文章目录 [TOC](文章目录) 前言一、方法一&#xff1a;使用固件烧录工具1. 安装CH340驱动2. 打开FLASH_DOWNLOAD文件3. 选择芯片类型和烧录方式4. 选择烧录文件5. 参数配置 二、方法二&#xff1a;VSCODE导入工程1.…

显式 GC 的使用:留与去,如何选择?

目录 一、什么是显式 GC&#xff1f; &#xff08;一&#xff09; 垃圾回收的基本原理 &#xff08;二&#xff09;显式 GC 方法和行为 1. System.gc() 方法 2. 显式 GC 的行为 &#xff08;三&#xff09;显式 GC 的使用场景与风险 1. JVM 如何处理显式 GC 2. 显式 GC…

SpringMVC概述以及入门案例

目录 SpringMVC概述 为什么需要Spring MVC&#xff1f; SpringMVC入门 工作流程分析 SpringMVC概述 SpringMVC技术与Servlet技术功能等同&#xff0c;均属于Web层开发技术。SpringMVC是一种基于java实现MVC模型的轻量级Web框架。 为什么需要Spring MVC&#xff1f; 在传统J…