【数据结构】单链表——单链表的定义及基本操作的实现(头插、尾插、头删、尾删、任意位置的插入与删除)

news2025/7/18 9:01:21

🧑‍💻作者: @情话0.0
📝专栏:《数据结构》
👦个人简介:一名双非编程菜鸟,在这里分享自己的编程学习笔记,欢迎大家的指正与点赞,谢谢!

在这里插入图片描述

单链表

  • 前言
  • 一、单链表的定义
  • 二、单链表上基本操作的实现(不带头结点)
    • 单链表中结点类型的描述如下:
    • 1. 动态申请一个节点
    • 2. 单链表尾插
    • 3. 单链表尾删
    • 4. 单链表头插
    • 5. 单链表头删
    • 6. 单链表查找
    • 7. 单链表在pos位置之后插入data
    • 8. 单链表删除pos位置之后的值
  • 三、源代码及运行结果展示
    • 1. SList.h
    • 2. SList.c
    • 3. test.c
    • 结果展示:
  • 总结


前言

  顺序表可以随时存取表中的任意一个元素,它的存储位置可以用一个简单直观的公式表示,但是插入和删除操作需要移动大量元素。链式存储线性表时,不需要使用地址连续的存储单元,即不要求逻辑上相邻的元素在物理位置上也相邻,它通过“链”建立起数据元素之间的逻辑关系,因此插入和删除操作不需要移动元素,而只需要修改指针,但也会失去顺序表可随机存取的优点。


一、单链表的定义

  线性表的链式存储又称为单链表,它是通过一组任意的存储单元来存储线性表表中的数据元素。为了建立数据元素之间的线性关系,对每个链表节点,除存放元素本身的信息外,还需要存放一个指向其后继的指针。单链表的结构如下图所示,其中data为数据域,存放数据元素;next为指针域,存放其后继节点的地址。
在这里插入图片描述
  利用单链表可以解决顺序表需要大量连续存储单元的缺点,但单链表附加指针域,也存在浪费存储空间的缺点。由于单链表的元素离散地分布在存储空间中,所以单链表时非随机存取的存储结构,即不能直接找到表中某个特定的结点。查找某个特定的借点时,需要从头开始遍历,依次查找。

下图为一个带头结点的单链表,头指针pList,它指向的是头结点,除最后一个节点外,它们的next都指向下一个点的地址,对于最后一个节点的next指向NULL

在这里插入图片描述

从上图可以看出,链式结构在逻辑上是连续的,但在物理上不一定连续。
链表中的结点一般都是从堆上申请的。
从堆上申请的空间是按照一定的策略来分配的,两次申请的空间有可能连续,有可能不连续。

头节点和头指针的区分: 不管带不带头节点,头指针都始终指向链表的第一个节点,而头节点是带头结点的链表的第一个结点,结点内通常不存储信息。

二、单链表上基本操作的实现(不带头结点)

单链表中结点类型的描述如下:

typedef int SLTDateType;
typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

1. 动态申请一个节点

对于插入操作来说,需要先动态申请一个结点,并将该结点的数值域与指针域进行赋值,指针域都设置为NULL

SListNode* BuySListNode(SLTDateType data)
{
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
	if (newNode == NULL)
	{
		return NULL;
	}
	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}

2. 单链表尾插

在进行尾插操作时,要考虑到的一点就是在插入该结点之前此单链表是否为空,为空时则直接将该结点设置为头结点就行,若不为空就循环遍历找到最后一个结点进行尾插就行。
还有一点最为重要,就是在进行函数传参时,第一个参数为二级指针,这是为什么呢?首先定义一个头指针,它是用来指向链表第一个节点,当你以一级指针进行传参时,那么在该函数内得到头指针是一份临时拷贝,所以对该指针的临时拷贝进行操作时就不会出现想要得到的效果,所以说,凡是有关头指针的操作就是传二级指针,对于后续结点的插入其实可以传递一级指针,因为所要改变的不是头指针,而是结点这个结构体。我感觉吧,可以把该链表想象为一个双头蛇,一个头为另一个头的拷贝,当你对那个拷贝的头操作后并不会影响另外一个头,但是对于后续节点来说就可以比作为蛇的身子。

void SListPushBack(SListNode** Head, SLTDateType data)
{
	assert(Head);
	SListNode* newNode = BuySListNode(data); //创建一个值为data的结点
	if (*Head == NULL)
	{
		*Head = newNode;
	}
	else
	{
		SListNode* cur = *Head;
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newNode;
	}
}

3. 单链表尾删

在尾删时同样也要传递二级指针,有可能该链表只有一个结点。

void SListPopBack(SListNode** Head)
{
	if (*Head == NULL) //链表为空
	{
		return;
	}
	else if ((*Head)->next == NULL) //链表只有一个结点
	{
		*Head = NULL;
	}
	else  //链表中有多个结点
	{
		SListNode* cur = (*Head)->next;
		SListNode* prev = *Head; //prev为前序指针
		while (cur->next)
		{
			cur = cur->next;
			prev = prev->next;
		}
		//在循环退出后,prev指针指向倒数第二个结点
		prev->next = NULL;
		free(cur);
	}
}

4. 单链表头插

void SListPushFront(SListNode** Head, SLTDateType data)
{
	assert(Head);
	SListNode* newNode = BuySListNode(data);  //创建一个值为data的结点
	newNode->next = *Head;
	*Head = newNode;
}

5. 单链表头删

void SListPopFront(SListNode** Head)
{
	if (*Head == NULL) //链表为空
	{
		return;
	}
	else
	{
		SListNode* cur = *Head;
		*Head = (*Head)->next;
		free(cur);
	}
}

6. 单链表查找

此函数旨在查找元素,并不会去修改头指针的指向,所以传一级指针即可。

SListNode* SListFind(SListNode* Head, SLTDateType data)
{
	assert(Head);
	while (Head)
	{
		if (Head->data == data)
		{
			return Head;
		}
		Head = Head->next;
	}
	return NULL; //没有找到就返回NULL
}

7. 单链表在pos位置之后插入data

该操作是在pos位置之后进行插入(该pos位置通过查找函数获取),当然也可以在pos位置之前插入,不同的是只需改变查找函数即可,让查找函数返回pos位置的前一个结点指针。
因为不管任意位置插入还是删除都在pos位置之后,所以并不会涉及到头指针,因此不需要传递二级指针。若是pos位置之前插入删除则需要考虑有可能涉及到头指针指向位置的改变。

void SListInsertAfter(SListNode* pos, SLTDateType data)
{
	SListNode* newNode = BuySListNode(data);
	if (NULL == pos)
		return;
	newNode->next = pos->next;  //这里的两行代码顺序不能乱,因为会导致pos之后的结点找不到
	pos->next = newNode;
}

8. 单链表删除pos位置之后的值

void SListDelAfter(SListNode* pos)
{
	SListNode* newNode = NULL;
	if (NULL == pos || NULL == pos->next)
		return;
	newNode = pos->next; //同样,此处代码不能乱
	pos->next = newNode->next;
	free(newNode);
}

三、源代码及运行结果展示

1. SList.h

结构体创建及函数声明

#include <malloc.h>
#include <stdio.h>
#include <assert.h>

typedef int SLTDateType;

typedef struct SListNode
{
	SLTDateType data;
	struct SListNode* next;
}SListNode;

SListNode* BuySListNode(SLTDateType data);

void SListPrint(SListNode* Head);

void SListPushBack(SListNode** Head, SLTDateType data);

void SListPushFront(SListNode** Head, SLTDateType data);

void SListPopBack(SListNode** Head);

void SListPopFront(SListNode** Head);

SListNode* SListFind(SListNode* Head, SLTDateType data);

void SListInsertAfter(SListNode* pos, SLTDateType data);

void SListTest();

2. SList.c

方法实现

#include "SList.h"

SListNode* BuySListNode(SLTDateType data)
{
	SListNode* newNode = (SListNode*)malloc(sizeof(SListNode));
	if (newNode == NULL)
	{
		return NULL;
	}
	newNode->data = data;
	newNode->next = NULL;
	return newNode;
}

void SListPushBack(SListNode** Head, SLTDateType data)
{
	assert(Head);
	SListNode* newNode = BuySListNode(data);
	if (*Head == NULL)
	{
		*Head = newNode;
	}
	else
	{
		SListNode* cur = *Head;
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = newNode;
	}
}

void SListPushFront(SListNode** Head, SLTDateType data)
{
	assert(Head);
	SListNode* newNode = BuySListNode(data);
	newNode->next = *Head;
	*Head = newNode;

}

void SListPopBack(SListNode** Head)
{
	if (*Head == NULL)
	{
		return;
	}
	else if ((*Head)->next == NULL)
	{
		*Head = NULL;
	}
	else
	{
		SListNode* cur = (*Head)->next;
		SListNode* prev = *Head;
		while (cur->next)
		{
			cur = cur->next;
			prev = prev->next;
		}
		prev->next = NULL;
		free(cur);
	}
}

void SListPopFront(SListNode** Head)
{
	if (*Head == NULL)
	{
		return;
	}
	else
	{
		SListNode* cur = *Head;
		*Head = (*Head)->next;
		free(cur);
	}
}

SListNode* SListFind(SListNode* Head, SLTDateType data)
{
	assert(Head);
	while (Head)
	{
		if (Head->data == data)
		{
			return Head;
		}
		Head = Head->next;
	}
	return NULL;
}

void SListInsertAfter(SListNode* pos, SLTDateType data)
{
	SListNode* newNode = BuySListNode(data);
	if (NULL == pos)
		return;
	newNode->next = pos->next;
	pos->next = newNode;
}

void SListDelAfter(SListNode* pos)
{
	SListNode* newNode = NULL;
	if (NULL == pos || NULL == pos->next)
		return;
	newNode = pos->next;
	pos->next = newNode->next;
	free(newNode);
}

void SListPrint(SListNode* Head)
{
	SListNode* cur = Head;
	while (cur)
	{
		printf("%d---->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

void SListTest()
{
	SListNode* ListNode = NULL;
	SListPushFront(&ListNode, 0);
	SListPushBack(&ListNode, 1);
	SListPushBack(&ListNode, 2);
	SListPushBack(&ListNode, 3);
	SListPushBack(&ListNode, 4);
	SListPushBack(&ListNode, 5);
	SListPrint(ListNode);

	SListPushFront(&ListNode, -1);
	SListPrint(ListNode);

	SListPopBack(&ListNode);
	SListPopBack(&ListNode);
	SListPrint(ListNode);

	SListPopFront(&ListNode);
	SListPrint(ListNode);

	SListNode* ret = SListFind(ListNode, 0);
	if (ret == NULL)
	{
		printf("没找到\n");
	}
	else
	{
		printf("找到了\n");
	}

	SListInsertAfter(SListFind(ListNode, 2), 0);
	SListPrint(ListNode);

	SListDelAfter(SListFind(ListNode, 2));
	SListPrint(ListNode);
}

3. test.c

主函数

#include "SList.h"

int main()
{
	SListTest();
	return 0;
}

结果展示:

在这里插入图片描述

总结

  对于链表来说有着自己的特点,比如:在内存中不需要连续的地址进行存储,元素之间通过指针相连、逻辑相邻但物理上并不一定相邻,插入与删除操作不需要移动大量的元素,但是无法随机存取,只能从头遍历。而对于不带头结点的单链表来说,尤其要注意对头指针的指向修改时要使用二级指针。

  文章若有不足的地方还请大佬指正!!!

在这里插入图片描述

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

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

相关文章

分享30个PHP源码,总有一款适合你

链接&#xff1a;https://pan.baidu.com/s/1dVbUn5YFMOze4J-K8sCAXQ?pwdeinu 提取码&#xff1a;einu 下面是文件的名字&#xff0c;我放了一些图片&#xff0c;文章里不是所有的图主要是放不下...&#xff0c;大家下载后可以看到。 Emlog for SAE 适合新浪sae使用的个人博客…

网关Gateway-快速上手

gateway网关官方文档: https://docs.spring.io/spring-cloud-gateway/docs/current/reference/html/# 网关的概念 网关作为流量的入口&#xff0c;常用的功能包括路由转发&#xff0c;权限校验&#xff0c;限流等。 Spring Cloud Gateway 是Spring Cloud官方推出的第二代网关…

Java:修改Jar的源码,并上传Nexus私有仓库,替换jar版本

第一步&#xff1a;修改jar包源代码 建一个全类名一模一样的类&#xff0c;然后把要修改的类的代码复制过去&#xff0c;然后编译生成class。然后拿编译后的class覆盖到jar中对应的位置 第二步&#xff1a;上传nexus jar文件&#xff0c;pom文件&#xff1a;在本地仓库中可以…

Linux操作系统~进程有哪些状态?

目录 R状态 S/D状态 什么是D状态 T状态 X状态 Z状态 什么是等待队列&#xff0c;什么是运行队列&#xff0c;什么是挂起/阻塞&#xff0c;什么叫唤醒进程 对比宏观上操作系统的三种状态 从操作系统宏观的概念上讲&#xff0c;进程有三种状态&#xff0c;就绪态&#xff0…

自动化测试和测试自动化你分的清楚吗?

目录 前言 两种自动化测试 为什么测试自动化对连续测试至关重要 使测试自动化成为现实 拥抱连续测试 总结 重点&#xff1a;配套学习资料和视频教学 前言 当我们谈论持续测试&#xff0c;以及持续交付和DevOps时&#xff0c;“自动化”一词就泛滥了。从根本上讲&#xf…

ES6之对象解构

对象和数组字面量是JavaScript中两种最常用的数据结构&#xff0c;由于JSON数据格式的普及&#xff0c;二者已经成为语言中最重要的一部分。在代码中&#xff0c;我们经常定义很多对象和数组&#xff0c;然后从去提取相关的信息片段&#xff0c;ES6为简化这种任务引入了新特性&…

猿代码浅谈MPI与OpenMP并行程序设计

一、什么是OpenMP&#xff1f; OpenMP是一种用于共享内存并行系统的多线程程序设计的库(Compiler Directive),特别适合于多核CPU上的并行程序开发设计。它支持的语言包括&#xff1a;C语言、C、Fortran;不过&#xff0c;用以上这些语言进行程序开发时&#xff0c;并非需要特别…

一文读懂qt界面设计(分裂器,布局,拉伸,各种属性设置)

可以先看看我这个文章&#xff1a;qt关于界面设计中的一些知识总结_我是标同学的博客-CSDN博客_qt 水平伸展 现在我们来正式开始讲解。 布局种类 qt中能称为布局管理器的有如下6个&#xff1a; 水平布局&#xff08;QHBoxLayout&#xff09;垂直布局&#xff08;QVBoxLayout…

数字电路基础04(查找表LUT)

文章目录 LUT(Look Up Table)为什么要用LUT?示例(3输入LUT)LUT(Look Up Table) 在FPGA中,利用LUT来实现组合逻辑的功能,将组合逻辑的输入输出结果,存储为真值表的形式,来代替传统的由逻辑门组成的组合逻辑电路LUT就是将组合逻辑转换成真值表LUT实际上是将输入数据作…

怎么清理c盘的垃圾文件?有什么好的清理方法推荐?

在使用电脑办公或者娱乐的时候&#xff0c;我们的电脑会产生很多临时文件&#xff0c;如果这些临时文件不被清理掉的话&#xff0c;就会导致电脑的运行速度越来越慢&#xff0c;为了能够让电脑的速度越来越快&#xff0c;很多人都会想要清理C盘&#xff0c;但是在清理C盘的时候…

机器视觉(三):摄像机标定技术

目录&#xff1a; 机器视觉&#xff08;二&#xff09;&#xff1a;机器视觉硬件技术 机器视觉&#xff08;三&#xff09;&#xff1a;摄像机标定技术 &#x1f30f;&#x1f9d0;以下为正文&#x1f984;&#x1fa90; 摄像机标定的目的&#xff1a;三维重建 空间物体表面…

ESP32使用MiroPython编程环境搭建

大家好&#xff01; 今天和大家聊一聊ESP32使用MrioPython编程的环境搭建过程。 目录 一、在ESP32上使用MiroPython的必要条件 二、安装Thonny 1.安装地址 2.安装过程 三、下载MiroPython 四、下载ESP32驱动 五、烧录MicroPython到ESP32 六、点亮ESP32设备LED灯 一、在…

无人机技术服务应用

无人机技术服务应用 随着无人机的迅速发展&#xff0c;无人机行业应用越来越丰富&#xff0c;如何实现无人机行业内高效的运营一直是我们关注的重点。当今无人机具有的优势很多&#xff0c;例如&#xff1a;携带方便、操作简单、反应迅速、载荷丰富、任务用途广泛、起飞降落对…

计算机网络【HTTP协议】

计算机网络【HTTP协议】&#x1f34e;一.HTTP协议概述&#x1f352;1.什么是HTTP协议&#x1f352;1.2 Fiddler&#xff08;抓包工具&#xff09;&#x1f34e;二.HTTP协议格式&#x1f352;2.1HTTP请求&#x1f349;2.1.1 HTTP请求格式&#x1f349;2.1.2 HTTP请求格式URL&…

语句覆盖、条件覆盖、判定覆盖、条件-判定覆盖、路径覆盖

白盒测试的测试用例在大二学习软件工程的时候也是一个重点模块&#xff0c;但是上课没有太多时间做太多的测试用例&#xff0c;然后许久不用会搞乱&#xff0c;所以这里简单复盘一下。 白盒测试是结构测试&#xff0c;主要对代码的逻辑进行验证。 逻辑覆盖率&#xff1a;语句覆…

软件推荐:AList

软件推荐&#xff1a;AList AList是一个开源Web服务&#xff0c;可以添加多种网盘&#xff0c;将你的多个网盘应用集成到一个统一应用中使用&#xff0c;并且还支持通过WebDAV服务映射到操作系统本地目录使用。 GitHub&#xff1a;https://github.com/alist-org/alist 安装 …

双十一买什么比较划算?四款实用性超强不吃灰的数码好物推荐

现如今&#xff0c;越来越多的数码产品逐渐融入我们的生活当中。但是&#xff0c;在众多的数码产品中&#xff0c;很多人买来用过一两次就放着吃灰的产品也不在少数。因此&#xff0c;我来给大家推荐几款实用性强、不吃灰的数码好物&#xff0c;一起来看看吧。 一、南卡小音舱…

面试算法题

文章目录数组中的第K个最大元素快排k个一组反转链表解法一&#xff1a;栈解法二&#xff1a;模拟买卖股票最佳时机买卖股票最佳时机i买卖股票最佳时机ii贪心无重复长度子串最长递增子序列只出现一次的数字 IIIlru缓存合并K个升序链表数组中的第K个最大元素 快排 class Solutio…

第一个程序

第一个程序 1. 常用DOS命令 1.1 打开命令提示符窗口 1. winR2. cmd3. 按下回车键 1.2 常用命令 2. Path 环境变量的配置 2.1 为什么要配置 Path环境变量 开发 Java程序&#xff0c; 需要使用 JDK 提供的开发工具&#xff0c; 而这些工具在 JDK 的 安装目录的 bin目录 下。未来…

【测试代码 基于Pytorch】的卷积神经网络(CNN) || 【基于Pytorch】的深度卷积神经网络(DCNN)

声明:仅学习使用~ 目录 一、卷积神经网络 CNN1、【基于Pytorch】的卷积神经网络(CNN)2、【基于Pytorch】的深度卷积神经网络(DCNN)一、卷积神经网络 CNN CNN,这里以 LeNet 为例。LeNet神经网络由深度学习三巨头之一的Yan LeCun提出,他同时也是卷积神经网络 (CNN,Convo…