数据结构《链表》无头单向非循环-动图详解

news2024/12/26 23:02:47

前言

前面学习了顺序表发现,顺序表虽然好,但也有很多不足的地方,比方说,顺序表是一块连续的物理空间,如果头插或者头删,那么整个数组的数据都要移动。但是链表不一样,链表是通过指针访问或者调整,链表是物理空间是不连续的,通过当前的next指针找到下一个。
插入和删除时,数组平均需要移动n/2个元素,而链表只需修改指针即可
链表的优点:
插入删除速度快
内存利用率高,不会浪费内存
大小没有固定,拓展很灵活
在这里插入图片描述

目录

  • 前言
  • 链表的概念及结构
  • 链表的分类
    • 最常用的两种链表
  • ————————————————
  • 链表的创建
  • 创建新的节点
  • 尾插
  • 头插
  • 尾删
  • 头删
  • 查找
  • 指定位置插入
  • 指定位置删除
  • pos位置后插入
  • pos位置后删除
  • 打印
  • 销毁
  • 完整代码

链表的概念及结构

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

注意:
1.从图上看,链式结构在逻辑上是连续的,但在物理上不一定连续
2.现实中节点一般都是从堆上申请出来的
3.从堆上malloc的空间是按照一定策略来分配的,第二次申请空间可能是连续的,也可能不是

链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:
单向或双向链表:
在这里插入图片描述

带头节点或者不带头节点:
在这里插入图片描述

循环或者非循环:

在这里插入图片描述

最常用的两种链表

无头单向非循环链表:
在这里插入图片描述
带头双向循环链表:
在这里插入图片描述

————————————————

链表的创建

链表是由数据和指针组成
那么我们在创建链表的时候,需要一个指针指向下一个节点
如下:

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;

}SListNode;

创建新的节点

创建新的节点,把要插入的值放进新的节点的data,然后再把next指针赋成空,最后返回这个节点的指针

//创建新的节点
SListNode* BuySLTNode(SLTDataType x)
{
	SListNode* ret = (SListNode*)malloc(sizeof(SListNode));
	if (ret == NULL)
	{
		perror("BuySLTNode");
		return NULL;
	}
	ret->data = x;
	ret->next = NULL;

	return ret;
}

尾插

既然是尾插,那就是在尾部插入元素

我们可以看到下面是用二级指针接收的,因为传过来的是一级指针的地址,所以我们要用二级指针接收,并且我们使用的时候要解引用,因为我们要改变的是一级指针本身

如果传过来的指针本身就是NULL,那就直接把新的节点赋给传过来的指针
如果不是空,那我们就找尾,找到尾之后,再把新的节点插入到尾部的next指针
注:
tail指针是指向元素当前位置,我们一般不直接使用原有的指针,因为我们要保留第一个元素指针的位置,以便后续使用。

//尾插
void SLTPushBack(SListNode** pphead, SLTDataType x)
{
	assert(pphead);

	SListNode* newnode = BuySLTNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SListNode* tail = *pphead;
		//找尾
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

头插

我们把新节点的next指向第一个元素的指针,然后再把第一个节点的指针更新为newnode

//头插
void SLTPushFront(SListNode** pphead, SLTDataType x)
{
	assert(pphead);

	SListNode* newnode = BuySLTNode(x);
	SListNode* tail = *pphead;

	newnode->next = *pphead;
	*pphead = newnode;
}

尾删

分为三种情况:
1.链表没有元素,那我们直接断言解决
2.如果链表只有一个元素,那我们判断一下,然后free掉第一个指针指向的空间
3.链表多个元素,我们这里选择的是,判断tail->next->next是否为空,如果不为空,我们让tail指针向后走一步,再继续判断,直到tail->next->next为空,我们free掉tail->next即可

//尾删
void SLTPopBack(SListNode** pphead)
{
	assert(pphead);
	assert(*pphead != NULL);//如果*pphead为空,说明链表没有元素

	if ((*pphead)->next == NULL)//如果(*pphead)->next为空,说明链表只有一个元素,我们只需要free掉第一个指针指向的空间即可
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* tail = *pphead;
		//判断tail->next->next是否为空,如果不为空,tail向后走一步,再判断
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		//循环不满足,说明tail->next->next为空
		//我们只需要free掉tail的next
		free(tail->next);
		tail->next = NULL;
	}
}

头删

分为两种情况:
1.链表为空,直接用断言解决
2.链表多个元素,我们直接用tail指针保留第一个指针的位置,然后再更新第一个位置的指针,最后free掉刚刚保留的tail位置
在这里插入图片描述

//头删
void SLTPopFront(SListNode** pphead)
{
	assert(pphead);
	assert(*pphead != NULL);

	SListNode* tail = *pphead;
	*pphead = tail->next;
	free(tail);
	tail = NULL;
}

查找

把phead指针赋给cur,如果cur->data和要查找的值相同,返回cur,如果不相同让cur继续向后走,直到相同位置,返回cur,如果走到NULL都没有相同的,说明没有。

//查找
SListNode* SLTFind(SListNode* phead, SLTDataType x)
{
	SListNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;

	}
	return NULL;
}

指定位置插入

进来直接判断pos是不是第一个元素的空间,如果是,直接头插
走到else里,我们定义一个prev指针,记录pos前一个的位置,直到prev的next等于pos,然后把要插入的值放进prev的next位置,再把pos给newnode的next
在这里插入图片描述

//指定插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDataType x)
{
	assert(pos);
	assert(pphead);
	SListNode* newnode = BuySLTNode(x);

	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;
	}
}

指定位置删除

判断要删除的节点是否就是第一个节点,如果是,直接调用头删
反之,定义一个prev指针记录pos的前一个位置,直到prev的next指针等于pos的时候,把pos的next赋给prev的next进行拼接,然后free掉pos即可
在这里插入图片描述

//指定删除
void SLTErase(SListNode** pphead, SListNode* pos)
{
	assert(pphead);
	assert(pos);
	//判断pos是否就是第一个节点
	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

pos位置后插入

pos位置后插入相比指定插入,通俗易懂
直接把newnode的next指向pos的next,然后再把pos的next指向newnode
在这里插入图片描述

//pos位置后插入
void SLTInsert_After(SListNode* pos, SLTDataType x)
{
	assert(pos);

	SListNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

pos位置后删除

把pos的next给del,del是等会要删除的位置
然后pos的next指向del的next,最后free掉del
在这里插入图片描述

//pos位置后删除
void SLTEease_After(SListNode* pos)
{
	assert(pos);
	assert(pos->next);
	SListNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

打印

把phead给cur指针,让cur指针往后走,每次cur打印data数据,直到走到NULL。

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

销毁

定义一个cur指针,再记录一下cur的下一个位置,然后free掉cur,再把刚刚记录的cur的next位置赋给cur,直到cur走到NULL为止
在这里插入图片描述

//销毁
void SLTDestroy(SListNode** pphead)
{
	assert(pphead);
	SListNode* cur = *pphead;

	while (cur)
	{
		SListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

完整代码

SList.h

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

typedef int SLTDataType;

typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;

}SListNode;

//打印
void SLTPrint(SListNode* phead);
//尾插
void SLTPushBack(SListNode** pphead, SLTDataType x);
//头插
void SLTPushFront(SListNode** pphead, SLTDataType x);
//尾删
void SLTPopBack(SListNode** pphead);
//头删
void SLTPopFront(SListNode** pphead);

//查找
SListNode* SLTFind(SListNode* phead, SLTDataType x);

//指定插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDataType x);

//指定删除
void SLTErase(SListNode** pphead, SListNode* pos); 

//pos位置后插入
void SLTInsert_After(SListNode* pos, SLTDataType x);

//pos位置后删除
void SLTErase_After(SListNode* pos);

//销毁
void SLTDestroy(SListNode** pphead);

SList.c

#include "SList.h"

//打印
void SLTPrint(SListNode* phead)
{
	SListNode* cur = phead;

	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}

	printf("NULL\n");
}

//创建新的节点
SListNode* BuySLTNode(SLTDataType x)
{
	SListNode* ret = (SListNode*)malloc(sizeof(SListNode));
	if (ret == NULL)
	{
		perror("BuySLTNode");
		return NULL;
	}
	ret->data = x;
	ret->next = NULL;

	return ret;
}

//尾插
void SLTPushBack(SListNode** pphead, SLTDataType x)
{
	assert(pphead);

	SListNode* newnode = BuySLTNode(x);

	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SListNode* tail = *pphead;
		//找尾
		while (tail->next != NULL)
		{
			tail = tail->next;
		}

		tail->next = newnode;
	}

}

//头插
void SLTPushFront(SListNode** pphead, SLTDataType x)
{
	assert(pphead);

	SListNode* newnode = BuySLTNode(x);
	SListNode* tail = *pphead;

	newnode->next = *pphead;
	*pphead = newnode;
}

//尾删
void SLTPopBack(SListNode** pphead)
{
	assert(pphead);


	assert(*pphead != NULL);

	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SListNode* tail = *pphead;

		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}

		free(tail->next);
		tail->next = NULL;
	}

}

//头删
void SLTPopFront(SListNode** pphead)
{
	assert(pphead);

	assert(*pphead != NULL);

	SListNode* tail = *pphead;
	*pphead = tail->next;
	free(tail);
	tail = NULL;

}

//查找
SListNode* SLTFind(SListNode* phead, SLTDataType x)
{
	SListNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;

	}
	return NULL;
}


//指定插入
void SLTInsert(SListNode** pphead, SListNode* pos, SLTDataType x)
{
	assert(pos);
	assert(pphead);
	SListNode* newnode = BuySLTNode(x);

	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;

	}
	
}
//指定删除
void SLTErase(SListNode** pphead, SListNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)
	{
		SLTPopFront(pphead);
	}
	else
	{
		SListNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
	}
}

//pos位置后插入
void SLTInsert_After(SListNode* pos, SLTDataType x)
{
	assert(pos);

	SListNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;

}

//pos位置后删除
void SLTEease_After(SListNode* pos)
{
	assert(pos);
	assert(pos->next);
	SListNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}


void SLTDestroy(SListNode** pphead)
{
	assert(pphead);
	SListNode* cur = *pphead;

	while (cur)
	{
		SListNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

Test.c(测试代码)

void SListTest2()
{
	SListNode* plist = NULL;

	SLTPushFront(&plist, 1);
	SLTPushFront(&plist, 2);
	SLTPushFront(&plist, 3);
	SLTPushFront(&plist, 4);
	SLTPrint(plist);

	SListNode* ret = SLTFind(plist, 2);
	//SLTPrint(plist);

	SLTInsert(&plist, ret, 22);
	SLTPrint(plist);

	SLTErase(&plist, ret);
	SLTPrint(plist);
}
int main()
{
	SListTest3();
	return 0;
}

下期讲解带头双向循环链表

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

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

相关文章

昨天面了个腾讯拿 48K 出来的,让我见识到了基础的天花板

今年的春招基本已经进入大规模的开奖季了&#xff0c;很多小伙伴收获不错&#xff0c;拿到了心仪的 offer。 各大论坛和社区里也看见不少小伙伴慷慨地分享了常见的面试题和八股文&#xff0c;为此咱这里也统一做一次大整理和大归类&#xff0c;这也算是划重点了。 俗话说得好&a…

ESP8266获取天气预报信息,并使用CJSON解析天气预报数据

一、实现功能 当前文章介绍如何使用ESP8266和STM32微控制器&#xff0c;搭配OLED显示屏&#xff0c;制作一个能够实时显示天气预报的智能设备。将使用心知天气API来获取天气数据&#xff0c;并使用MQTT协议将数据传递给STM32控制器&#xff0c;最终在OLED显示屏上显示。 心知…

Python篇——数据结构与算法(第二部分)

目录 二、排序算法&#xff08;承接第一部分&#xff09; 1、堆排序算法——树的基础知识补充 2、树的基本概念 3、二叉树基础知识 &#xff08;1&#xff09;满二叉树 &#xff08;2&#xff09;完全二叉树 &#xff08;3&#xff09;二叉树的存储方式&#xff08;表示方式…

Python基础教程:第九章_Python异常模块与包

从现在开始&#xff0c;让我们来进入到新的章节&#xff0c; Python 异常模块与包的内容学习。本章节我们主要分为 6 部分进行讲解&#xff0c;包含了 Python 异常的相关操作以及 Python 的模块操作&#xff0c; Python 的包操作和安装第三方 Python 包的相关操作。 了解异常 …

【Netty】Netty ChannelHandler(四)

文章目录 前言一、ChannelHandler二、ChannelInboundHandler三、ChannelOutboundHandler四、ChannelDuplexHandler总结 前言 前两篇文章我们已经对Netty进行了简单的了解和架构设计原理的剖析。 相关文章链接如下&#xff1a; Netty 概述&#xff08;一&#xff09;Netty 架构…

在互联网寒冬,我们应届生应如何提高竞争力?

前言 在当前就业形势下&#xff0c;如何提高应届生在职场中的竞争力&#xff1f;具有哪些有效的方法和策略可供选择&#xff1f;这是一个备受关注的热点话题。哪些方面会对应届生的职场发展起到关键的推动和支撑作用呢&#xff1f;我也来讲一下我是打算如何提升自己的职场竞争力…

移动应用架构解析:用户界面层、业务逻辑层、数据访问层

移动应用的成功离不开一个良好的架构设计&#xff0c;在移动应用开发过程中&#xff0c;合理的层次结构对于应用的可维护性、可扩展性和可测试性至关重要。 移动应用的常见层次结构包括用户界面层、业务逻辑层、数据访问层&#xff0c;但是随着跨平台开发框架的不断发展&#…

【低压配电漏电继电器660V/LLJ-100H/AC220V 中性点漏电保护 JOSEF】

LLJ-F(S)系列漏电继电器 系列型号&#xff1a; LLJ-10F(S)漏电继电器LLJ-15F(S)漏电继电器LLJ-16F(S)漏电继电器 LLJ-25F(S)漏电继电器LLJ-30F(S)漏电继电器LLJ-32F(S)漏电继电器 LLJ-60F(S)漏电继电器LLJ-63F(S)漏电继电器LLJ-80F(S)漏电继电器 LLJ-100F(S)漏电继电器LLJ-120…

医学影像检测方法(B超、DR、CT、MRI)

医学影像检测方法 当涉及到医学影像学时&#xff0c;B超&#xff08;超声波检查&#xff09;、DR&#xff08;数字X射线摄影&#xff09;、CT&#xff08;计算机断层扫描&#xff09;和MRI&#xff08;磁共振成像&#xff09;是常见的诊断工具。以下是对这四种影像技术的基本概…

智能门锁揭开新方案:NV340D芯片打造更智能安全的语音解锁体验

智能门锁可以实现一键开锁、实时监控等功能&#xff0c;带来了更便捷、智能的门禁管理体验&#xff0c;逐渐成为人们生活中必不可少的一部分。近年来&#xff0c;随着人工智能技术的不断进步&#xff0c;越来越多的智能门锁开始集成语音控制系统&#xff0c;以提供更加方便的门…

人民大学与加拿大女王大学金融硕士项目——职场不会拒绝一个优秀的金融人才

在金融行业摸爬滚打多年的金融人&#xff0c;通过多年的拼搏与积累&#xff0c;已身处于一个相对舒适、从容的阶段&#xff0c;能沉淀下来再学习的金融人更是令人钦佩。在繁忙的工作之余他们依然保持对学业的热情&#xff0c;以应对瞬息万变的环境发展。人民大学与加拿大女王大…

Unity AssetBundle资源热更插件

Unity AssetBundle资源热更插件 CatAssetManager运行模式 - Package Only新建一个AssetBundle更改AssetBundle的Group分类更改AssetBundle的打包方式 构建规则 加密方式输出AssetBundle 运行模式 - Updateable查看我们热更的Bundle输出目录WebServer目录上传到服务器上选择热更…

u盘数据不见了能恢复吗?可以试试这3种方法

U盘通常体积小巧&#xff0c;存储容量较大&#xff0c;在现代社会中广泛使用。用户可以将各种类型的数据存储到U盘中&#xff0c;如照片、音乐、视频、文档等。但是使用过程中U盘数据无故消失了怎么办呢&#xff1f;在未备份u盘数据的情况下&#xff0c;u盘数据不见了能恢复吗&…

Jenkins发送邮件、定时执行、持续部署

集成Allure报告只需要配置构建后操作即可。但如果是web自动化&#xff0c;或是用HTMLTestRunner生成报告&#xff0c;构建后操作要选择Publish HTML reports&#xff0c;而构建中还要添加Execute system Groovy script插件&#xff0c;内容&#xff1a; System.setProperty(&q…

FT2000+ openEuler 20.03 LTS SP3 NUMA关闭 numa=off 对应用程序申请内存大小的影响,NUMA开关作用

测试程序 编写内存消耗程序 eatMemory.c #define _GNU_SOURCE#include <stdio.h> #include <stdlib.h> #include <sys/types.h> #include <string.h> #include <unistd.h> #include <sys/time.h> #include <sched.h> #include <…

k8s入门实战(Pod-Label-Deployment)

k8s入门实战(Pod-Label-Deployment) Pod Pod 是可以在 Kubernetes 中创建和管理的、最小的可部署的计算单元。 k8s架构图&#xff1a; k8s集群启动后&#xff0c;集群中各个组件也是以pod方式运行 [rootmaster ~]# kubectl get pod -n kube-system NAME …

RAR压缩文件如何转换成ZIP格式?

压缩文件有多种不同的格式&#xff0c;有时候因为需求不同&#xff0c;我们需要把RAR压缩文件转换成ZIP格式&#xff0c;那要如何操作呢&#xff1f;下面小编分享2种简单的方法。 方法一&#xff1a; 如果需要转换的RAR压缩包不是很多&#xff0c;我们可以直接把文件名字后缀“…

Spring Boot日志系统大揭秘:从零开始学习Spring Boot日志:常见问题解答和最佳实践

一. 关于 Spring Boot 日志的使用 Spring Boot 日志机制和工具用于记录应用程序的日志信息和追踪应用程序的执行过程。它集成了常用的日志框架&#xff0c;如 Log4j、logback、Java Util Logging等&#xff0c;并提供简单易用的配置方式&#xff0c;让开发人员可以方便地监控应…

【web基础与HTTP协议】

目录 一、Web基础1、域名1.1、域名的概述1.2、域名空间结构1.3、域名注册 2、网页的概念2.1、网页的概述2.2、网址的概述1、URI和URL的区别 二、HTML概述1、HTML 基本标签2、HTML 文件结构如下3、头标签中常用标签4、内容标签中常用标签 三、静态网页与动态网页3.1、目前常用的…

day4--链表内指定区间反转

迭代方法 1. 第m个节点的前一个节点pre和第n个节点&#xff1b; 2. 将第m个节点到第n个节点的链表部分反转&#xff1b; 3. 将pre节点的next指向反转后链表的头节点&#xff0c;将反转后链表的尾节点的next指向n1节点。 /*** struct ListNode {* int val;* struct ListNode…