【数据结构】单链表功能的实现

news2024/11/15 15:43:17

目录

1.链表的概念及结构

2.单链表功能的实现

2.1打印单链表

2.2创建节点

2.3单链表尾插

2.3单链表头插

2.5单链表尾删

2.6单链表头删

2.7单链表的查找

2.8在指定位置之前插入数据

2.9在指定位置之后插入数据

2.10删除pos节点

2.11删除pos之后的节点

2.12销毁链表

3.完整代码

SList.h

SList.c


1.链表的概念及结构

线性表的逻辑结构属于线性结构,采用顺序存储结构顺序表,采用链式存储结构线性链表

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

链表的结构跟火车车厢相似,淡季时火车的车厢会相应减少,旺季时火车的车厢会额外增加几节。只需要将某节车厢去掉或者加上,不影响其他车厢,每节车厢都是独立存在的。

且每节车厢都有车门,假设每节车厢的车门都是锁上的状态,需要不同的钥匙才能解锁,每次只能携带一把钥匙的情况下如何从车头走到车尾? 最简单的做法:每节车厢里都放一把下一节车厢的钥匙。

在链表里,每节“车厢”是什么样的呢?

与顺序表不同的是,链表里的每节"车厢"都是独立申请下来的空间,我们称之为“结点/节点” ,节点的组成主要有两个部分:要保存的数据下一个节点的地址(指针变量)。

图中指针变量 plist保存的是第一个节点的地址,我们称plist此时“指向”第一个节点,如果我们希望plist“指向”第二个节点时,只需要修改plist保存的内容为0x0012FFA0。

为什么还需要指针变量来保存下一个节点的位置?

链表中每个节点都是独立申请的(即需要插入数据时才去申请一块节点的空间),通过指针变量保存下一个节点位置才能从当前节点找到下一个节点。

注意

  1. 链式结构在逻辑上是连续的,在物理结构上不一定连续。
  2. 节点一般是从上申请的。
  3. 从堆上申请来的空间,是按照一定策略分配出来的,每次申请的空间可能连续,可能不连续。

2.单链表功能的实现

2.1打印单链表

void SLPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)//pcur!=NULL
	{
		printf("%d->", pcur->data);
		pcur = pcur->next; //将指针指向下一个节点
	}
	printf("NULL\n");
}

2.2创建节点

单链表每次插入都需要插入一个节点,所以我们最好写一个创建节点的函数方便后续调用。

SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)  //申请失败
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

2.3单链表尾插

尾插时需要找到最后一个节点的位置,再插入数值,但是要注意假如传进来一个空链表,ptail=NULL;那么对它进行找尾操作势必会造成ptail->next这句代码对空指针进行解引用,所以分成两种情况:

1.如果链表为空,直接插入即可。

2.如果链表不为空,需要找到尾节点再插入。

输出结果:

我们发现输出结果不是1,而是NULL,这是为什么呢?

SLTPushBack(plist, 1);

一级指针不能实现的原因传参的时候另外创建一个一级指针phead接收plist传过来的值,它和plist指针一样指向NULL( 这里属于传值调用,虽然plist变量存储的值是地址),尾插数据后,再让phaed指针指向newnode,在此期间plist仍然指向NULL,没有发生任何改变,因为形参的改变无法影响实参,调用链表打印函数,打印字符NULL。

当链表为空时,一开始plist指向空指针,然后需要plist指向newnode,需要改变plist的指向,这里用一级指针是实现不了的,需要用二级指针二级指针才能改变一级指针plist的指向。若链表不为空不需要改变plist的指向,程序依然可以正常运行。

小贴士

一级指针的作用 : 将普通变量的一级指针传入函数作为参数 ,可以在函数中访问该一级指针指向的普通变量, 并且可以修改该普通变量的值,但是该普通变量所在内存地址不能被修改。 

传参的时候传一级指针变量的地址&plist使用pphead二级指针来接收:

尾插代码

void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);  //不能传空指针,不能对空指针进行解引用
	//空链表和非空链表
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)    //*pphead是指向第一个节点的指针
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SLTNode* ptail =*pphead;
		while (ptail->next)//ptail->next!=NULL
		{
			ptail = ptail->next;
		}
		//ptail指向的就是尾节点
		ptail->next = newnode;
	}	
}

2.3单链表头插

头插数据需要改变一级指针plist的指向,这里也需要二级指针改变一级指针plist的指向。

void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead; //将newnode和头结点连接在一起
	*pphead = newnode;       //将链表头结点指向新的头
}

2.5单链表尾删

与单链表尾插类似,当链表只有一个头结点时需要将plist置为空,所以也需要二级指针。

void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);//链表不能为空
	//链表只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//链表有多个节点
		SLTNode* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next)   //找尾节点
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);	//释放掉ptail指向的空间
		ptail = NULL;
		prev->next = NULL;//将ptail前一个节点next指针置为空,防止野指针
	}
}

2.6单链表头删

先创建一个指针next,保存第二个节点的位置,防止释放头结点导致找不到第二个节点。

void SLPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);//链表不能为空
	SLTNode* next = (*pphead)->next;//保存第二个节点
	free(*pphead);
	*pphead = next; //第二个节点作为链表新头,头指针走到新头的位置
}

2.7单链表的查找

查找数据不需要改变指针plist的指向,这里使用一级指针传参即可。如果找到就返回这个节点的地址,否则就返回空指针NULL。

SLTNode* SLTFind(SLTNode* phead, SLTDataType x)
{
	SLTNode* pcur = phead;//再定义一个指针,phead指针可能后续还要用,不希望改变phead指针
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//pcur==NULL  没有找到!
	return NULL;
}

2.8在指定位置之前插入数据

当链表在头节点插入时使用头插。

void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	//若pos==*pphead;说明是头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);//调用头插
	}
	else
	{
		SLTNode* newnode = SLTBuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)//找pos的前一个节点prev
		{
			prev = prev->next;
		}
		//将prev  newnode  pos 三个节点连起来
		newnode->next = pos;
		prev->next = newnode;
	}	
}

2.9在指定位置之后插入数据

void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//pos  newnode  pos->next
	newnode->next = pos->next;//这里一定要注意顺序问题!
	pos->next = newnode;
}

2.10删除pos节点

void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	//pos是头节点/pos不是头节点
	if (pos == *pphead)
	{
		//头删
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//找pos的前一个节点prev
		{
			prev = prev->next;
		}
		//prev pos pos->next
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}	
}

2.11删除pos之后的节点

void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* del = pos->next;
	//pos  del  del->next
	pos->next = del->next;
	free(del);
	del = NULL;
}

2.12销毁链表

void SListDesTroy(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

3.完整代码

SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
//定义节点的结构
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SLTNode;

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

//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x);

//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x);

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

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

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

//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x);

//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x);

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos);

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos);

//销毁链表
void SListDesTroy(SLTNode** pphead);

SList.c

#include"SList.h"
//打印链表
void SLPrint(SLTNode* phead)
{
	SLTNode* pcur = phead;
	while (pcur)//pcur!=NULL
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

//申请一个新节点
SLTNode* SLTBuyNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail!");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

//链表尾插
void SLTPushBack(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);  //不能传空指针,不能对空指针进行解引用
	//空链表和非空链表
	SLTNode* newnode = SLTBuyNode(x);
	if (*pphead == NULL)    //*pphead是指向第一个节点的指针
	{
		*pphead = newnode;
	}
	else
	{
		//找尾
		SLTNode* ptail =*pphead;
		while (ptail->next)//ptail->next!=NULL
		{
			ptail = ptail->next;
		}
		//ptail指向的就是尾节点
		ptail->next = newnode;
	}	
}

//链表头插
void SLTPushFront(SLTNode** pphead, SLTDataType x)
{
	assert(pphead);
	SLTNode* newnode = SLTBuyNode(x);
	newnode->next = *pphead; //将newnode和头结点连接在一起
	*pphead = newnode;       //将链表头结点指向新的头
}

//尾删
void SLTPopBack(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);//链表不能为空
	//链表只有一个节点
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		//链表有多个节点
		SLTNode* prev = *pphead;
		SLTNode* ptail = *pphead;
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		free(ptail);	//释放掉ptail指向的空间
		ptail = NULL;
		prev->next = NULL;//将ptail前一个节点next指针置为空,防止野指针
	}
}

//头删
void SLPopFront(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);//链表不能为空
	SLTNode* next = (*pphead)->next;//先保存第二个节点的位置,防止释放头结点导致找不到第二个节点
	free(*pphead);
	*pphead = next; //第二个节点作为链表新头,头指针走到新头的位置
}

//查找
SLTNode* SLTFind(SLTNode* phead, SLTDataType x)//不需要改变plist指向,传一级指针即可
{
	SLTNode* pcur = phead;//再定义一个指针,phead指针可能后续还要用,不希望改变phead指针
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	//pcur==NULL  没有找到!
	return NULL;
}

//在指定位置之前插⼊数据
void SLTInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	//若pos==*pphead;说明是头插
	if (pos == *pphead)
	{
		SLTPushFront(pphead, x);//调用头插
	}
	else
	{
		SLTNode* newnode = SLTBuyNode(x);
		SLTNode* prev = *pphead;
		while (prev->next != pos)//找pos的前一个节点prev
		{
			prev = prev->next;
		}
		//将prev  newnode  pos 三个节点连起来
		newnode->next = pos;
		prev->next = newnode;
	}	
}

//在指定位置之后插⼊数据
void SLTInsertAfter(SLTNode* pos, SLTDataType x)
{
	assert(pos);
	SLTNode* newnode = SLTBuyNode(x);
	//pos  newnode  pos->next
	newnode->next = pos->next;//这里一定要注意顺序问题!
	pos->next = newnode;
}

//删除pos节点
void SLTErase(SLTNode** pphead, SLTNode* pos)
{
	assert(pphead);
	assert(*pphead);
	assert(pos);
	//pos是头节点/pos不是头节点
	if (pos == *pphead)
	{
		//头删
		SLPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)//找pos的前一个节点prev
		{
			prev = prev->next;
		}
		//prev pos pos->next
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}	
}

//删除pos之后的节点
void SLTEraseAfter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* del = pos->next;
	//pos  del  del->next
	pos->next = del->next;
	free(del);
	del = NULL;
}

//销毁链表
void SListDesTroy(SLTNode** pphead)
{
	assert(pphead);
	assert(*pphead);
	SLTNode* pcur = *pphead;
	while (pcur)
	{
		SLTNode* next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

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

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

相关文章

快专利与慢专利:速度与质量的天平

在当今快速发展的科技时代&#xff0c;专利成为了创新成果的重要保护手段。然而&#xff0c;不同的创新有着不同的节奏&#xff0c;由此也产生了“快专利”与“慢专利”之分。快专利以其迅速的申请和应用&#xff0c;为创新者抢占市场先机&#xff1b;慢专利则凭借深度的研发和…

Django Admin后台从一个页面同时编辑多个模型

要从Django Admin后台一个页面同时编辑多个对象&#xff0c;我们需要使用内联。 假设你有一个Category模型&#xff0c;并且需要在Admin后台Category编辑页面&#xff0c;同时编辑Villain模型。你可以在admin.py中使用内联&#xff1a; class VillainInline(admin.StackedInl…

EDIUS X 10.34.9631 视频剪辑软件 下载 包含安装说明

下载地址(资源制作整理不易&#xff0c;下载使用需付费&#xff0c;不能接受请勿浪费时间下载) 链接&#xff1a;https://pan.baidu.com/s/1P2wKxVcSx5WzAtHXCaAp5A?pwd227i 提取码&#xff1a;227i

自动控制:鲁棒控制的原理和设计

自动控制&#xff1a;鲁棒控制的原理和设计 引言 在实际控制系统中&#xff0c;由于模型不确定性、外部扰动、参数变化等因素&#xff0c;传统的控制方法难以保证系统在各种情况下的性能。这时&#xff0c;鲁棒控制&#xff08;Robust Control&#xff09;应运而生。鲁棒控制…

Vue(十一)默认插槽、具名插槽、作用域插槽

文章目录 一、需求二、插槽1. 默认插槽2. 具名插槽3. 作用域插槽 一、需求 有三个Category组件&#xff0c;展示不同的内容。 需求&#xff1a;美食模块需要展示图片&#xff0c;游戏模块还是文字&#xff0c;电影模块展示预告片。 <!--App组件--> <template>&l…

四款录音神器,你选对了吗?

在快节奏的现代生活中&#xff0c;我们经常需要处理大量的录音文件&#xff0c;如会议记录、采访、讲座等。为了更高效地处理这些信息&#xff0c;录音转文字工具变得越来越重要。本文将为大家介绍四款常用的录音转文字工具&#xff0c;并分享使用体验。 一、福昕PDF转换器 直…

Matlab 并联双振子声子晶体梁结构带隙特性研究

参考文献&#xff1a;吴旭东,左曙光,倪天心,等.并联双振子声子晶体梁结构带隙特性研究[J].振动工程学报,2017,30(01):79-85. 为使声子晶体结构实现范围更宽的多带隙特性&#xff0c;基于单振子型声子晶体结构弯曲振动带隙频率范围窄的局 限&#xff0c;提出了一种双侧振子布置…

电商智能分析:阿里巴巴商品详情API返回值的挖掘与利用

电商智能分析是利用大数据和机器学习技术来深入理解用户行为、商品趋势以及市场变化的过程。阿里巴巴商品详情API作为获取商品详细信息的重要工具&#xff0c;其返回值中蕴含了丰富的数据&#xff0c;可以通过挖掘和利用这些数据来进行智能分析。下面&#xff0c;我将提供一个基…

美畅物联丨科技赋能校车安全:智慧监控管理系统的创新应用

1、背景 1.1应用需求 孩子&#xff0c;作为国家未来的希望之星和民族发展的潜力所在&#xff0c;其安全与健康向来都是社会瞩目的核心要点。校车&#xff0c;作为孩子们日常出行的关键交通载体&#xff0c;其安全性更是时刻牵动着每一个家庭的敏感神经。然而&#xff0c;不可…

TensorFlow1和TensorFlow2介绍

目录 一.安装 二.TensorFlow结构分析 数据流图介绍 三.图与TensorBoard 1.图结构 2.图的相关操作 3.自定义图 4.开启会话Session中指定使用那个图 四.TensorBoard&#xff1a;可视化 1.数据序列化-events文件 2.启动TensorBoard 3.tensorflow2中使用tensorboard 1.…

【编程基础C++】素数判定、最小公倍数与最大公因数的实现方法

文章目录 素数法一法二 最大公因数辗转相除法另一写法 最小公倍数直接枚举法根据GCD算LCM 素数 素数 是指大于1的自然数&#xff0c;且只能被1和自身整除。例如&#xff0c;2、3、5和7都是素数。它们在数学中非常重要&#xff0c;因为任何大于1的自然数都可以唯一地表示为素数…

【类模板】成员函数模板

一、成员函数模板的基本含义 不管是普通的类&#xff0c;还是类模板&#xff0c;都可以为其定义成员函数模板&#xff0c;以下的情况就是类模板和成员函数模板都有各自独立的一套参数&#xff1a; template<typename T1> class A { public:T1 m_ic;static constexpr int…

电力104规约

对象性质十进制十六进制数量适用报文类型ASDU遥测1793~2304701H~900H512*9、11、21、34、35遥信1~10241H~400H1024*1、3、20、30、31遥控2817~2944B01H~B80H128*45、46遥调2945~3072B81H~C00 H128*47APCI 应用规约控制信息; ASDU 应用服务数据单元; APDU 应用规约数据单元;…

CountDownLatch的应用与原理

一、什么是CountDownLatch CountDownLatch是具有synchronized机制的一个工具&#xff0c;目的是让一个或者多个线程等待&#xff0c;直到其他线程的一系列操作完成。 CountDownLatch初始化的时候&#xff0c;需要提供一个整形数字&#xff0c;数字代表着线程需要调用countDow…

K8s系列之:解释Kubernetes Operators

K8s系列之&#xff1a;解释Kubernetes Operators 什么是控制器循环&#xff1f;Kubernetes Operator是如何工作的&#xff1f;如何添加自定义资源自定义资源定义Kubernetes Operators&#xff1a;案例研究 你是否曾想过&#xff0c;Site Reliability Engineering&#xff08;SR…

【优化】Nginx 配置页面请求不走缓存 浏览器页面禁用缓存

【优化】Nginx 配置页面请求不走缓存 禁用缓存 目录 【优化】Nginx 配置页面请求不走缓存 禁用缓存 对所有请求禁用缓存 对特定location禁用缓存 注意事项 全局禁用缓存 要配置Nginx使其不缓存内容&#xff0c;通常是指禁止浏览器缓存响应的内容&#xff0c;或者是在代理…

Qt 模仿企业微信图标实现按钮图片文字上下结构

简述 实现类似企业微信左侧导航栏的上下结构的按钮 效果图 可以用2种方案实现&#xff0c;2种最终效果图如下&#xff1a; 方案1 QToolButton 实现 ui.toolButton->setFixedSize(50, 50);ui.toolButton->setCheckable(true);ui.toolButton->setAutoExclusive(true);…

电源测试设备功能篇:测试仪器的灵活兼容与扩展

依托ATECLOUD智能云测试平台打造的电源ate自动测试设备&#xff0c;相较于传统的自动化测试系统&#xff0c;其突出特点在于提供了灵活的系统操作。这种操作灵活性不仅表现在自动化测试的便捷性、报告模板的多样化以及数据分析的灵活性上&#xff0c;还表现在电源测试仪器设备配…

覃嘉仪,艺人经纪人、经纪人、影视经纪人。2002.7.9出生于四川省遂宁市射洪县

覃嘉仪&#xff0c;艺人经纪人、经纪人、影视经纪人。2002.7.9出生于四川省遂宁市射洪县 2020年开始从事宣传工作&#xff0c;2023成为“WP经纪工作室”艺人经纪&#xff0c;现担任孙亦欣、魏逸熙等艺人的经纪人。 2024年涉足于影视行业&#xff0c;并加入嘉林娱乐。2024年在由…

部标JT808标准下的视频汇聚新方案:EasyCVR平台助力推动车辆监管智能化进程

在数字化转型的浪潮中&#xff0c;智慧城市的建设正以前所未有的速度推进&#xff0c;而市政车载设备作为城市运行的重要“神经末梢”&#xff0c;其智能化、联网化水平直接影响着城市管理效率与服务质量。近年来&#xff0c;随着部标协议&#xff08;即国家行业标准协议&#…