(数据结构)单链表——C语言

news2024/10/23 17:19:30

目录

1 概念与结构

1.1 结点

1.2 链表的性质

2 实现单链表

2.1打印SLPrint

2.2申请一个结点SLBuyNode

2.3尾插SLPushBack

2.4头插SLPushfront

2.5尾删SLPopBack

2.6头删SLPopfront

2.7查找结点位置SLFindNode

2.8在pos位置插入SLInsert

2.9在pos节点之后插入SLInsertAfter

2.10删除pos位置的结点SLErase

2.11删除pos位置之后后的结点SLEraseAfter

2.12销毁链表SLDestory

3.整体代码


1 概念与结构

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

1.1 结点

与顺序表不同的是,链表⾥的每节"⻋厢"都是独⽴申请下来的空间,我们称之为“结点/结点” 结点的组成主要有两个部分:当前结点要保存的数据和保存下⼀个结点的地址(指针变量)。 图中指针变量head保存的是第⼀个结点的地址,我们称head此时“指向”第⼀个结点,如果我们希望 head“指向”第⼆个结点时,只需要修改head保存的内容为0x0012。 链表中每个结点都是独⽴申请的(即需要插⼊数据时才去申请⼀块结点的空间),我们需要通过指针 变量来保存下⼀个结点位置才能从当前结点找到下⼀个结点。

1.2 链表的性质

1、链式机构在逻辑上是连续的,在物理结构上不⼀定连续

2、结点⼀般是从堆上申请的

3、从堆上申请来的空间,是按照⼀定策略分配出来的,每次申请的空间可能连续,可能不连续

结点对应的结构体代码:

struct SListNode
{
    int data; //结点数据
    struct SListNode* next; //指针变量⽤保存下⼀个结点的地址
};

当我们想要保存⼀个整型数据时,实际是向操作系统申请了⼀块内存,这个内存不仅要保存整型数 据,也需要保存下⼀个结点的地址(当下⼀个结点为空时保存的地址为空)。 当我们想要从第⼀个结点⾛到最后⼀个结点时,只需要在当前结点拿上下⼀个结点的地址就可以了。

2 实现单链表

这里也是先建三个文件,SList.h,SList.c,test.c,分别为头文件,实现函数的文件,测试文件

SList.h

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

typedef int SLDataType;

typedef struct SLNode
{
	SLDataType data;
	struct SLNode* next;
}SLNode;

void SLPrint(SLNode* phead);
SLNode* SLBuyNode(SLDataType x);

void SLPushBack(SLNode** pphead, SLDataType x);
void SLPushfront(SLNode** pphead, SLDataType x);
void SLPopBack(SLNode** pphead);
void SLPopfront(SLNode** pphead);

SLNode* SLFindNode(SLNode* phead, SLDataType x);
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x);
void SLInsertAfter( SLNode* pos,SLDataType x);
void SLErase(SLNode** pphead, SLNode* pos);
void SLEraseAfter(SLNode* pos);
void SLDestory(SLNode** pphead);

包括了结构体SLNode表示结点,这里有一个结构体自引用,用来找到下一个结点位置,以及实现的各种函数这里typedef的SLDataType,便于后续修改数据类型,这里以int类型为例。

2.1打印SLPrint

void SLPrint(SLNode* phead)
{
	SLNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

这里定义一个指向头结点的指针,当pcur不为NULL时,就打印pcur->data,pcur等于指向pcur的下一个节点,相当于循环里面的++,这是单链表遍历的方式,直到pcur为NULL停止。

2.2申请一个结点SLBuyNode

SLNode* SLBuyNode(SLDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

这里申请一个结点,所以返回类型是这个结构体指针,利用malloc函数申请,注意判断是否申请成功,再将值赋给newnode->data,newnode的下一个节点为NULL。

2.3尾插SLPushBack

void SLPushBack(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* newnode = SLBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else 
	{
		SLNode* ptail = *pphead;
		while (ptail->next)
		{
		ptail = ptail->next;
		}
	ptail->next = newnode;
	}
}

尾插即从链表尾部插入,注意这里传参需要传二级指针,因为这里可能会引起头结点的改变,要使实参的改变,就要传它的指针,本身它就是一级指针,所以这里传二级,首先断言pphead不能指为空,申请一个结点,如果链表为空,则直接将申请的结点直接赋给头结点,如果链表不为空,因为链表只能从上一个结点找到下一个,所以尾插之前,就要找到尾结点,就需要遍历整个链表,再将尾结点的下一个结点,指向newnode,即可。

2.4头插SLPushfront

void SLPushfront(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* newnode = SLBuyNode(x);

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

从头部插入,还是断言,然后申请一个节点,头插较为简单,直接将申请节点的next指向头结点,再将newnode赋给头结点。

2.5尾删SLPopBack

void SLPopBack(SLNode** pphead)
{
	assert(pphead && *pphead);
	SLNode* prev = *pphead;
	SLNode* ptail = *pphead;
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
	
}

从尾部删除元素,和尾插一样,如果链表只有一个结点直接free,置空就可以,如果链表不止一个结点,需要遍历,定义尾结点的前一个结点为prev,向后遍历两个依次向后移动直到ptail->next为空就停止,找到尾结点的前一个结点,再将prev的next置为空,然后free掉ptail就可以了。

2.6头删SLPopfront

void SLPopfront(SLNode** pphead)
{
	assert(pphead && *pphead);

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

从头部删除,先定义头结点的next结点,free掉头结点,再将next结点赋给next,成为新的头结点。

2.7查找结点位置SLFindNode

SLNode* SLFindNode(SLNode* phead, SLDataType x)
{
	assert(phead);
	SLNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;

查找,不会影响头结点的改变,所以这里可以传一级指针,这里就和尾插一样遍历,加一个判断pcur->data是否相等x,返回其结点就行,如果未找到就返回NULL。

2.8在pos位置插入SLInsert

void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLPushfront(pphead, x);
	}
	else
	{
	SLNode* prev = *pphead;
	SLNode* newnode = SLBuyNode(x);
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	newnode->next=pos;
	prev->next = newnode;
	}
}

在pos位置插入一个结点,先申请一个节点,如果在头结点位置插入,直接调用头插函数,如果不是头结点的位置,然后像尾差一样向后遍历找到pos的前一个节点,再将newnode的下一个结点指向pos,pos的前一个结点的next指向newnode即完成插入。

2.9在pos节点之后插入SLInsertAfter

void SLInsertAfter(SLNode* pos, SLDataType x)
{
	assert(pos);
	SLNode* newnode = SLBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

在pos之后,插入不需要遍历,直接在pos后插入,申请完节点后,直接将newnode->next结点指向pos的next,再将pos的next指向newnode。

2.10删除pos位置的结点SLErase

void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

这里删除结点,要断言,链表不为空,pos位置存在,和尾删一样遍历找到pos位置之前的prev,直接将prev的next指向pos的next,释放掉pos的空间,即删除结点。

2.11删除pos位置之后后的结点SLEraseAfter

void SLEraseAfter(SLNode* pos)
{
	assert(pos&&pos->next!=NULL);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

删除pos位置的节点,将pos的next定义为del,再将pos->next指向del->next,再释放掉del,置为NULL。

2.12销毁链表SLDestory

void SLDestory(SLNode** pphead)
{
	SLNode* pcur=*pphead;
	while (pcur)
	{
		SLNode*next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

循环链表,依次删除,直到pcur为空,最后再将头结点释放掉,置为空。

3.整体代码

SList.h

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

typedef int SLDataType;

typedef struct SLNode
{
	SLDataType data;
	struct SLNode* next;
}SLNode;

void SLPrint(SLNode* phead);
SLNode* SLBuyNode(SLDataType x);

void SLPushBack(SLNode** pphead, SLDataType x);
void SLPushfront(SLNode** pphead, SLDataType x);
void SLPopBack(SLNode** pphead);
void SLPopfront(SLNode** pphead);

SLNode* SLFindNode(SLNode* phead, SLDataType x);
void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x);
void SLInsertAfter( SLNode* pos,SLDataType x);
void SLErase(SLNode** pphead, SLNode* pos);
void SLEraseAfter(SLNode* pos);
void SLDestory(SLNode** pphead);

SList.c

#include"SList.h"

void SLPrint(SLNode* phead)
{
	SLNode* pcur = phead;
	while (pcur)
	{
		printf("%d->", pcur->data);
		pcur = pcur->next;
	}
	printf("NULL\n");
}

SLNode* SLBuyNode(SLDataType x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		exit(1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

void SLPushBack(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* newnode = SLBuyNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else 
	{
		SLNode* ptail = *pphead;
		while (ptail->next)
		{
		ptail = ptail->next;
		}
	ptail->next = newnode;
	}
}

void SLPushfront(SLNode** pphead, SLDataType x)
{
	assert(pphead);
	SLNode* newnode = SLBuyNode(x);

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

void SLPopBack(SLNode** pphead)
{
	assert(pphead && *pphead);
	SLNode* prev = *pphead;
	SLNode* ptail = *pphead;
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		while (ptail->next)
		{
			prev = ptail;
			ptail = ptail->next;
		}
		prev->next = NULL;
		free(ptail);
		ptail = NULL;
	}
	
}

void SLPopfront(SLNode** pphead)
{
	assert(pphead && *pphead);

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

SLNode* SLFindNode(SLNode* phead, SLDataType x)
{
	assert(phead);
	SLNode* pcur = phead;
	while (pcur)
	{
		if (pcur->data == x)
		{
			return pcur;
		}
		pcur = pcur->next;
	}
	return NULL;
}

void SLInsert(SLNode** pphead, SLNode* pos, SLDataType x)
{
	assert(pphead && *pphead);
	assert(pos);
	if (pos == *pphead)
	{
		SLPushfront(pphead, x);
	}
	else
	{
	SLNode* prev = *pphead;
	SLNode* newnode = SLBuyNode(x);
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	newnode->next=pos;
	prev->next = newnode;
	}
}

void SLInsertAfter(SLNode* pos, SLDataType x)
{
	assert(pos);
	SLNode* newnode = SLBuyNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

void SLErase(SLNode** pphead, SLNode* pos)
{
	assert(pphead && *pphead);
	assert(pos);
	SLNode* prev = *pphead;
	while (prev->next != pos)
	{
		prev = prev->next;
	}
	prev->next = pos->next;
	free(pos);
	pos = NULL;
}

void SLEraseAfter(SLNode* pos)
{
	assert(pos&&pos->next!=NULL);
	SLNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

void SLDestory(SLNode** pphead)
{
	SLNode* pcur=*pphead;
	while (pcur)
	{
		SLNode*next = pcur->next;
		free(pcur);
		pcur = next;
	}
	*pphead = NULL;
}

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

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

相关文章

2024HarmonyOS应用开发者高级认证 最新题库第二部分

单选题 1.以下哪个装饰器用来表示并发共享对象。&#xff08;D&#xff09; AShared BState CStyle DSendable 2.hiAppEvent提供的Watcher接口&#xff0c;需要订阅到OS的崩溃事件&#xff0c;正确的实现方式(选下面这个) hiAppEvent.addWatcher({ name:"watcher&…

探索网页组件化:原生JavaScript动态加载HTML与iframe的使用与比较

在网页设计中&#xff0c;将内容作为组件动态加载到页面上是一种提高用户体验和页面性能的有效手段。本文将详细介绍两种实现动态内容加载的方法&#xff1a;使用原生JavaScript动态加载HTML和使用iframe&#xff0c;并对比它们的使用方式和优缺点。 原生JavaScript动态加载HTM…

[论文笔记]HERMES 3 TECHNICAL REPORT

引言 今天带来论文HERMES 3 TECHNICAL REPORT&#xff0c;这篇论文提出了一个强大的工具调用模型&#xff0c;包含了训练方案介绍。同时提出了一个函数调用标准。 为了简单&#xff0c;下文中以翻译的口吻记录&#xff0c;比如替换"作者"为"我们"。 聊天模…

RISC-V笔记——内存模型总结

1 前言 Memory consistency model定义了使用Shared memory(共享内存)执行多线程(Multithread)程序所允许的行为规范。RISC-V使用的内存模型是RVWMO(RISC-V Weak Memory Ordering)&#xff0c;RVWMO内存模型是根据全局内存顺序(global memory order)定义的&#xff0c;全局内存…

简单有效修复d3d9.dll错误,11种d3d9.dll错误详细解决办法教程

当你遇到d3d9.dll文件丢失的问题时&#xff0c;可以通过今天的这篇文章详细的步骤来尝试修复这个问题&#xff0c;今天将教大家十一种d3d9.dll丢失修复的方法。 1. 重新安装DirectX以恢复d3d9.dll d3d9.dll是DirectX的一部分&#xff0c;因此重新安装DirectX通常可以解决d3d9.…

proguard对java代码进行混淆加密。并进行反编译测试,后续可配合classfinal进行使用

文章目录 1.插件混淆&#xff08;1.&#xff09;pom配置&#xff08;2.&#xff09;混淆配置&#xff08;3.&#xff09;打包 2.反编译查看效果&#xff08;1.&#xff09;工具下载&#xff08;2.&#xff09;反编译对比 3.启动测试4.功能测试5.二次加密 本文档只是为了留档方便…

【ChatGPT插件漏洞三连发之一】未授权恶意插件安装

漏洞 要了解第一个漏洞&#xff0c;我们必须首先向您展示 OAuth 身份验证的工作原理&#xff1a; 假设您是 Dan&#xff0c;并且您想使用您的 Facebook 帐户连接到 Example.com。当您点击“使用Facebook登录”时会发生什么&#xff1f; 在步骤 2-3 中&#xff1a; 在 Dan 单…

2024成为自动化测试的7种技能!

随着敏捷和DevOps等新时代项目开发方法逐渐取代旧的瀑布模型&#xff0c;测试需求在业界不断增长。测试人员现在正在与开发人员一起工作&#xff0c;自动化测试在许多方面极大地取代了手动测试。如果您是自动化测试领域的新手&#xff0c;刚雇用您的组织将期望您快速&#xff0…

java线程的几种状态

线程的所有状态 NEW: 安排了工作, 还未开始行动RUNNABLE: 可工作的. 又可以分成正在工作中和即将开始工作. BLOCKED: 这几个都表示排队等着其他事情 WAITING: 这几个都表示排队等着其他事情TIMED_WAITING: 这几个都表示排队等着其他事情TERMINATED: 工作完成了. 线程状态和状…

《黑神话悟空》各章节boss顺序汇总

第一章BOSS顺序&#xff1a; 1、牯护院&#xff1a;犀牛精&#xff0c;位于苍狼岭娟&#xff0c;击败后能获得定身术。 2、广智&#xff1a;火刀狼&#xff0c; 位于观音禅院&#xff0c;击败后获得广智变身&#xff0c;记得敲钟。 3、蓝皮幽魂&#xff1a;蓝皮大头&#xff0…

大模型入门到精通!大模型应用开发极简入门(含PDF)

大模型的出现正悄然改变人们的生活与工作方式&#xff0c;比如ChatGPT-4、文心一言、通义千问等语言大模型。它们已帮助很多办公室“白领”们在解决日常工作问题&#xff0c;如制定计划、撰写实施方案&#xff0c;甚至制作美化PPT等&#xff08;笔者及身边的同事在工作中还经常…

Star Tower:智能合约的安全基石与未来引领者

在区块链技术的快速发展中&#xff0c;智能合约作为新兴的应用形式&#xff0c;正逐渐成为区块链领域的重要组成部分。然而&#xff0c;智能合约的可靠性问题一直是用户最为关心的焦点之一。为此&#xff0c;Star Tower以其强大的技术实力和全面的安全保障措施&#xff0c;为智…

pytest中@pytest.fixture常用顺序function

ytest中pytest.fixture用法讲解 1、测试函数开始之前2、执行测试函数&#xff1a;3、测试函数结束后&#xff1a; 备注&#xff1a;内容来自chatGPT 在 pytest 中&#xff0c;pytest.fixture 是一个非常强大的功能&#xff0c;用于设置测试所需的环境和状态。它可以通过 scope…

听泉鉴宝在三个月前已布局商标注册!

近日“听泉鉴宝”以幽默的风格和节目效果迅速涨粉至2500多万&#xff0c;连线出现“馆藏文物”和“盗墓现场”等内容&#xff0c;听泉鉴宝早在几个月前已布局商标注册。 据普推知产商标老杨在商标局网站检索发现&#xff0c;“听泉鉴宝”的主人丁某所持股的江苏灵匠申请了三十…

R语言统计分析——置换检验2

参考资料&#xff1a;R语言实战【第2版】 独立两样本和K样本检验 # 安装coin包 install.packages(c("coin")) # 加载coin包 library(coin) # 创建检验数据集 score<-c(40,57,45,55,58,57,64,55,62,65) treatment<-factor(c(rep("A",5),rep("B…

51单片机应用——直流电机PWM调速

目标实现功能 单片机引脚输出PWM波形控制直流电机以不同转速工作。 1.PWM简介 PWM技术 PWM的中文全称是脉宽调制&#xff0c;常用于电动机控制、开关电源、音频放大器等。利用PWM技术可以达到微处理器&#xff08;如单片机&#xff09;的数字输出对模拟电路控制的效果。 P…

膜结构首次应用于国内游轮项目—轻空间

轻空间&#xff08;江苏&#xff09;膜科技有限公司近日成功中标一项国内游轮项目&#xff0c;这一成就标志着膜结构在游轮船舶行业的首次应用&#xff0c;开启了新的市场探索之旅。 作为行业先锋&#xff0c;轻空间始终致力于将创新技术融入多元化场景。本次游轮项目的成功中标…

Opensearch集群部署【docker、服务器、Helm多种部署方式】

操作系统兼容性 我们建议在 Red Hat Enterprise Linux (RHEL) 或使用systemd的基于 Debian 的 Linux 发行版上安装 OpenSearch &#xff0c;例如 CentOS、Amazon Linux 2 和 Ubuntu Long-Term Support (LTS)。OpenSearch 应该适用于大多数 Linux 发行版&#xff0c;但我们只测…

2023年五一杯数学建模C题双碳目标下低碳建筑研究求解全过程论文及程序

2023年五一杯数学建模 C题 双碳目标下低碳建筑研究 原题再现&#xff1a; “双碳”即碳达峰与碳中和的简称&#xff0c;我国力争2030年前实现碳达峰&#xff0c;2060年前实现碳中和。“双碳”战略倡导绿色、环保、低碳的生活方式。我国加快降低碳排放步伐&#xff0c;大力推进…

工业一体机为软件开发商提供稳定可靠的硬件平台

在当今数字化、智能化的工业时代&#xff0c;软件在工业生产和管理中的作用日益凸显。而软件的高效运行离不开稳定可靠的硬件平台&#xff0c;工业一体机正是这样一个为软件开发商提供坚实支撑的关键设备。 工业一体机的铝合金工艺和无风扇设计为软件运行创造了良好的散热环境。…