数据结构:线性表之-单向链表(无头)

news2024/11/27 10:32:38

目录

什么是单向链表

顺序表和链表的区别和联系

顺序表:

链表:

链表表示(单项)和实现

1.1 链表的概念及结构

1.2单链表(无头)的实现

所用文件

将有以下功能:

链表定义

创建新链表元素

尾插

头插

尾删

头删

查找-给一个节点的指针

pos位置之前插入

删除pos位置的值

成品展示

SList.h

SList.c

test.c


什么是单向链表

单向链表是一种常见的线性数据结构,它由一系列节点组成,每个节点包含两部分:数据和指向下一个节点的指针。每个节点只能访问它后面的节点,而不能访问前面的节点。

单向链表的特点:

  • 每个节点包含数据和指向下一个节点的指针。
  • 最后一个节点的指针指向空值(NULL),表示链表的结束。
  • 可以动态地添加或删除节点,链表的长度可以根据需要进行扩展或缩小。
  • 可以根据指针迅速插入或删除节点,而不需要移动其他节点。

单向链表相对于数组来说,具有一些优点和缺点:

  • 优点:插入和删除元素的时间复杂度为O(1),不需要像数组一样进行元素的移动;链表长度可以动态调整,没有固定大小的限制。
  • 缺点:要访问特定位置的元素需要从头开始遍历,时间复杂度为O(n);相比于数组,在使用额外的指针存储下一个节点的信息,会占用更多的内存空间。

由于单向链表的特点,它常常被用于需要频繁插入和删除元素的场景,或者在事先无法确定数据大小和数量的情况下使用。

顺序表和链表的区别和联系

顺序表:

优点:
空间连续、支持随机访问
缺点:

  1. 中间或前面部分的插入删除时间复杂度O(N)
  2. 2.增容的代价比较大。

链表:

缺点:
以节点为单位存储,不支持随机访问
优点:

  1. 任意位置插入删除时间复杂度为O(1)
  2. 没有增容消耗,按需申请节点空间,不用了直接释放。

链表表示(单项)和实现

1.1 链表的概念及结构

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

1.2单链表(无头)的实现

 

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

所用文件

定义三个文件:

  1. 头文件 SList.h
  2. 函数的实现SList.c
  3. 代码的测试test.c

将有以下功能:

//打印链表
void SListPrint(SLTNode* phead);

//创建新链表元素(动态申请一个节点)
SLTNode* BuySListNode(SLTDataType x);

//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x);

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x);

//尾删
void SListPopBack(SLTNode** pphead);

//头删
void SListPopFront(SLTNode** pphead);

//查找->可在查找的基础上进行修改SListAlter
SLTNode* SListFind(SLTNode* phead,SLTDataType x);

//改
void SListAlter(SLTNode* phead, SLTDataType x,SLTDataType y);

//pos位置之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos);

链表定义

定义链表基本结构

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

创建新链表元素

创建新元素用于插入原链表

SLTNode* BuySListNode(SLTDataType x)
{
	//开辟空间
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);

	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

尾插

void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	//开辟空间
	SLTNode* newnode = BuySListNode(x);

	if (*pphead == NULL)
	{
		//防止开始时节点为空
		*pphead = newnode;
	}
	else
	{
		//找尾节点
		SLTNode* tail = *pphead;//找到链表首元素
		while (tail->next != NULL)
		{
			//检索到未节点
			tail = tail->next;
		}
		//插入
		tail->next = newnode;
	}
}

头插

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySListNode(x);

	newnode->next = *pphead;
	*pphead = newnode;//地址传给pphead  
	//*pphead=&plist
	
	/*
		头插无需检查是否为空
	*/
}

尾删

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next==NULL)
	{
		//1,只有一个节点

		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//2,有多个节点
		
		//将前一个链元素中存放的地址换为NULL,防止野指针
		/* 写法一 */
		SLTNode* tailPrev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next!=NULL)
		{
			tailPrev = tail;//tail的地址
			tail = tail->next;
		}
		free(tail);
		tailPrev->next = NULL;

		/* //写法二
		* //tail寻找的是倒数第二个元素
		while (tail->next->next!=NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
		*/
	}
}

头删

void SListPopFront(SLTNode** pphead)
{ 
	//防止被删空
	assert(*pphead);
	
	//找到首位链表元素
	SLTNode* next = (*pphead)->next;//存储首元素存放下一个元素的地址
	free(*pphead);//释放首元素
	*pphead = next;//将第二位元素改为首元素
}

查找-给一个节点的指针

//无需更改元素,故传一级指针
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data==x)
			return cur;
		cur = cur->next;
	}
	//未找到指定元素,返回NULL
	return NULL;
}

改元素是建立再查找基础之上进行更改

void SListAlter(SLTNode* phead, SLTDataType x, SLTDataType y)
{
	printf("修改成功:\n");
	//先找到相应元素,再进行更改
	SLTNode* ret = SListFind(phead, y);
	ret->data = x;
}

pos位置之前插入

任意位置之前插入

void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	//头插
	if (pos == *pphead)
		SListPushFront(pphead, x);
	else
	{ 
		//任意位置之前插入
		SLTNode* prev = *pphead;
		while (prev->next!=pos)//找到pos的位置
		{
			prev = prev->next;//prev存放pos的地址
		}
		//找到位置
		SLTNode* newnode = BuySListNode(x);//创建新链表元素,并赋值
		prev->next = newnode;//给前一个元素赋上下一元素地址
		newnode->next = pos;//给插入元素存放下一个元素的地址
	}
}

删除pos位置的值

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)
		SListPopFront(pphead);//头删
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//找到pos的位置
		{
			prev = prev->next;//prev存放pos的地址
			//移到pos前一位,next存放的是pos的地址
		}
		//将prev存放的地址改为pos后一个元素的地址
		prev->next = pos->next;
		//释放pos
		free(pos);
		pos = NULL;
	}
}

成品展示

SList.h

#pragma once

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

typedef int SLTDataType;

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

//打印链表
void SListPrint(SLTNode* phead);

//创建新链表元素(动态申请一个节点)
SLTNode* BuySListNode(SLTDataType x);

//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x);

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x);

//尾删
void SListPopBack(SLTNode** pphead);

//头删
void SListPopFront(SLTNode** pphead);

//查找->可在查找的基础上进行修改SListAlter
SLTNode* SListFind(SLTNode* phead,SLTDataType x);

void SListAlter(SLTNode* phead, SLTDataType x,SLTDataType y);

//pos位置之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos);

SList.c

#include "SList.h"
 
//打印
void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur!=NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;//存放下一个元素的地址
	}
	printf("NULL\n");
}

//创建新链表元素
SLTNode* BuySListNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	assert(newnode);

	newnode->data = x;
	newnode->next = NULL;

	return newnode;
}

//尾插
void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* newnode = BuySListNode(x);//data

	if (*pphead == NULL)
	{
		//防止开始时节点为空
		*pphead = newnode;
	}
	else
	{
		//找尾节点
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			//存放新节点地址
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

//头插
void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);

	SLTNode* newnode = BuySListNode(x);

	newnode->next = *pphead;
	*pphead = newnode;//地址传给pphead  
	//*pphead=&plist
	
	/*
		头插无需检查是否为空
	*/
}

//尾删
void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	
	if ((*pphead)->next==NULL)
	{
		//1,只有一个节点

		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//2,有多个节点

		//将前一个链元素中存放的地址换为NULL,防止野指针
		/* 写法一 */
		SLTNode* tailPrev = NULL;
		SLTNode* tail = *pphead;
		while (tail->next!=NULL)
		{
			tailPrev = tail;//tail的地址
			tail = tail->next;
		}
		free(tail);
		tailPrev->next = NULL;

		/* //写法二
		* //tail寻找的是倒数第二个元素
		while (tail->next->next!=NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
		*/
	}
}

//头删
void SListPopFront(SLTNode** pphead)
{ 
	//防止被删空
	assert(*pphead);

	SLTNode* next = (*pphead)->next;
	free(*pphead);
	*pphead = next;
}

//查找-给一个节点的指针
SLTNode* SListFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* cur = phead;
	while (cur)
	{
		if (cur->data==x)
			return cur;
		cur = cur->next;
	}
	return NULL;
}

//改
void SListAlter(SLTNode* phead, SLTDataType x, SLTDataType y)
{
	assert(phead);
	printf("修改成功:\n");
	SLTNode* ret = SListFind(phead, y);
	ret->data = x;
}
//pos位置之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(pos);

	//头插
	if (pos == *pphead)
		SListPushFront(pphead, x);
	else
	{ 
		SLTNode* prev = *pphead;
		while (prev->next!=pos)//找到pos的位置
		{
			prev = prev->next;//prev存放pos的地址
		}
		//找到位置
		SLTNode* newnode = BuySListNode(x);//创建新链表元素,并赋值
		prev->next = newnode;//给前一个元素赋上下一元素地址
		newnode->next = pos;//给插入元素存放下一个元素的地址
	}
}

//删除pos位置的值
void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(pos);

	if (*pphead == pos)
		SListPopFront(pphead);//头删
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//找到pos的位置
		{
			prev = prev->next;//prev存放pos的地址
			//移到pos前一位,next存放的是pos的地址
		}
		//将prev存放的地址改为pos后一个元素的地址
		prev->next = pos->next;
		//释放pos
		free(pos);
		pos = NULL;
	}
}

test.c

test.c仅仅是用于测试代码,本文以弄懂单向链表为主,故不进行菜单制作
但改测试中也包含了对部分链表结构即原理进行了讲解,请耐心看完

#include "SList.h"
void TestSList1()
{	
	SLTNode* n1 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n1);

	SLTNode* n2 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n2);

	SLTNode* n3 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n3);

	SLTNode* n4 = (SLTNode*)malloc(sizeof(SLTNode));
	assert(n4);
	
	n1->data=1;
	n2->data=2;
	n3->data=3;
	n4->data=4;

	n1->next = n2;
	n2->next = n3;
	n3->next = n4;
	n4->next = NULL;
	
	SListPrint(n1);
}

void TestSList2()
{
	SLTNode* plist = NULL;
	//传的是plist指针的地址
	
	//如果直接传plist,将导致SLTNode* phead中
	//phead为临时拷贝,不影响实参
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);
	
	SListPrint(plist);//不改变无需传二级指针

	SListPushFront(&plist,0);
	SListPrint(plist);

	SListPopFront(&plist);
	SListPrint(plist);
	
	SListPopBack(&plist);
	/*SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);
	SListPopBack(&plist);
	SListPrint(plist);*/
	/*SListPopBack(&plist);
	SListPrint(plist);*/
}
	
void TestSList3()
{
	SLTNode* plist = NULL;
	//传的是plist指针的地址

	//如果直接传plist,将导致SLTNode* phead中
	//phead为临时拷贝,不影响实参
	SListPushBack(&plist, 1);
	SListPushBack(&plist, 2);
	SListPushBack(&plist, 3);
	SListPushBack(&plist, 4);

	SListPrint(plist);//不改变无需传二级指针


	//查找
	SLTNode* ret = SListFind(plist, 3);
	if (ret)
	{
		//返回值不为空则为找到
		printf("找到了\n");
	}
	SListPrint(plist);
	修改
	//SListAlter(plist, 20, 2);
	//SListPrint(plist);

	//插入
	SLTNode* pos = SListFind(plist, 3);//先要找到再进行更改
	if (pos)
	{
		SListInsert(&plist, pos, 40);
	}
	SListPrint(plist);

	//删除
	pos = SListFind(plist, 2);//先要找到再进行删除
	if (pos)
	{
		SListErase(&plist, pos);
	}
	SListPrint(plist);
}

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

单向链表讲解完毕啦!创作不易,如果喜欢请留下个赞吧,感激不尽😊

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

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

相关文章

H.264编码及AAC编码基础

文章目录 前言一、视频编码的实现原理1、视频编码技术的基本原理2、视频编码技术的实现方法3、运动估计和补偿①、块&#xff08;Block&#xff09;与宏块&#xff08;MicroBlock&#xff09;②、I 帧、P 帧、B 帧的小结③、I 帧&#xff08;帧内编码&#xff09;④、如何衡量和…

软件测试中的43个功能测试点总结

功能测试就是对产品的各功能进行验证&#xff0c;根据功能测试用例&#xff0c;逐项测试&#xff0c;检查产品是否达到用户要求的功能。针对web系统的常用测试方法如下&#xff1a; 1、页面链接检查&#xff1a; 每一个链接是否都有对应的页面&#xff0c;并且页面之间切换正…

EasyPHP-Devserver-17安装和配置mantisBT

文章目录 1、准备工作2、安装easyphp2.1 http://127.0.0.1 无法访问 3、安装mantisBT和phpMyAdmin3.1 配置浏览器的访问url和端口号&#xff08;配置局域网内可访问&#xff09;3.2 安装mantis 4、Administrator 注册新用户时设置登录密码5、附件上传6、邮件配置 文章参考自&am…

模型推理后处理C++代码优化案例

文章目录 项目场景&#xff1a;问题描述原因分析&#xff1a;解决方案&#xff1a;小结 项目场景&#xff1a; 经过推理的后处理运行时间的优化。 先来看下优化前后的时间对比&#xff1a; 优化前&#xff1a; 优化后&#xff1a; 提升还是很大的。 问题描述 模型推理后得…

mybatis-plus 数据字段进行加解密入库,且加密字段支持模糊搜索

mybatis-plus 数据进行字段加解密入库&#xff0c;加密字段支持模糊搜索 前提介绍 &#xff08;开发环境需求&#xff09; 1. 开发框架、环境 springbootmybatis-plusmysql5.7&#xff08;oracle应该也是可以的&#xff0c;没有测试&#xff0c;但实现思路是都可以满足&…

CSP 202206-1 归一化处理

答题 #include<iostream> #include<cmath> using namespace std;int main() {int n;double variance0,average0;cin>>n;double a[n];for(int i0;i<n;i){cin>>a[i];averagea[i];}averageaverage/n;for(int i0;i<n;i){variance(a[i]-average)*(a[…

Redis事务的理解

介绍 Redis通过MULTI、EXEC、WATCH等命令来实现事务功能。 事务提供了一种将多个命令请求打包&#xff0c;然后一次性、按照顺序地执行多个命令的机制&#xff0c;并且在事务执行期间&#xff0c;服务器不会因为其他客户端请求而中断事务的执行功能&#xff0c;他会将事务中的…

nvidia-smi 命令详解

nvidia-smi 命令详解 1. nvidia-smi 面板解析2. 显存与GPU的区别 Reference: nvidia-smi命令详解 相关文章&#xff1a; nvidia-smi nvcc -V 及 CUDA、cuDNN 安装 nvidia-smi(NVIDIA System Management Interface) 是一种命令行实用程序&#xff0c;用于监控和管理 NVIDIA G…

Jetsonnano B01 笔记3:GPIO上拉下拉-输入输出读取

今日继续我的jetsonnano学习之路&#xff0c;今日学习的是GPIO的上拉下拉&#xff0c;输入输出的读取&#xff0c;文章贴出完整操作步骤过程&#xff0c;贴出源码。 目录 Linux常用文件命令&#xff1a; ls&#xff08;list&#xff09;列表&#xff1a; man&#xff1a; …

系统报错“由于找不到msvcp140.dll无法继续执行代码”的处理方法

我在使用电脑时&#xff0c;突然发现了一个错误提示&#xff1a;“无法启动程序&#xff0c;因为找不到msvcp140.dll文件”。这让我非常困惑&#xff0c;因为我确定这个文件应该存在于我的电脑上。但是电脑依然报错“由于找不到msvcp140.dll无法继续执行代码”&#xff0c;这个…

apache-activemq-5.17.1一键安装安装

下载 安装 双击InstallService.bat脚本 查看是否安装完成

C# Solidworks二次开发:创建草图文本和创建草图中心线API详解

今天要介绍的是关于如何创建草图文本的API以及创建草图中心线的API&#xff0c;同时把一些连带的API也会和大家介绍一下&#xff0c;依然是满满的干货。 &#xff08;1&#xff09;创建草图文本API&#xff0c;InsertSketchText() 这个API的输入参数如下图所示&#xff1a; 一…

SD、SDIO和MMC接口基础和规范介绍

在MMC规范发展的过程中出现了很多的名词&#xff0c;如SDHC、SDIO、SDXC等&#xff0c;每次看到这些不同的规范都有点懵&#xff0c;也很容易搞混&#xff0c;所以本篇文章就来介绍一下MMC规范发展过程中出现的一些新的规范&#xff0c;并详细地理解一下SD和SDIO。 文章目录 1 …

Postman接口测试流程

一、工具安装 ● 安装Postman有中文版和英文版&#xff0c;可以选择自己喜欢的版本即可。安装时重新选择一下安装路径&#xff08;也可以默认路径&#xff09;&#xff0c;一直下一步安装完成即可。&#xff08;本文档采用英文版本&#xff09;安装文件网盘路径链接&#xff1…

哈希的应用——布隆过滤器

文章目录 前言1. 布隆过滤器提出2. 布隆过滤器概念3. 布隆过滤器的插入多哈希函数映射减少冲突结构定义及set&#xff08;插入&#xff09;函数实现 4. 布隆过滤器的查找test&#xff08;查找&#xff09;函数实现布隆过滤器允许误判 5. 布隆过滤器的适用场景6. 如何选择布隆过…

AOI软件之 CAD图纸导入功能

在这里&#xff0c;我不过多的解释AOI&#xff0c;半导体检测行业内的小伙伴自然会懂&#xff1b;我也不会过多解释何为diemap或者wafer-layout。因为我们本文的核心场景仅仅是cad图纸的解析和基本绘图的二次开发。而且我们紧紧是面向行业内的场景需求来说明此功能。 无图我说…

强大的JTAG边界扫描(4):STM32边界扫描应用

文章目录 1. 获取芯片的BSDL文件2. 硬件连接3. 边界扫描测试4. 总结 试想这样一个场景&#xff0c;我们新设计了一款集成了很多芯片的板卡&#xff0c;包括BGA封装的微控制器&#xff0c;如FPGA/MCU&#xff0c;还有LED、按键、串口、传感器、ADC等基本外设。 我们需要测试一下…

时序分解 | MATLAB实现基于EWT经验小波变换的信号分解分量可视化

时序分解 | MATLAB实现基于EWT经验小波变换的信号分解分量可视化 目录 时序分解 | MATLAB实现基于EWT经验小波变换的信号分解分量可视化效果一览基本介绍程序设计参考资料 效果一览 基本介绍 EWT经验小波变换 包含频谱相关系数 可直接运行 Matlab代码 1.可自由设置分量个数&…

【uni-app】—2.必备软件安装

一、Node.js 安装流程 二、微信开发者工具 1.选择自己系统对应安装包下载 2. 安装 设置安装路径 三、HBuilderx 1. 根据系统下载安装包 2. 解压安装包&#xff08;自定义解压路径&#xff09; 3. 运行 四、安卓模拟器&#xff08;夜神&#xff09; 1. 下载安装包 2…

eclipse链接MySQL数据库

在MySQL官网下载驱动 MySQLhttps://www.mysql.com/cn/点击下载&#xff1a; 页面滚动到最下方选择社区版&#xff1a; 选择Java版本: 接下来&#xff0c;需要选择操作系统&#xff0c;我们选择平台独立&#xff1a; eclipse 接下来&#xff0c;我们打开eclipse&#xff0c;新建…