数据结构基础:3.单链表的实现。

news2024/9/25 9:28:20

单链表的介绍和实现

  • 一.基本概念
    • 1.基本结构
    • 2.结构体节点的定义:
  • 二.功能接口的实现
    • 0.第一个节点:plist
    • 1打印链表
    • 2创建一个节点
    • 3.头插
    • 4.头删
    • 5.尾插
    • 6.尾删
    • 7.查找
    • 8.在pos之前插入x
    • 9.在pos之后插入x
    • 10.删除pos位置
    • 11.删除pos的后一个位置
    • 12.链表释放
  • 三.整体代码

一.基本概念

1.基本结构

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

逻辑结构:前面的叫做数据域,后面叫做指针域,指针是用来保存下一个节点的地址的。在逻辑上是这样连续的但是在实际的物理结构上面空间上不是连续的。请添加图片描述

物理结构:这些都是节点,在内存中是通过前一个节点保存后一个节点的地址这样的话就可以连续去寻找链表数据。
请添加图片描述

2.结构体节点的定义:

请添加图片描述

二.功能接口的实现

0.第一个节点:plist

如果我们想要创建好一个链表并且去使用他我们需要有这些节点并且通过结构体的成员变量去连接我们的这个结构体构成一个链表的状态因为我们的方向是单向的所以每一次使用的时候我们应该需要知道第一个节点的地址,并且第一个节点它在逻辑上必须处于链表的第一个位置,我们在进行接口的操作都是依赖于我们的第一个节点。(第一个节点如果要变化那么我们的在主函数中的第一个节点的地址就要变化)(地址的变化是需要传二级指针的)

请添加图片描述

1打印链表

打印链表只需要循环遍历一次链表的数据内容。(不需要对第一个节点的地址发生改变)

//打印链表
void SLTprint(SLTNode* plist)
{
	//循环遍历不去更改头所以不需要传二级指针

	//打印之前我们需要注意我们是否有数据打印
	//说明链表已经没有数值或者还没有节点数据
	assert(plist!=NULL);
	SLTNode* cur = plist;
	//一个节点的下一个是空就不打印数据了
	while (cur)
	{
		printf("%d->", cur->data);
		//循环的一个条件
		cur = cur->next;
	}
	printf("NULL\n");

}

2创建一个节点

一个新节点的创建需要注意一个参数和一个返回值,参数是我们的节点的数据值,返回的参数是这个节点的地址,因为我们只能通过存储地址的方式进行链表的连接。

//创建一个节点
SLTNode* BuySListNode(STLDatatype x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode*));
	if (newnode == NULL)
	{
		perror("malloc file\n");
		exit(-1);
		//开辟空间失败直接退出程序
	}
	//使用这个节点
	newnode->data = x;
	//因为这是一个新的节点我们的下一个数据还没有给它
	//它现在不需要去存储下一个节点的地址。
	newnode->next = NULL;
	return newnode;
}

3.头插

头插:我们的头节点每经过一次头插就要改变前面提过我们去使用链表的时候是需要第一个节点的指针的。这个指针如果改变是需要影响到接口外的实参的。(需要传二级指针改变)

//头插
//头插
void SLTpushfront(SLTNode** pphead, STLDatatype x)
{
	//插入一个数据需要一个新的节点
	SLTNode* newnode = BuySListNode(x);
	//是有数据,说明*pphead不是空。
	//如果直接改变第一个的地址那么。第二个数据就找不到了。
	newnode->next = *pphead;
	*pphead = newnode;
}

4.头删

头删需要把第一个节点给释放,原来的第二个作为新的头,需要对我们的第一个节点进行地址的改变(需要传二级指针)

void SLTpopfront(SLTNode** pphead)
{
	//删除数据如果没有数据就不去删除
	assert(*pphead != NULL);

	//不止一个数据,保存第二个节点的地址
	SLTNode* newhead = (*pphead)->next;
	free(*pphead);
	*pphead = NULL;
	//释放老节点,拿到新节点
	*pphead = newhead;
}

5.尾插

不去修改第一个节点,可以传二级也可以传一级。

void SLTpushback(SLTNode** pphead, STLDatatype x)
{
	SLTNode* newnode = BuySListNode(x);
	//当没有数据的时候
	if (*pphead == NULL)
	{
		//对一个地址的修改
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		//下一个是NULL就是尾
		while (cur->next != NULL)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}

6.尾删

有可能删除第一个所以需要传二级指针。

//尾删
void SLTpopback(SLTNode** pphead)
{
	assert(*pphead);

	//当链表中只剩下最后一个节点的时候,需要把头指针也制空。
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//找尾
		SLTNode* cur = *pphead;
		SLTNode* prev = *pphead;
		while (cur->next != NULL)
		{
			//cur下一个是NULL说明现在的这个就是尾
			prev = cur;
			cur = cur->next;
		}
		//产生野指针的问题,确实释放了但是我们cur的前面一个指向的是一个野指针。
		free(cur);

		prev->next = NULL;
	}
}

7.查找

1.查找的区域不可以为空,返回一个节点地址。

SLTNode* SLTFind(SLTNode* phead, STLDatatype x)
{
	assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
}

8.在pos之前插入x

1.如果pos是第一个位置相当于头插可以复用前面的代码也可以自己写一个。
2.有可能会改到头所以必须要传二级指针。

void SLTInsert(SLTNode** pphead, SLTNode* pos, STLDatatype x)
{
	SLTNode* newnode = BuySListNode(x);
	if (pos == *pphead)
	{
		SLTpushfront(pphead, x);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next!=pos)
		{
			cur=cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;
	}
}

9.在pos之后插入x

void SLTInsertAfter(SLTNode* pos, STLDatatype x)
{
	SLTNode* newnode = BuySListNode(x);
	SLTNode* next = pos->next;
	pos->next = newnode;
	newnode->next = next;
}

10.删除pos位置

// 删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	if (pos == *pphead)
	{
		//头删
		SLTpopfront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		SLTNode* newhead = cur->next->next;
		free(pos);
		pos = NULL;
		cur->next = newhead;
	}
}

11.删除pos的后一个位置

// 删除pos的后一个位置
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos->next != NULL);
	if (pos->next->next == NULL)
	{
		free(pos->next);
		pos->next = NULL;
		pos->next = NULL;
	}
	else
	{
		SLTNode* next = pos->next->next;
		free(pos->next);
		pos->next = NULL;
		pos->next = next;
	}
	
}

12.链表释放

// 单链表的销毁
void SListDestroy(SLTNode** plist)
{
	assert(plist != NULL);
	//只需要释放不需要改变,所以一级就可以
	SLTNode* cur = *plist;
	while (cur->next!=NULL)
	{
		SLTNode* next = cur->next;
		cur->next = NULL;
		free(cur);
		cur = NULL;
		cur = next;
		SLTprint(cur);
	}
	cur->next = NULL;
	free(cur);
	cur = NULL;
	//原来的节点地址被释放,随机值但不是NULL
	*plist = NULL;
}

三.整体代码

#define _CRT_SECURE_NO_WARNINGS 1

#include"SLTNode.h"

//打印链表
void SLTprint(SLTNode* plist)
{
	//循环遍历不去更改头所以不需要传二级指针

	//打印之前我们需要注意我们是否有数据打印
	//说明链表已经没有数值或者还没有节点数据
	assert(plist!=NULL);
	SLTNode* cur = plist;
	//一个节点的下一个是空就不打印数据了
	while (cur)
	{
		printf("%d->", cur->data);
		//循环的一个条件
		cur = cur->next;
	}
	//实参是一个空指针但是存贮空指针的这个空间不是
	printf("NULL\n");

}
//创建一个节点
SLTNode* BuySListNode(STLDatatype x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode*));
	if (newnode == NULL)
	{
		perror("malloc file\n");
		exit(-1);
		//开辟空间失败直接退出程序
	}
	//使用这个节点
	newnode->data = x;
	//因为这是一个新的节点我们的下一个数据还没有给它
	//它现在不需要去存储下一个节点的地址。
	newnode->next = NULL;
	return newnode;
}

//头插
void SLTpushfront(SLTNode** pphead, STLDatatype x)
{
	//插入一个数据需要一个新的节点
	SLTNode* newnode = BuySListNode(x);
	//是有数据,说明*pphead不是空。
	//如果直接改变第一个的地址那么。第二个数据就找不到了。
	newnode->next = *pphead;
	*pphead = newnode;
}

//头删
void SLTpopfront(SLTNode** pphead)
{
	//删除数据如果没有数据就不去删除
	assert(*pphead != NULL);

	//不止一个数据,保存第二个节点的地址
	SLTNode* newhead = (*pphead)->next;
	free(*pphead);
	//释放老节点,拿到新节点
	*pphead = newhead;
}
//尾插
void SLTpushback(SLTNode** pphead, STLDatatype x)
{
	SLTNode* newnode = BuySListNode(x);
	//当没有数据的时候
	if (*pphead == NULL)
	{
		//对一个地址的修改
		*pphead = newnode;
	}
	else
	{
		SLTNode* cur = *pphead;
		//下一个是NULL就是尾
		while (cur->next != NULL)
		{
			cur = cur->next;
		}
		cur->next = newnode;
	}
}
//尾删
void SLTpopback(SLTNode** pphead)
{
	assert(*pphead);

	//当链表中只剩下最后一个节点的时候,需要把头指针也制空。
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//找尾
		SLTNode* cur = *pphead;
		SLTNode* prev = *pphead;
		while (cur->next != NULL)
		{
			//cur下一个是NULL说明现在的这个就是尾
			prev = cur;
			cur = cur->next;
		}
		//产生野指针的问题,确实释放了但是我们cur的前面一个指向的是一个野指针。
		free(cur);

		prev->next = NULL;
	}
}

SLTNode* SLTFind(SLTNode* phead, STLDatatype x)
{
	assert(phead);
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
}

// 在pos之前插入x
void SLTInsert(SLTNode** pphead, SLTNode* pos, STLDatatype x)
{
	SLTNode* newnode = BuySListNode(x);
	if (pos == *pphead)
	{
		SLTpushfront(pphead, x);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next!=pos)
		{
			cur=cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;
	}
}

// 在pos以后插入x
void SLTInsertAfter(SLTNode* pos, STLDatatype x)
{
	SLTNode* newnode = BuySListNode(x);
	SLTNode* next = pos->next;
	pos->next = newnode;
	newnode->next = next;
}

// 删除pos位置
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	if (pos == *pphead)
	{
		//头删
		SLTpopfront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		SLTNode* newhead = cur->next->next;
		free(pos);
		pos = NULL;
		cur->next = newhead;
	}
}

// 删除pos的后一个位置
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos->next != NULL);
	if (pos->next->next == NULL)
	{
		free(pos->next);
		pos->next = NULL;
		pos->next = NULL;
	}
	else
	{
		SLTNode* next = pos->next->next;
		free(pos->next);
		pos->next = NULL;
		pos->next = next;
	}
	
}

// 单链表的销毁
void SListDestroy(SLTNode** plist)
{
	assert(plist != NULL);
	//只需要释放不需要改变,所以一级就可以
	SLTNode* cur = *plist;
	while (cur->next!=NULL)
	{
		SLTNode* next = cur->next;
		cur->next = NULL;
		free(cur);
		cur = NULL;
		cur = next;
		SLTprint(cur);
	}
	cur->next = NULL;
	free(cur);
	cur = NULL;
	//原来的节点地址被释放,随机值但不是NULL
	*plist = NULL;
}

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

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

相关文章

缩略所写的代码

有一长串的代码需要进行缩略 可以在要缩略的代码的前一行加上注释。并在其中写上 #region。 在最后一行的下一行加上注释,并在其中写上 #endregion。 最终结果:

“华为杯”研究生数学建模竞赛2019年-【华为杯】D题:汽车行驶工况构建

目录 摘 要: 1.问题背景与问题重述 1.1 问题背景 1.2 问题重述 2.模型假设 3.符号说明 4.问题一的求解 4.1 问题分析 4.2 异常数据的处理 4.2.1 明显错误数据的处理 4.2.2 加减速异常数据的处理 4.3 缺失数据的处理 4.3.1 数据插补处理 4.3.2 视为长期停车处理 4.3.…

springboot编写mp4视频播放接口

简单粗暴方式 直接读取指定文件,用文件流读取视频文件,输出到响应中 GetMapping("/display1/{fileName}")public void displayMp41(HttpServletRequest request, HttpServletResponse response,PathVariable("fileName") String fi…

数学分析:流形1

光滑道路是一个映射,但我们通过光滑道路的这个名词,可以想象成一个曲线。然后这个曲线上就会有一个速度的概念,这个速度在不同道路上(但都经过同一个点x_0)会有不同的方向,他们组成的空间就是切空间。速度就…

The user specified as a definer (‘mysql.infoschema‘@‘localhost‘) does not exist

连接上报无法刷新浏览器 use mysql; show tables; ERROR 1449 (HY000): The user specified as a definer (mysql.infoschemalocalhost) does not existselect user,host from user; 删除 drop user ‘mysql.infoschema’‘127.0.0.1’; 重现创建 create user ‘mysql.infosc…

《视觉SLAM十四讲》笔记(4-6)

文章目录 4 李群与李代数4.1 李群与李代数基础4.1.1 群4.1.2 李代数的引出4.1.3 李代数的定义 4.2 指数与对数映射4.3 李代数求导与扰动模型 5 相机与图像5.1 相机模型5.1.1 针孔相机模型5.1.2 畸变5.1.3 双目相机5.1.4 RGB-D相机 6 非线性优化 4 李群与李代数 为了解决什么样…

Redis学习路线(6)—— Redis的分布式锁

一、分布式锁的模型 (一)悲观锁: 认为线程安全问题一定会发生,因此在操作数据之前先获取锁,确保线程串行执行。例如Synchronized、Lock都属于悲观锁。 优点: 简单粗暴缺点: 性能略低 &#x…

KWP2000协议和OBD-K线

KWP2000最初是基于K线的诊断协议, 但是由于后来无法满足越来越复杂的需求,以及自身的局限性,厂商又将这套应用层协议移植到CAN上面,所以有KWP2000-K和KWP2000-CAN两个版本。 这篇文章主要讲基于K线的早期版本协议,认…

【数据结构】无头+单向+非循环链表(SList)(增、删、查、改)详解

一、链表的概念及结构 1、链表的概念 之前学习的顺序表是用一段物理地址连续的存储单元依次存储数据元素的线性结构,而链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的,可以实现更加…

Element UI如何自定义样式

简介 Element UI是一套非常完善的前端组件库,但是如何个性化定制其中的组件样式呢?今天我们就来聊一聊这个 举例 就拿最常见的按钮el-button来举例,一般来说默认是蓝底白字。效果图如下 可是我们想个性化定制,让他成为粉底红字应…

在windows上安装minio

1、下载windows版的minio: https://dl.min.io/server/minio/release/windows-amd64/minio.exe 2、在指定位置创建一个名为minio文件夹,然后再把下载好的文件丢进去: 3、右键打开命令行窗口,然后执行如下命令:(在minio.…

【数据结构】栈(Stack)的实现 -- 详解

一、栈的概念及结构 1、概念 栈:一种特殊的线性表,其只允许在表尾进行插入和删除元素操作。进行数据插入和删除操作的一端称为栈顶,另一端称为栈底。栈中的数据元素遵守后进先出 LIFO(Last In First Out)的原则。 压栈…

Android Glide预处理preload原始图片到成品resource 预加载RecyclerViewPreloader,Kotlin

Android Glide预处理preload原始图片到成品resource & 预加载RecyclerViewPreloader&#xff0c;Kotlin <uses-permission android:name"android.permission.READ_EXTERNAL_STORAGE" /><uses-permission android:name"android.permission.READ_MED…

集合的概述

基本的集合有六种&#xff0c;分别是Vector、ArrayList、LinkedList、TreeSet、HashSet、LinkedHashSet 其中Vector、ArrayList、LinkedList实现了List接口&#xff0c;LinkedHashSet实现了HashSet接口&#xff0c;TreeSet、HashSet实现了Set接口 List和Set又实现了Collectio…

Anaconda原理解析及使用

anaconda想必大家都不陌生&#xff0c;属于使用python的重要工具&#xff0c;更是学习机器学习、深度学习的必备工具。在搭建环境过程中&#xff0c;感觉出现的许多问题根源在于对于anaconda的基本原理理解不到位&#xff0c;导致许多无效操作。为此&#xff0c;我重温了一遍an…

TypeScript实战篇 - TS实战:花田APP的架构

目录 TS实现花田APP的聊天Node端 整体架构 项目拆分 项目的特点 模型层 所有系统都是模型的外设 模型层的优势 TS实现花田APP的聊天Node端 整体架构 项目拆分 代号&#xff1a;huatian 5个独立的npm包 huatian/ui 花田的主项目huatian/component 花田组件库huatian/…

❤️创意网页:高考加油倒计时网页文字加多版 - 增加祝福语句和下雪背景效果

✨博主&#xff1a;命运之光 &#x1f338;专栏&#xff1a;Python星辰秘典 &#x1f433;专栏&#xff1a;web开发&#xff08;简单好用又好看&#xff09; ❤️专栏&#xff1a;Java经典程序设计 ☀️博主的其他文章&#xff1a;点击进入博主的主页 前言&#xff1a;欢迎踏入…

Emacs之改造最快的文件搜索工具fd-dired(基于fd命令)(一百二十一)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 人生格言&#xff1a; 人生…

RAID相关知识

简介 RAID &#xff08; Redundant Array of Independent Disks &#xff09;即独立磁盘冗余阵列&#xff0c;通常简称为磁盘阵列。RAID技术将多个单独的物理硬盘以不同的方式组合成一个逻辑磁盘&#xff0c;从而提高硬盘的读写性能和数据安全性。 数据组织形式 分块&#x…

Flowable-任务-手动任务

定义 手动任务是预期在没有任何业务流程执行引擎或任何应用程序的帮助下执行的任务&#xff0c;它用于建 模那些引擎不需要知道的人所做的工作&#xff0c;以及那些不存在已知系统或 UI 界面的人所做的工作&#xff0c;一 般完善流程结构描述&#xff0c;不被引擎执行。例如&a…