(纯c)数据结构之------>链表(详解)

news2024/11/15 10:15:18

目录

一.  链表的定义

        1.链表的结构.

        2.为啥要存在链表及链表的优势.

二. 无头单向链表的常用接口

        1.头插\尾插

        2.头删\尾删

        3.销毁链表/打印链表

        4.在pos位置后插入一个值

        5.消除pos位置后的值

        6.查找链表中的值并且返回它的地址

        7.创建一个动态开辟的结点

三.顺序表与链表的优缺点对比.


        文章开始->:

    一.链表的定义

        首先在学习单链表之前我们已近学过顺序表这一数据结构了,我们知道在使用顺序表的时候,当我们空间不够的时候我们需要扩容,还有在我们进行头插头删的时候我们需要移动元素,这时进行这些操作的时候是非常浪费时间的,并且扩容的时候还有可能浪费一定的空间当然这也是顺序表的缺点,而为了解决这些麻烦我们就弄出来了另外一个数据结构-->(链表)

        链表的定义:在逻辑结构上元素是连续的,但是实际的物理结构上链表是非连续非顺序的存储结构,数据元素的逻辑顺序其实是通过指针来连接的。

        下面就是正常的逻辑结构

 

        所以链表这种结构可以很简单的解决顺序表的问题,他在管理数据的时候并不需要扩容,而是当我们需要空间的时候他会取开辟一块空间然后我们只需要去改变指针的指向就可以将数据给连接起来了,这也省去了移动元素的时间。

        总结单链表的优点:单链表在使用内存空间的时候并不需要想顺序表那样进行扩容,而是我们需要空间的时候会自动去内存中开辟一块空间,也不需要再插入元素的时候移动元素,我们只需要改变指针的指向就可以实现链表逻辑上的顺序管理了。

        链表其实最大的好处就是可以进行头插和头删.

        下图就是我们插入元素时候的操作:->

        

 这样我们就不需要移动元素只需要改变指针的指向就行了。

        

接下来就是我们的重点进入常用接口的详细讲解-->

二.无头单链表的常用接口

      首先我们要理解的就是无头:所谓的无头就是我们并没有先申请一个结点,而是我们的头指针直接指向第一个节点,如果链表是空的那么我们我们的头指针指向的是空指针 。

        首先我们来看一看链表的结构

           在逻辑上-->

        在实际上-->

        

         实际上我们内存当中并没有指针指向的说法,只是我们为了方便理解链表这一数据结构而引入进来的。

链表定义的代码如下:

 typedef int SlistDataType;
    typedef struct Slist
{
    struct Slist* next;
    SlistDataType data;
}ST;

1.销毁链表/打印链表:   在这里我们要注意就是实参与形参的区别不然我们的操作可能会出现问题,打印的话我们直接遍历就行。

        这个接口是必须要有,因为我们在创建链表之前肯定得先有链表这一结构,而销毁链表是为了防止我们程序出现内存泄漏的问题。

        销毁链表:因为我们所申请的空间是在堆区上开辟的空间,而堆区上开辟的空间需要我们自己来释放空间,并且链表所开辟的空间并不是一个连续块的空间,所以我们需要来遍历链表这样保证我们将我们所开辟的空间来进行释放完整,防止内存泄漏。

        

void DestorySlist(STNode* plist)
{
	assert(plist);
	//这里我们需要一个一个的删除链表的结点
	while (plist != NULL)
	{
		STNode* newplist = plist->next;//存放下一个结点
		free(plist);
		plist = newplist;
	}

}

        打印链表:我们只需要遍历到结点为空的情况就行了

        下面是打印的代码:

        

void PrintSlist(STNode* plist)
{
	assert(plist);
	while (plist != NULL)
	{
		printf("%d->", plist->data);
		plist = plist->next;
	}
	printf("NULL\n");
}

     

  2.动态开辟一个结点:在我们进行插入有关操作的时候我们需要申请一块空间来存放要插入的值,所以这一步操作也是不能省略的:

        我们直接上代码:

        

STNode* BuySlistNode(SlistDataType x)
{
	STNode* newnode = (STNode*)malloc(sizeof(STNode));
	//判断开辟的空间成功没
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;//这样设计可以使得我们最后一个结点不需要在进行单独的设空
	return newnode;

}

 3.尾插\头插:这里我们需要用到二级指针,因为我们知道改变结构体需要结构体指针,而改变结构体指针,我们需要结构体指针的指针,即二级指针来使用。当我们在进行尾插的第一个插入的时候,我们需要改变结构体指针,所以得用二级指针。,而头插每次都需要改变结构体指针

        下面代码:尾插

        

//注意二级指针
void PushBackSlist(STNode** pplist, SlistDataType x)
{
	STNode* newnode = BuySlistNode(x);
	if (*pplist == NULL)
	{
		//插入第一个
		*pplist = newnode;
	}
	else
	{
		STNode* tail = *pplist;
		//我们得先找尾指针
		while(tail->next!=NULL)
		{ 
			
			tail = tail->next;
		}
		tail->next = newnode;

	}
}

        尾插的图片:

     

 

  头插代码:在这里我们需要注意的是在进行改变指针的时我们需要先进行将新节点的next指针先指向头,让后在将头改变,如果反了的话我们会使newnode->next指向自身。

        

void PushFrontSlist(STNode** pplist, SlistDataType x)
{
	STNode* newnode = BuySlistNode(x);
	newnode->data = x;
	newnode->next = *pplist;
	*pplist = newnode;

}

        头插图:

4.头删/尾删

        头删:我们首先在删除之前判断链表是否为空,如果为空我们就会报错,如果不为空则会继续进行操作,在这里当链表中只有一个结点的时候,那么我们就需要修改结构体指针了。

        下面是代码:

void PopFrontSlist(STNode** pplist)
{
	//为空
	assert(*pplist);
	//一个结点
	if ((*pplist)->next == NULL)
	{
		STNode* del = *pplist;
		free(del);
		*pplist = NULL;
	}
	//多个结点
	else
	{
		STNode* del = *pplist;
		STNode* newnode = (*pplist)->next;
		free(del);
		*pplist = newnode;
	}
}

        测试时图片:

        

         尾删:这里也要先判断链表是否为空,然后如果只有一个元素我们需要改变结构体指针,其他的我们则只需要将前面一个的指针指向NULL,就行了。

        尾删代码:

        

void PopBackSlist(STNode** pplist)
{
	//判断是否为空
	assert(*pplist);
	//一个结点
	if ((*pplist)->next == NULL)
	{
		STNode* del = *pplist;
		free(del);
		*pplist = NULL;
	}
	else
	{
		STNode* cur = *pplist;
		//找前一个
		while (cur->next->next != NULL)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}

        尾删测试:

        

         5.链表的查找接口:如果找到了则返回这个值的地址,如果没找到则打印未找到,思路:我们只需要遍历数组即可。

STNode* FindSlist(STNode* plist, SlistDataType x)
{
	assert(plist);
	STNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	printf("未找到\n");
	return NULL;
}

        测试:

        

         6.在pos位置后插入,与消除pos位置后的接口

        插入:首先我们得判断pos是否有意义,如果有则代表有意义,我们就保存pos位置后的结点,然后pos->next=newnode,newnode->next=posafter;

        代码:

void InsertafterSlist( STNode* pos, SlistDataType x)
{

		assert(pos);

		STNode* posafter = pos->next;
		STNode* newnode = BuySlistNode(x);
		pos->next = newnode;
		newnode->next = posafter;
}

        测试:

                

      删除pos后面的值:我们先判断pos是否有意义,有意义直接将pos后面的free掉,pos->next=NUll;

        代码:

        

void EraseafterSlist(STNode* pos)
{
	assert(pos);
	STNode* posafter = pos->next;
	pos->next =posafter->next ;
	free(posafter);

}

         测试图:

        

        注意:这后面三个接口通常都是一起使用的。

        到这里我们常用的接口已近讲解完毕了,接下来进行最后一部分的讲解--->

    三:顺序表与链表优缺点对比

        顺序表的优点:顺序表可以随机访问开辟空间的地址,且在内存当中是连续的一块空间,,支持随机访问,缓存利用率比较高。

                       缺点:再进行插入操作的时候需要扩容,而扩容其实底层原理是很麻烦的,这里可以看我前面写过的动态内存开辟的那一张设计realloc扩容,这里就不详细介绍了,且扩容之后还会浪费空间,其次是在进行头删/头插的时候我们需要移动元素,这里会导致很多的空间被持续使用,浪费了大量空间,而且扩容可能扩的非常多,任意插入与删除元素的效率低,时间复杂度为O(n).

        顺序表适合频繁访问的场景。

        链表的优点:再进行任意位置插入与删除的时候,不需要挪动元素时间复杂度为O(1),也不需要扩容操作,只需新增一个结点就行了,然后改变指针的指向就行了。        

        缺点:就是不能随机访问内存。且缓存利用率低

        链表适用于任意位置插入与删除的情况。

        总而言之:数据结构各有各的优点,也各有各的缺点,这些数据结构适合应用的场景不同而已。

                 ~~本章结束,感谢大家的耐心观看,如果你觉得有用的话,可以点个赞哦!

                

 

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

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

相关文章

VAE原理 代码详解 pin_memory

VAE代码 import torch from torch import nn import torch.nn.functional as F class VAE(nn.Module):def __init__(self, input_dim784, h_dim400, z_dim20): # 28x28784,20可能是这个手写体一共有20类?super(VAE, self).__init__()self.input_dim input_dimsel…

【Hadoop】HDFS读写流程和客户端命令使用

🍁 博主 "开着拖拉机回家"带您 Go to New World.✨🍁 🦄 个人主页——🎐开着拖拉机回家_Linux,Java基础学习,大数据运维-CSDN博客 🎐✨🍁 🪁🍁 希望本文能够给您带来一定的…

XSS攻击与防御

目录 一、环境配置 kali安装beef contos7安装dvwa 二、XSS攻击简介 三、XSS攻击的危害 四、xSS攻击的分类 五、XSS产生的原因 六、构造XSS攻击脚本 (一)基础知识 常用的html标签 常用的js脚本 (二)构造脚本的方式弹窗警告 七、自动XSS攻击 (一)BeEF简介 (二)BeEF…

Python 阿里云盾滑块验证

&#xfeff;<table><tr><td bgcolororange>本文仅供学习交流使用&#xff0c;如侵立删&#xff01;</td></tr></table> 记一次阿里云盾滑块验证分析并通过 操作环境 win10 、 macPython3.9selenium、pyautogui 分析 最近在做中国庭审…

SQLI-labs-第一关

知识点&#xff1a;单引号字符型注入 思路&#xff1a; 1、根据提示&#xff0c;为get注入&#xff0c;在url中输入内容 2、判断注入点 输入?id1&#xff0c;显示数据库语句错误&#xff0c;说明这里存在sql注入漏洞 输入?id1‘ and 11 -- &#xff0c;回显正常&#xff0c…

安科瑞AMB300系列母线槽红外测温解决方案监测母线槽连接处温度-安科瑞黄安南

一、行业背景 随着当今社会的发展和用电量的急剧上升&#xff0c;现代化工程设施和装备的涌现&#xff0c;封闭式母线即母线槽因方便、节能、载流量大、机械强度高 、安装灵活、寿命长等特点&#xff0c;逐渐取代传统电缆&#xff0c;广泛应用于室内变压站、高层建筑和大型厂房…

Lumion软件安装包分享(附安装教程)

目录 一、软件简介 二、软件下载 一、软件简介 Lumion是一款由Lumion公司开发的实时3D渲染和动画制作软件&#xff0c;广泛应用于建筑、规划和设计领域。它以快速、高效和逼真的渲染效果而闻名&#xff0c;帮助用户创建出色的建筑可视化作品。 1、Lumion的主要特点 实时渲染…

导数基本概念

定义 f ( x ) − f ( a ) x − a {f(x) - f(a)\over x -a} x−af(x)−f(a)​ 表示 f(x) 函数从 x 到 a 的平均变化率&#xff0c;如果使 x 趋近于 a&#xff0c;则表示函数在 a 点的变化率。 若有以下极限存在&#xff08;定义域不包含a&#xff09;&#xff1a; lim ⁡ x →…

strstr函数

目录 函数介绍&#xff1a; 函数分析&#xff1a; ​使用案例&#xff1a; 函数介绍&#xff1a; 返回指向 str1 中第一次出现的 str2 的指针&#xff0c;如果 str2 不是 str1 的一部分&#xff0c;则返回一个空指针。 匹配过程不包括终止空字符&#xff0c;但它到此为止。 …

系统架构设计高级技能 · 安全架构设计理论与实践

系列文章目录 系统架构设计高级技能 软件架构概念、架构风格、ABSD、架构复用、DSSA&#xff08;一&#xff09;【系统架构设计师】 系统架构设计高级技能 系统质量属性与架构评估&#xff08;二&#xff09;【系统架构设计师】 系统架构设计高级技能 软件可靠性分析与设计…

小研究 - JVM 逃逸技术与 JRE 漏洞挖掘研究(六)

Java语言是最为流行的面向对象编程语言之一&#xff0c; Java运行时环境&#xff08;JRE&#xff09;拥有着非常大的用户群&#xff0c;其安全问题十分重要。近年来&#xff0c;由JRE漏洞引发的JVM逃逸攻击事件不断增多&#xff0c;对个人计算机安全造成了极大的威胁。研究JRE安…

ToolAI–全球最完整最全面的AI人工智能工具集合

ToolAI是一个全球最完整最全面的AI人工智能工具集合网站&#xff0c;收集了全球最完整的数千个AI网站、工具、app&#xff0c;包含文案写作、邮件助手、聊天机器人、社交媒体等等各种行业类型的AI工具&#xff0c;可以按照地区或者分类进行查找浏览&#xff0c;目前收集6800 人…

CMake3.27+OpenCV4.8+VS2019+CUDA配置

1、准备工作 CMake3.27+OpenCV4.8+opencv_contrib-4.8.0+CUDA+CUDNN+TensorRT下载好并安装cuda 2、正式开始安装 启动CMake开始配置 打开刚解压的cmake文件夹中找到bin目录下的cmake-gui.exe 点击cmake中左下角的 Configure进行第一次配置,会弹出选择环境对话框 …

MyBatis与Spring整合以及AOP和PageHelper分页插件整合

目录 前言 一、MyBatis与Spring整合的好处以及两者之间的关系 1.好处 2.关系 二、MyBatis和Spring集成 1.导入pom.xml 2.编写配置文件 3.利用mybatis逆向工程生成模型层代码 三、常用注解 四、AOP整合pageHelper分页插件 创建一个切面 测试 前言 MyBatis是一个开源的…

容器镜像生成记

概述 容器docker/k8s发布已有一段时间&#xff0c;不少小伙伴开始上手实践。下面以一个简单的应用为例。来说明如何生成镜像并推送至镜像仓库。 准备工作 镜像仓库注册 以最常见的aliyun镜像仓库为例&#xff1a; 支付宝登录aliyun官网&#xff0c;搜索容器镜像服务&#x…

盘点 2023 十大免费开源 WAF

WAF 是 Web Application Firewall 的缩写&#xff0c;也被称为 Web 应用防火墙。区别于传统防火墙&#xff0c;WAF 工作在应用层&#xff0c;对基于 HTTP/HTTPS 协议的 Web 系统有着更好的防护效果&#xff0c;使其免于受到黑客的攻击。 近几年经济增速开始放缓&#xff0c;科…

快速了解;Mybatis-Plus

一、Mybatis-Plus介绍 MyBatis-Plus&#xff08;简称 MP&#xff09;是一个 MyBatis 的增强工具&#xff0c;在 MyBatis 的基础上只做增强不做改变&#xff0c;为简化开发、提高 效率而生。 官网&#xff1a;https://mybatis.plus/ 或 https://mp.baomidou.com/ 文档地址&…

arm体系结构:汇编指令

前言 本文主要介绍ARM RISC 32位体系结构下的相关知识&#xff0c;主要理解寄存器和相关指令&#xff0c;主要读懂汇编。ARM汇编指令集汇总 指令集介绍 ARM微处理器的指令集是加载/存储型的&#xff0c;也即指令集仅能处理寄存器中的数据&#xff0c;而且处理结果都要放回寄…

科大讯飞笔试编程第二题(处理Scanner不能先输入数字再输入字符串问题)

问题&#xff1a; 在使用scanner的时候如果先读取一个数字&#xff0c;在读取一行带有空格的字符串&#xff0c;势必会出错或者字符串读不到 public static void main(String[] args) {Scanner scanner new Scanner(System.in);int x scanner.nextInt();String s scanner.n…