【(数据结构)— 单链表的实现】

news2025/1/10 16:50:09

(数据结构)— 单链表的实现

  • 一.链表的概念及结构
  • 二.单链表的实现
    • 2.1单链表头文件——功能函数的定义
    • 2.2单链表源文件——功能函数的实现
    • 2.3 单链表源文件——功能的测试
    • 2.4单链表测试结果运行展示
  • 3. 链表的分类

一.链表的概念及结构

概念: 链表是⼀种 物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。
在这里插入图片描述

链表的结构跟⽕⻋⻋厢相似,淡季时⻋次的⻋厢会相应减少,旺季时⻋次的⻋厢会额外增加⼏节。只需要将⽕⻋⾥的某节⻋厢去掉/加上,不会影响其他⻋厢,每节⻋厢都是独⽴存在的。
⻋厢是独⽴存在的,且每节⻋厢都有⻋⻔。想象⼀下这样的场景,假设每节⻋厢的⻋⻔都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带⼀把钥匙的情况下如何从⻋头⾛到⻋尾?

最简单的做法:每节⻋厢⾥都放⼀把下⼀节⻋厢的钥匙。

在链表⾥,每节“⻋厢”是什么样的呢?

在这里插入图片描述

与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/节点”
节点的组成主要有两个部分:当前节点要保存的数据和保存下⼀个节点的地址(指针变量)。
图中指针变量 plist保存的是第⼀个节点的地址,我们称plist此时“指向”第⼀个节点,如果我们希望plist“指向”第⼆个节点时,只需要修改plist保存的内容为0x0012FFA0。
为什么还需要指针变量来保存下⼀个节点的位置?
链表中每个节点都是独⽴申请的(即需要插⼊数据时才去申请⼀块节点的空间),我们需要通过指针变量来保存下⼀个节点位置才能从当前节点找到下⼀个节点。
结合前⾯学到的结构体知识,我们可以给出每个节点对应的结构体代码:
假设当前保存的节点为整型:

struct SListNode
{
 int data; //节点数据
 struct SListNode* next; //指针变量⽤保存下⼀个节点的地址
};

当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数据,也需要保存下⼀个节点的地址(当下⼀个节点为空时保存的地址为空)。
当我们想要从第⼀个节点⾛到最后⼀个节点时,只需要在前⼀个节点拿上下⼀个节点的地址(下⼀个节点的钥匙)就可以了。

给定的链表结构中,如何实现节点从头到尾的打印?

在这里插入图片描述

在这里插入图片描述

思考:当我们想保存的数据类型为字符型、浮点型或者其他⾃定义的类型时,该如何修改?
补充说明:
1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续
2、节点⼀般是从堆上申请的
3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续

二.单链表的实现

2.1单链表头文件——功能函数的定义

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

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

//创建几个节点组成链表,并且打印链表
//打印
void SLNPrint(SLNode* phead);

//尾插
void SLNPushBack(SLNode** pphead, SLDatatype x);
//头插
void SLNPushFront(SLNode** pphead, SLDatatype x);
//尾删
void SLNPopBack(SLNode** pphead); 
//头删
void SLNPopFront(SLNode** pphead);
//在指定位置插入删除
// 
//查找指定的pos
SLNode* SLNFind(SLNode** pphead, SLDatatype x);
//在指定位置之前插入
void SLNInsrt(SLNode** pphead, SLNode* pos, SLDatatype x);
//在指定位置之后插入删除
void SLNInsrtAfter(SLNode* pos, SLDatatype x);
//删除指定位置的数据
void SLNErase(SLNode** pphead, SLNode* pos);
//删除指定位置之后的数据
void SLNEraseAfter(SLNode* pos);
//销毁链表
void SLNDestroy(SLNode** pphead);

2.2单链表源文件——功能函数的实现

SList.c
#define _CRT_SECURE_NO_WARNINGS 1
#include"SList.h"
//打印
void SLNPrint(SLNode* phead)
{
	//循环打印
	//可以用phead直接来访问,但是注意,当代码走到
	//第16行的时候,此时phead已经变成NULL了
	//若代码没写完,我还要继续使用指向第一个节点的地址时,这时我就
	//找不到第一个节点的地址
	//所以为了避免第一个节点被更改,要定义一个新的来代替
	SLNode* pcur = phead;
	while (pcur)
	{
		printf("%d ->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}
//创建新的节点
SLNode* SLByNode(SLDatatype x)
{
	//开辟新的空间
	SLNode* node = (SLNode*)malloc(sizeof(SLNode));
	node->data = x;
	node->next = NULL;
	return node;
}
//尾插
void SLNPushBack(SLNode** pphead, SLDatatype x)
{
	SLNode* node = SLByNode(x);
	//判断链表是否为空,如果为空,直接插入
	if (*pphead == NULL)
	{
		*pphead = node;
		return;
	}

	//说明链表不为空 ,找尾
	SLNode* pcur = *pphead;
	while (pcur->next != NULL)
	{
		pcur = pcur->next;
	}
	pcur->next = node;

}

//头插
void SLNPushFront(SLNode** pphead, SLDatatype x)
{
	//创建新的节点
	SLNode* node = SLByNode(x);
	//让新的节点跟头节点连起来
	node->next = *pphead;
	//再让新节点成为头节点
	*pphead = node;
	
}

//尾删
void SLNPopBack(SLNode** pphead)
{
	//先判断链表是否为空,如果为空就无法进行删除操作
	assert(pphead);
	//还要判断第一个节点不能为空
	assert(*pphead);
	//只有一个节点的情况
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
		return;
	}
	//有多个节点的情况
	//先找到尾节点/尾节点的前一个节点
	SLNode* ptail = *pphead;
	SLNode* prev = NULL;
	while (ptail->next)
	{
		prev = ptail;
		ptail = prev->next;
	}
	//让尾节点的前一个节点指向不再指向尾节点,而是指向尾节点的下一个节点 
	prev->next = ptail->next;
	//然后在删除尾节点
	free(ptail);
	//为什么要将ptaii置为空指针?代码后面明明没有再使用ptail,因为打印链表的代码有判断节点的地址是否为空
	ptail = NULL;

}

void SLNPopFront(SLNode** pphead)
{
	//判断链表不能为空
	assert(pphead);
	//判断头节点不能为空
	assert(*pphead);
	SLNode* del = *pphead;
	*pphead = (*pphead)->next;
	free(del);
	//出于代码规范
	del = NULL;

}


//查找指定的pos
SLNode* SLNFind(SLNode** pphead, SLDatatype x)
{
	assert(pphead);
	SLNode* pcur = *pphead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//出循环,代表没找到
	return NULL;
}
//在指定位置之前插入
void SLNInsrt(SLNode** pphead, SLNode* pos, SLDatatype x)
{
	SLNode* node = SLByNode(x);
	assert(pphead);
	assert(*pphead);
	//只有一个节点的情况 == pos就是头节点,直接进行头插
	if (pos == *pphead)
	{
		node->next = *pphead;
		*pphead = node;
		return;
	}
	//有多个节点的情况
	//找pos的前一个节点
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{

		prev = prev->next;
	}
	//这里的插入节点的处理顺序可以调换
	prev->next = node;
	node->next = pos;

}

//在指定的位置之后插入
void SLNInsrtAfter(SLNode* pos, SLDatatype x)
{
	assert(pos);
	SLNode* node = SLByNode(x);
	// node  pos  node->next
	node->next = pos->next;
	pos->next = node;
}

//删除指定位置的节点
void SLNErase(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;
	
}
//删除指定位置之后的节点
void SLNEraseAfter(SLNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

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

2.3 单链表源文件——功能的测试

test.c
#include"SList.h"



void sllist()
{
	SLNode* node1 = (SLNode*)malloc(sizeof(SLNode));
	node1->data = 1;
	SLNode* node2 = (SLNode*)malloc(sizeof(SLNode));
	node2->data = 2;
	SLNode* node3 = (SLNode*)malloc(sizeof(SLNode));
	node3->data = 3;
	SLNode* node4 = (SLNode*)malloc(sizeof(SLNode));
	node4->data = 4;


	node1->next = node2;
	node2->next = node3;
	node3->next = node4;
	node4->next = NULL;


	SLNode* plist = node1;
	SLNPrint(plist);
	
	

}
void sllist01()
{
	SLNode* plist = NULL;
	//尾插
	SLNPushBack(&plist, 1);
	SLNPushBack(&plist, 2);
	SLNPushBack(&plist, 3);
	SLNPushBack(&plist, 4);
	//头插 
	/*SLNPushFront(&plist, 1);
	SLNPushFront(&plist, 2);
	SLNPushFront(&plist, 3);
	SLNPushFront(&plist, 4);*/
	//尾删
	//SLNPopBack(&plist);
	//头删
	//SLNPopFront(&plist);

	//在指定位置前插入
	//查找指定的pos
	SLNode* find = SLNFind(&plist, 3);
	//SLNInsrt(&plist, find, 11);
	//在指定位置之后插入
	//SLNInsrtAfter(find, 5);
	//删除指定位置的节点
	//SLNErase(&plist, find);
	//删除指定位置之后的节点
	//SLNEraseAfter(find);
	//销毁链表
	SLNDestroy(&plist);
	SLNPrint(plist);//打印链表
}

2.4单链表测试结果运行展示

1.尾部插入
在这里插入图片描述

2.头部插入
在这里插入图片描述

3.尾部/头部删除
在这里插入图片描述

4.在指定位置前插入
在这里插入图片描述

5.在指定位置后插入
在这里插入图片描述

6.删除指定位置的节点
在这里插入图片描述

7.删除指定位置之后的节点
在这里插入图片描述

8.销毁链表
在这里插入图片描述

3. 链表的分类

链表的结构⾮常多样,以下情况组合起来就有8种(2 x 2 x 2)链表结构:
在这里插入图片描述

链表说明:
在这里插入图片描述
在这里插入图片描述

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:
单链表和双向带头循环链表
1. 无头单向非循环链表:结构简单,⼀般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
2. 带头双向循环链表:结构最复杂,⼀般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使⽤代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

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

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

相关文章

电子学会2023年9月青少年软件编程(图形化)等级考试试卷(三级)真题,含答案解析

青少年软件编程(图形化)等级考试试卷(三级) 分数:100 题数:31 一、单选题(共18题,共50分) 1.运行下面程序后,角色的x坐标值是?( ) A. 100 B. 90

osWorkflow-1——osWorkflow官方例子部署启动运行(版本:OSWorkflow-2.8.0)

osWorkflow-1——osWorkflow官方例子部署启动运行&#xff08;版本&#xff1a;OSWorkflow-2.8.0&#xff09; 1. 前言——准备工作1.1 下载相关资料1.2 安装翻译插件 2. 开始搞项目2.1 解压 .zip文件2.2 简单小测&#xff08;war包放入tomcat&#xff09;2.3 导入项目到 IDE、…

137.【SpringCloud-快速搭建】

微服务框架搭建 (一)、SpringCloud-Parent1.创建一个SpringBoot项目2.导入我们的依赖 (二)、SpringCloud-API (实体类)1.创建一个SpringBoot项目2.导入我们的依赖3.创建我们的实体类 (三)、SpringCloud-dept (业务A)1.创建一个SpringBoot项目2.导入我们的依赖3.配置我们的配置信…

力扣第39题 组合总和 c++ 回溯剪枝题

题目 39. 组合总和 中等 相关标签 数组 回溯 给你一个 无重复元素 的整数数组 candidates 和一个目标整数 target &#xff0c;找出 candidates 中可以使数字和为目标数 target 的 所有 不同组合 &#xff0c;并以列表形式返回。你可以按 任意顺序 返回这些组合。 cand…

故障诊断开源代码推荐 | 轴承故障诊断迁移学习综述,免费获取!

故障诊断开源代码推荐 | 轴承故障诊断迁移学习综述&#xff0c;免费获取&#xff01; 1 论文基本信息2 摘要3 目录4 引言5 定义与故障诊断流程6 开源轴承故障数据集7 轴承故障的迁移学习诊断8 定性分析9 机遇与挑战10 总结11 README.md 针对故障诊断领域开源代码较少&#xff0…

身份证OCR识别:黑科技助力证件信息快速提取

身份证OCR识别是一种基于光学字符识别&#xff08;Optical Character Recognition&#xff0c;OCR&#xff09;技术的自动化身份证信息提取和识别过程。它通过将纸质身份证上的文字、数字、图像等内容转换为可电子化编辑和处理的数据形式&#xff0c;实现了对身份证信息的快速、…

Apache IoTDB v1.2.2 发布|增加 flink-sql-connector、tsfile 文件级级联传输等功能

Release Announcement Version 1.2.2 Apache IoTDB v1.2.2 已经发布&#xff0c;主要增加了 flink-sql-iotdb-connector 插件、tsfile 文件级级联传输、count_time 聚合函数等新特性&#xff0c;优化了 Limit & Offset 查询性能、ConfigNode 重启逻辑等&#xff0c;并提升…

Vue.js2+Cesium1.103.0 十三、通过经纬度查询 GeoServer 发布的 wms 服务下的 feature 对象的相关信息

Vue.js2Cesium1.103.0 十三、通过经纬度查询 GeoServer 发布的 wms 服务下的 feature 对象的相关信息 Demo <template><divid"cesium-container"style"width: 100%; height: 100%;"><div style"position: absolute;z-index: 999;bott…

Unity AI Muse 基础教程

Unity AI Muse 基础教程 Unity AI 内测资格申请Unity 项目Package ManagerMuse Sprite 安装Muse Texture 安装 Muse Sprite 基础教程什么是 Muse Sprite打开 Muse Sprite 窗口Muse Sprite 窗口 参数Muse Sprite Generations 窗口 参数Muse Sprite Generations 窗口 画笔Muse Sp…

小程序开发平台源码系统+ 带前后端完整搭建教程

大家好&#xff0c;给大家分享一个小程序开发平台源码系统。这款小程序开发平台中有很多功能&#xff0c;今天主要来给大家介绍一下洗车行业小程序制作的功能。以下是部分核心代码图&#xff1a; 系统特色功能&#xff1a; LBS定位&#xff1a;小程序能够自动显示附近的共享洗…

稀里糊涂的转义

一、前言 前段时间挖机ERP系统出现一个问题&#xff0c;表单录入客户名称是 L & Q International Trading Limited&#xff0c;然后页面展示变成 L &amp; Q International Trading Limited&#xff0c;即字符 &变成了&amp&#xff1b;。 二、为什么要转义 &…

Vue-3.5vuex分模块

模块module 由于vuex使用单一状态树&#xff0c;应用的所有状态会集中到一个比较大的对象。当应用变得非常复杂时&#xff0c;store对象就有可能变得相当臃肿。&#xff08;当项目变得越来越大的时候&#xff0c;Vuex会变得越来越难以维护&#xff09; 模块创建 先在store文…

提升协作效率:钉钉流程与低代码平台的无缝对接

摘要&#xff1a;本文由葡萄城技术团队原创并首发。转载请注明出处&#xff1a;葡萄城官网&#xff0c;葡萄城为开发者提供专业的开发工具、解决方案和服务&#xff0c;赋能开发者。 前言 钉钉作为目前很多企业都在使用的移动办公平台&#xff0c;提供了很多常用的OA审批功能&…

剑指智能驾驶,智己LS6胜算几何?

监制 | 何玺 排版 | 叶媛 10月12日&#xff0c;IM智己旗下的新车智己LS6宣布上市。 新车型搭载尖端科技多项&#xff0c;其中以“全画幅数字驾舱屏”、和城市高阶智能辅助驾驶为核心的智驾技术&#xff0c;更是引来众多用户关注。 01 新能源新卷王智己LS6 智己LS6一发布就…

浅谈智能照明控制系统在体育馆中的应用

【摘要】在社会经济日益发展的今天&#xff0c;人们的物质文化水平都有着不同程度上的提高。与此同时人们更加追求高质量的工作和生活环境。照明在现代化的多功能体育场馆中是非常重要的一个环节。智能化的照明控制系统能够根据环境的变化以及客户的需求等条件来自动调节照明系…

解密推荐系统:用Redis解决特征存储问题

文章目录 &#x1f31f; 线上服务&#xff1a;如何在线上提供高并发的推荐服务&#xff1f;&#x1f34a; 1. 架构设计&#x1f34a; 2. 负载均衡&#x1f34a; 3. 高并发处理&#x1f34a; 4. 监控和调整&#x1f34a; 5. 数据安全 &#x1f31f; 存储模块&#xff1a;如何用R…

1.15.C++项目:仿muduo库实现并发服务器之HttpRequest和HttpResponse模块的设计

文章目录 一、HttpRequest模块二、HttpResponse模块三、实现思想&#xff08;一&#xff09;功能&#xff08;二&#xff09;意义 四、代码 一、HttpRequest模块 二、HttpResponse模块 三、实现思想 &#xff08;一&#xff09;功能 HttpRequest模块 存储HTTP请求信息 接收到…

基于若依和flowable6.7.2的ruoyi-nbcio流程管理系统正式发布

更多ruoyi-nbcio功能请看演示系统 gitee源代码地址 前后端代码&#xff1a; https://gitee.com/nbacheng/ruoyi-nbcio 演示地址&#xff1a;RuoYi-Nbcio后台管理系统 项目概要 本项目基于 RuoYi-Flowable-Plus 进行二次开发&#xff0c;从nbcio-boot(https://gitee.com/nb…

maven 常用知识速记

创建项目 maven archetype:generate依赖范围 有如下依赖示例&#xff1a; <dependency><groupId>junit</groupId><artifactId>junit</artifactId><version>4.7</version><scope>test</scope> </dependency>其中…

Unity中用序列化和反序列化来保存游戏进度

[System.Serializable]标记类 序列化 [System.Serializable]是一个C#语言中的属性&#xff0c;用于标记类&#xff0c;表示该类的实例可以被序列化和反序列化。序列化是指将对象转换为字节流的过程&#xff0c;以便可以将其保存到文件、数据库或通过网络传输。反序列化则是将字…