单链表(Single Link Table)——单文件实现

news2024/12/23 19:56:38

一、单链表前言

上篇文章我们讲述了顺序表,认真学习我们会发现顺序表优缺点。

缺点1:头部和中部的插入删除效率都不行,时间和空间复杂度都为O(N);

缺点2:空间不够了扩容有一定的消耗(尤其是realloc的异地扩容);

缺点3:扩容逻辑,可能还存在空间,就像我只需要插入170个数据,但是要扩容200个数据大小,就会造成浪费。

优点1:尾插尾删足够快;

优点2:下标的随机访问和修改很快很方便;

基于顺序表的缺点我们通过今天的单链表来解决;

二、链表

2.1链表的基本概念和结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表
中的指针链接次序实现的 。

结构图:

链表和顺序表一样也是通过结构体实现的,不一样的是链表中的结构体只含有有效数据和指向下个结构体的结构体指针

让我们先通过代码实现一个简单的链表吧!

#include<stdio.h>
#include<stdlib.h> 
typedef struct SListNode{
	int data;
	struct SListNode* next;
}SLTNode;
int main()
{
	SLTNode*n1=(SLTNode*)malloc(sizeof(SLTNode));
	n1->data=1;
	SLTNode*n2=(SLTNode*)malloc(sizeof(SLTNode));
	n2->data=2;
	SLTNode*n3=(SLTNode*)malloc(sizeof(SLTNode));
	n3->data=3;
	n1->next=n2;
	n2->next=n3;
	n3->next=NULL;
	SLTNode*plist=n1;
	while(plist)
	{
		printf("%d->",plist->data);
		plist=plist->next;
	}
	printf("NULL\n");
	 return 0;
 } 

这里我们开辟了三个结构体的空间用三个结构体指针指向每个结构体,然后将每个结构体里的结构体指针指向下一个结构体,再将每个结构体存入有效数据,一个简单的3长度的链表。

注意:

1.看上面的图逻辑上我们可能会认为是连续的,但是在物理地址内存上可能是连续的也可能是不连续的;

2.现实中的每个结点都是从堆上开辟的

3.从堆上开辟空间是按照一定策略的,两次连续的开辟空间可能是连续的,也可能是不连续的

2.2链表的分类

2.2.1单向或者双向

2.2.2带头或者不带头

2.2.3循环或者非循环

虽然有这么多的链表结构,但是我们实际中最常用的还是两种结构

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

三、无头单向非循环链表增删查改链表的实现

3.1链表的创建

typedef int SLTDatatype;
typedef struct SListNode
{
	SLTDatatype data;
	struct SListNode* next;
}SLTNode;

3.2填充数据及开辟新的结点

SLTNode* BuySLT(SLTDatatype x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

链表的实现靠的是结构体里的结构体指针指向下一个结构体链接,所以我们要将新的结点返回,

返回的是结构体指针,定义函数是也要结构体指针接受。

3.3打印链表

void SLprint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

我们是靠头结构体指针找到链表的,但是打印有效数据要移动指针所以我们就要在一开始创建一个指针,利用这个指针的移动打印数据,当这个指针为空时并不是指向空时停止打印。

3.4查找有效数据

SLTNode* SLFind(SLTNode* phead, SLTDatatype x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data = x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

和打印有效数据相似通过另一个指针的移动查找我们需要的有效数据,当指针为空时我们找不到有效数据返回NULL,当找到有效数据时返回指向含有这个有效数据的结构体;

3.5单链表尾插

void SLTPushBack(SLTNode** pphead, SLTDatatype x)
{
	SLTNode* newnode = BuySLT(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

首先我们需要一个空结构体指针指向开辟好的返回值为结构体指针,当无链表时也相当于头插我们只需将开辟好返回值为结构体指针赋值给空指针就行,此时这个链表只有一个结构体;当链表已经形成时此时才是我们的尾插,我们需要另一个指针从头开始找到链表末尾的空指针,再将这个空指针指向以开辟好的返回值为结构体指针就可以从尾部将其串联起来;

为什么要使用二级指针呢?

我们是有一个指针指向空或者链表的头,在尾插时我们要通过头指针的移动增添数据,但是这个指针我们不可以移动,移动的话会造成内存泄漏,所以我们又创建一个指针将这个指针指向我们的空或者链表的头,通过指针的操作将我们的链表串联起来,我们也可以将这个指针想象成我们的头指针,这个指针会变化相当于头指针变化,但是我们是在一个函数域中操作,除了这个作用域一切都会销毁,相当于什么都没敢。通俗的说我们是在操作头指针,在另个作用域中改变我们原有的值只能通过二级指针实现,但是头指针已经是个指针了,所以我们需要一个二级指针。

3.6尾删

void SLTPopBack(SLTNode** pphead)
{
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
        SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

首先我们要判断这个链表是否只有一个结构体,如果只有一个结构体直接将这个结构体释放销毁就行了;如果有多个结构体链接只需要找到倒数第二个结构体中的指向最后一个结构体的指针,释放这个指向最后一个结构体的指针就是可以实现尾删;

3.7头插

void SLPushFront(SLTNode** pphead, SLTDatatype x)
{
	SLTNode* newnode = BuySLT(x);
	if ((*pphead) == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* newnode = BuySLT(x);
		newnode->next = *pphead;
		*pphead = newnode;
	}
}

如果这个头指针为空时及没有链表,直接将这个指针指向开辟好的结构体的就行,如果不为空,将开辟好的结构体里指向下一个结构体的结构体指针指向我们的头指针,再将头指针指向开辟好的结构体就行;

3.8头删

void SLPopFront(SLTNode** pphead)
{
	if ((*pphead) == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* newhead = (*pphead)->next;
		free(*pphead);
		*pphead = newhead;
	}
}

首先我们还是先判断这个链表是否只含有一个结构体,如果只含有一个结构体和尾删的方法一样

如果含有多个结构体头删,我们只需要新创建一个指针保存第一个结构体里储存着第二个结构体的指针,然后释放掉我们的头指针,再将新创建的指针赋值给头指针就可以完成尾删。

到这里无头单向非循环链表增删查改的重点内容和注意事项就讲解完毕了,下面我们实现一下链表中间随机的增删查改,就不一一讲解了,大家自行了解;

四、无头单向非循环链表中间随机增删查改

4.1在pos位置后插入x

void SLInsertAfter(SLTNode* pos, SLTDatatype x)
{
	SLTNode* newnode = BuySLT(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

4.2在pos位置后插入x

void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDatatype x)
{
	if (pos == *pphead)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLTNode* newnode = BuySLT(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;

	}
}

4.3删除pos位置的值

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	if ((*pphead) == pos)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

 4.4删除pos位置后面的值

void SLEraseAfter(SLTNode* pos)
{
	assert(pos->next);
	SLTNode* newnode = pos->next->next;
	free(pos->next);
	pos->next = newnode;
}

五、无头单向非循环链表增删查改完整代码

#define _CRT_SECURE_NO_WARNINGS 67
#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDatatype;
typedef struct SListNode
{
	SLTDatatype data;
	struct SListNode* next;
}SLTNode;
//打印 
void SLprint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
//开辟新的空间
SLTNode* BuySLT(SLTDatatype x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc failed");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
//查找
SLTNode* SLFind(SLTNode* phead, SLTDatatype x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data = x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
//尾插
void SLTPushBack(SLTNode** pphead, SLTDatatype x)
{
	SLTNode* newnode = BuySLT(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
//尾删
void SLTPopBack(SLTNode** pphead)
{
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next->next)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}
//头插	
void SLPushFront(SLTNode** pphead, SLTDatatype x)
{
	SLTNode* newnode = BuySLT(x);
	if ((*pphead) == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* newnode = BuySLT(x);
		newnode->next = *pphead;
		*pphead = newnode;
	}
}
//头删 
void SLPopFront(SLTNode** pphead)
{
	if ((*pphead) == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* newhead = (*pphead)->next;
		free(*pphead);
		*pphead = newhead;
	}
}
//在pos位置后插入x
void SLInsertAfter(SLTNode* pos, SLTDatatype x)
{
	SLTNode* newnode = BuySLT(x);
	newnode->next = pos->next;
	pos->next = newnode;
}
//在pos位置前插入x 
void SLInsert(SLTNode** pphead, SLTNode* pos, SLTDatatype x)
{
	if (pos == *pphead)
	{
		SLPushFront(pphead, x);
	}
	else
	{
		SLTNode* newnode = BuySLT(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = newnode;
		newnode->next = pos;

	}
}
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	if ((*pphead) == pos)
	{
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

void SLEraseAfter(SLTNode* pos)
{
	assert(pos->next);
	SLTNode* newnode = pos->next->next;
	free(pos->next);
	pos->next = newnode;
}
int main()
{
	SLTNode* plist = NULL;
	//尾插
	SLTPushBack(&plist, 1);
	SLTPushBack(&plist, 2);
	SLTPushBack(&plist, 3);
	SLTPushBack(&plist, 4);
	SLTPushBack(&plist, 5);
	SLTPushBack(&plist, 10);
	printf("尾插:\n");
	SLprint(plist);
	//尾删
	printf("尾删:\n");
	SLTPopBack(&plist);
	SLprint(plist);
	//头插
	printf("头插:\n");
	SLPushFront(&plist, 10);
	SLprint(plist);
	//头删
	printf("头删:\n");
	SLPopFront(&plist);
	SLprint(plist);
	//在pos位置后插入x
	SLTNode* pos1 = SLFind(plist, 1);
	SLInsertAfter(pos1, 10);
	printf("在pos位置后插入x\n");
	SLprint(plist);
	//在pos位置前插入x
	SLTNode* pos2 = SLFind(plist, 1);
	printf("在pos位置前插入x\n");
	SLInsert(&plist, pos2, 20);
	SLprint(plist);
	//删除pos位置的值
	SLTNode* pos3 = SLFind(plist, 10);
	SLTErase(&plist, pos3);
	printf("删除pos位置的值:\n");
	SLprint(plist);
	//删除pos后面的值
	SLTNode* pos4 = SLFind(plist, 1);
	SLEraseAfter(pos4);
	printf("删除pos位置后面的值:\n");
	SLprint(pos4);
	return 0;
}

到这里我们的无头单向非循环链表增删查改的所有内容就讲解完了,有什么问题或者需要指正的可以在评论区留下您宝贵的意见。

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

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

相关文章

Linux网络编程:网络协议及网络传输的基本流程

目录 一. 计算机网络的发展 二. 网络协议的认识 2.1 对于协议分层的理解 2.2 TCP/IP五层协议模型 2.3 OSI七层模型 三. 网络传输的流程 3.1 同一网段中计算机通信的流程 3.2 不同网段中计算机设备的通信 3.3 对于IP地址和MAC地址的理解 3.4 数据的封装和解包 四. 总结…

2.linux的组管理和权限管理

一、组管理 1.Linux组的介绍 在linux中每个用户必须属于一个组&#xff0c;不能独立于组外。在linux中每个文件有所有者&#xff0c;所有组&#xff0c;其他组的概念 ①所有者 ②所在组 ③其他组 ④改变用户所在组 2.文件/目录 所有者 哪个用户创建了文件&#xff0c;就…

使用rpm重新安装包

#查询 rpm -qa | grep cloudstack #卸载 rpm -e cloudstack-agent-4.18.0.0-1.x86_64 #安装 rpm -ivh cloudstack-agent-4.18.0.0-1.x86_64.rpm

17.Xaml DockPanel控件 ---> 停靠面板

1.运行效果 2.运行源码 a.Xaml源码 <Window x:Class="testView.MainWindow"xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"xmlns:d="http://schemas.mic…

CFD模拟仿真理论求解-基于大数据的物理现象研究:热传导方程的数值求解

基于大数据的物理现象研究&#xff1a;热传导方程的数值求解 CFD模拟仿真理论求解 在科学研究和工程实践中&#xff0c;许多物理现象都可以用微分方程来描述。其中&#xff0c;热传导方程是一个非常重要且基础的例子。热传导方程是一个二阶线性偏微分方程&#xff0c;描述了热…

Docker-namespace

Docker-namespace namespace基础命令dd 命令mkfsdfmountunshare pid 隔离试验mount 隔离 namespace namespace 是 Linux 内核用来隔离内核资源的方式。通过 namespace 可以让一些进程只能看到与自己相关的一部分资源&#xff0c;而另外一些进程也只能看到与它们自己相关的资源…

D3JS简介

D3JS 什么是D3js D3.js是一个流行的JavaScript数据可视化库&#xff0c;它提供了一系列的API和工具&#xff0c;用于创建交互式的数据图表、地图等可视化效果。以下是一些D3.js的特点和用途&#xff1a; 数据驱动&#xff1a;D3.js基于数据驱动的思想&#xff0c;将数据和视觉…

科教兴国 | 拓世集团携手中国航天广电集团,打造《AI+教育平台》

在这个时代&#xff0c;人工智能的奇迹交织成一片璀璨的星河。在这片星河中&#xff0c;各大企业如同星辰&#xff0c;闪烁着探索的光芒&#xff0c;寻找着那些志同道合的伙伴。我们并肩飞翔&#xff0c;穿越信息的海洋&#xff0c;共同描绘出未来的蓝图。每一次合作&#xff0…

麒麟信安主办openEuler嵌入式Meetup:打造湖南大学openEuler技术小组,大咖齐聚共探技术前沿

9月8日&#xff0c;由开放原子基金会指导&#xff0c;openEuler社区、麒麟信安、湖南欧拉生态创新中心以及湖南大学联合主办的openEuler嵌入式Meetup在湖南大学成功举办。这一技术盛会汇聚了业内顶尖专家和开发者&#xff0c;旨在为嵌入式技术领域注入新的活力和创新。 活动现场…

巴州阳光志愿者服务协会党支部开展 第十一季“衣旧情深”爱心 活动

为了让捐赠真正回归公益慈善&#xff0c;奉行衣物回收“取之于民&#xff0c;用之于民”的理念&#xff0c;2023年9月10日&#xff0c;巴州阳光志愿者服务协会党支部书记李晓红组织志愿者们去普惠乡开展第十一季“衣旧情深”爱心活动。 本次活动是以“99公益日”活动为契机&…

Mysql5.7(Docker环境)实现主从复制

文章目录 前言一、MySQL主从数据库同步如何实现&#xff1f;(理论)1.1 为什么要使用数据库主从1.2 数据库主从实现原理是什么&#xff1f; 二、Docker环境配置MySQL5.7主从(实践)2.1 配置安装Master2.2 配置安装Slave 前言 本文章将以MySQL5.7版本来讲诉MySQL主从复制的原理以…

Android Jetpack Compose之状态持久化与恢复

目录 1.概述2.实例解析4. Compose提供的MapSaver和ListSaver4.1 mapServer4.2 ListSaver 1.概述 在之前的文章中&#xff0c;我们提到了remember&#xff0c;我们都知道remember可以缓存创建状态&#xff0c;避免因为重组而丢失。使用remember缓存的状态虽然可以跨越重组&…

软件测试之功能测试

一、测试项目启动与研读需求文档 &#xff08;一&#xff09; 组建测试团队 1.测试团队中的角色 2.测试团队的基本责任 尽早地发现软件程序、系统或产品中所有的问题。 督促和协助开发人员尽快地解决程序中的缺陷。 帮助项目管理人员制定合理的开发和测试计划。 对缺陷进行跟…

芯科蓝牙BG27开发笔记3-修改第一个程序

提问&#xff1a; 如何实现连续发送通知消息&#xff1f; 蓝牙无线射频信号在时间轴不是连续不断地存在&#xff0c;为了实现大数量的传输&#xff0c;需要额外的机制保证设备可以在下一次启动射频后可以接着发送之前没有发完的消息&#xff0c;nordic是可以使用队列&#xf…

springboot jpa手动事务

创建springboot项目 搭建最简单的SpringBoot项目_Steven-Russell的博客-CSDN博客 引入jpa和数据据依赖 <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId> </dependency>…

yolov8 模型部署--TensorRT部署-c++服务化部署

写目录 yolov8 模型部署--TensorRT部署1、模型导出为onnx格式2、模型onnx格式转engine 部署 yolov8 模型部署–TensorRT部署 1、模型导出为onnx格式 如果要用TensorRT部署YOLOv8&#xff0c;需要先使用下面的命令将模型导出为onnx格式&#xff1a; yolo export modelyolov8n.p…

二维前缀和

导言 当我们需要求到某个矩阵的子矩阵的和时,就可以使用二维前缀和 这是一个矩阵, 就是左上角区域的所有数之和 ...... 如果要 求中间的子矩阵的和,(x,y)为左上角 ...... ...... ,(i,j)为右下角,那么只需要算 - - ------这一…

QT第五天

void Widget::on_show_clicked() { QString sql "select * from myTable" ; QSqlQuery querry; if(!querry.exec(sql)) { QMessageBox::information(this,"失败","展示失败"); return; } //此时&…

如何用 DAP 仿真器下载程序

1.仿真器简介 本书配套的仿真器为 Fire-Debugger&#xff0c;遵循 ARM 公司的 CMSIS-DAP 标准&#xff0c;支持所有基于 Cortex-M内核的单片机&#xff0c;常见的 M3、M4 和 M7 都可以完美支持。 Fire-Debugger 支持下载和在线仿真程序&#xff0c;支持XP/WIN7/WIN8/WIN10 这…

c++ day 4

1、仿照string类&#xff0c;完成myString 类 #include <iostream> #include<cstring>using namespace std;class myString { private:char *str; //记录c风格的字符串int size; //记录字符串的实际长度 public://无参构造myString():size(10…