数据结构——单链表的实现(c语言版)

前言 

        单链表作为顺序表的一种,了解并且熟悉它的结构对于我们学习更加复杂的数据结构是有一定意义的。虽然单链表有一定的缺陷,但是单链表也有它存在的价值, 它也是作为其他数据结构的一部分出现的,比如在图,哈希表中。

目录

1.链表节点的结构

2.头插头删

3.尾插尾删

4.任意位置的插入和删除

5.查找链表的值和修改链表节点的值

6.销毁链表

7.测试代码

8.全部代码

9.总结 


1.链表节点的结构

        单链表有节点的值和节点的next指针组成,如图:

 

typedef int SListDatatype;
typedef struct SListNode
{
	SListDatatype _data;//存储节点的数据
	struct SListNode* _next;
}SListNode;

2.头插头删

        头插分为两种情况,第一种是没有节点的情况,第二种是 有节点的情况。如图:

                头删也分为两种情况,如果只有一个节点的时候,直接删除就行了,然后将头结点置空。如果有多个节点,需要先记录头结点,然后再进行删除就可以了。

void SListPushFront(SListNode** ppHead, SListDatatype data)//头插
{
	SListNode* newNode = SlistBuyNode(data);//申请一个新的节点
	if (*ppHead == NULL)
	{
		//链表为空
		*ppHead = newNode;
		return;
	}
	newNode->_next = (*ppHead);
	*ppHead = newNode;//对头结点进行链接
}
void SListPopFront(SListNode** ppHead)//头删
{
	assert(*ppHead);//确保指针的有效性
	if ((*ppHead)->_next == NULL)
	{
		//链表只有一个节点
		free(*ppHead);
		*ppHead = NULL;
		return;
	}
	//删除头结点,然后更新头结点
	SListNode* newHead = (*ppHead)->_next;
	free(*ppHead);
	*ppHead = newHead;
	return;
}

3.尾插尾删

        尾插也分为链表为空和指针不为空的情况,如果链表为空,申请节点,让链表的头结点指向申请的节点,然后将这个节点的_next置空,如果链表不为空,首先需要找到尾结点,然后将尾结点与这个节点链接起来,再将这个新申请的节点的_next置空。如图:

 

        尾删也分为两种情况:1只有一个节点和2存在多个节点

如果只有一个节点,删除以后需要将头结点置空,防止出现野指针的问题。

如果有多个节点,删除尾结点以后需要将新的尾结点置空。

如图: 

void SListPushBack(SListNode** ppHead, SListDatatype data)//尾插
{
	SListNode*newNode =  SlistBuyNode(data);//申请一个新的节点
	
	if (*ppHead == NULL)//链表为空
	{
		*ppHead = newNode;
		return;
	}
	if ((*ppHead)->_next == NULL)//链表只存在一个节点
	{
		(*ppHead)->_next = newNode;
		return;
	}
	SListNode* cur = *ppHead;
	while (cur->_next)//找到尾节点
	{
		cur = cur->_next;
	}
	cur->_next = newNode;//进行链接
	return;
}
void SListPopBack(SListNode** ppHead)//尾删
{
	assert(*ppHead);
	if (*ppHead == NULL)//链表为空不需要删除
	{
		return;
	}
	if ((*ppHead)->_next == NULL)
	{
		free(*ppHead);//链表只有一个节点
		(*ppHead) = NULL;
		return;
	}
	SListNode* cur = *ppHead;
	SListNode* prev = NULL;

	while (cur->_next)//找到尾结点
	{
		prev = cur;//保存上一个节点
		cur = cur->_next;
	}
	free(cur);//释放尾结点所在的空间
	prev->_next = NULL;//将上一个节点的_next置空
	return;

4.任意位置的插入和删除

        由于单链表结构的限制,这里只实现了在pos位置之后的插入和删除,如果删除pos的后一个节点就需要确保pos的后一个节点是存在的,否则就会出现问题。

void SListInsertAfter(SListNode*pos, SListDatatype data)//任意位置的插入,在pos之后插入
{
	assert(pos);//确保指针不为空
	SListNode* newNode = SlistBuyNode(data);
	SListNode* next = pos->_next;
	pos->_next = newNode;
	newNode->_next = next;
}
void SListErase(SListNode*pos)//任意位置的删除,pox位置之后的删除
{
	assert(pos);//确保节点的有效性
	//如果只有一个节点
	if (pos->_next )//pos节点的下一个节点存在
	{
		SListNode* next = pos->_next;
		SListNode* nextNext = next->_next;
		free(next);//删除节点,重新链接
		pos->_next = nextNext;
	}
}

5.查找链表的值和修改链表节点的值

        遍历链表就可以对链表中的数据进行查找,找到查找的值,就可以对节点的值进行修改。 

SListNode* SListFind(SListNode* pHead, SListDatatype data)//查找
{
	SListNode* cur = pHead;
	while (cur)
	{
		if (cur->_data == data)
			return cur;
		cur = cur->_next;//迭代向后走
	}
	return NULL;//找不到
}

 

void testSList()
{
	//查找和修改的测试
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListNode* node = SListFind(pHead, 5);//查找
	if (node)
	{
		//节点的数据
		node->_data = 50;
	}
	SListPrint(pHead);
}

6.销毁链表

void SListDestory(SListNode** ppHead)//销毁
{
	assert(*ppHead);
	//确保指针有效性
	SListNode* cur = *ppHead;
	while (cur)
	{
		SListNode* freeNode = cur;
		cur = cur->_next;
		free(freeNode);

	}
	*ppHead = NULL;
}

 

7.测试代码

void testSListBack()
{
	//尾插尾删的测试代码
	SListNode* pHead = NULL;
	SListPushBack(&pHead, 1);
	SListPushBack(&pHead, 2);
	SListPushBack(&pHead, 3);
	SListPushBack(&pHead, 4);
	SListPushBack(&pHead, 5);
	SListPushBack(&pHead, 6);
	SListPrint(pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);


}
void testSListFront()
{
	//头插头删的测试代码
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
}
void testSList()
{
	//查找和修改的测试
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListNode* node = SListFind(pHead, 5);//查找
	if (node)
	{
		//节点的数据
		node->_data = 50;
	}
	SListPrint(pHead);
}
void TestSList1()
{
	//对在pos节点之后进行插入和删除的测试
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListNode* node = SListFind(pHead, 5);//查找
	if (node)
	{
		
		//插入节点
		SListInsertAfter(node, -2);
		SListPrint(pHead);

		SListErase(node);
		SListPrint(pHead);

	}
	SListDestory(&pHead);
}

8.全部代码

//SList.h

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<assert.h>
typedef int SListDatatype;
typedef struct SListNode
{
	SListDatatype _data;//存储节点的数据
	struct SListNode* _next;
}SListNode;
SListNode* SlistBuyNode(SListDatatype data);


void SListDestory(SListNode** ppHead);//销毁
void SListPushBack(SListNode**ppHead,SListDatatype data);//尾插
void SListPopBack(SListNode** ppHead );//尾删

void SListPushFront(SListNode** ppHead, SListDatatype data);//头插
void SListPopFront(SListNode** ppHead);//头删

void SListInsertAfter(SListNode* pos, SListDatatype data);//任意位置的插入

void SListErase(SListNode*pos);//任意位置的删除

SListNode* SListFind(SListNode*pHead, SListDatatype data);//查找
void SListPrint(SListNode* pHead);//显示链表数据
//void SListDestory(SListNode** ppHead);//删除链表

 //SList.c

#include"SList.h"

SListNode* SlistBuyNode(SListDatatype data)
{
	 SListNode*newNode = (SListNode*)malloc(sizeof(SListNode));
	 if (newNode == NULL)
	 {
		 //申请节点失败
		 printf("申请节点失败\n");
		 exit(-1);//暴力返回
	 }
	 newNode->_data = data;//给节点赋值
	 newNode->_next = NULL;

	 return newNode;
}

void SListDestory(SListNode** ppHead)//销毁
{
	assert(*ppHead);
	//确保指针有效性
	SListNode* cur = *ppHead;
	while (cur)
	{
		SListNode* freeNode = cur;
		cur = cur->_next;
		free(freeNode);

	}
	*ppHead = NULL;
}
void SListPushBack(SListNode** ppHead, SListDatatype data)//尾插
{
	SListNode*newNode =  SlistBuyNode(data);//申请一个新的节点
	
	if (*ppHead == NULL)//链表为空
	{
		*ppHead = newNode;
		return;
	}
	if ((*ppHead)->_next == NULL)//链表只存在一个节点
	{
		(*ppHead)->_next = newNode;
		return;
	}
	SListNode* cur = *ppHead;
	while (cur->_next)//找到尾节点
	{
		cur = cur->_next;
	}
	cur->_next = newNode;//进行链接
	return;
}
void SListPopBack(SListNode** ppHead)//尾删
{
	assert(*ppHead);
	if (*ppHead == NULL)//链表为空不需要删除
	{
		return;
	}
	if ((*ppHead)->_next == NULL)
	{
		free(*ppHead);//链表只有一个节点
		(*ppHead) = NULL;
		return;
	}
	SListNode* cur = *ppHead;
	SListNode* prev = NULL;

	while (cur->_next)//找到尾结点
	{
		prev = cur;//保存上一个节点
		cur = cur->_next;
	}
	free(cur);//释放尾结点所在的空间
	prev->_next = NULL;//将上一个节点的_next置空
	return;
}
void SListPushFront(SListNode** ppHead, SListDatatype data)//头插
{
	SListNode* newNode = SlistBuyNode(data);//申请一个新的节点
	if (*ppHead == NULL)
	{
		//链表为空
		*ppHead = newNode;
		return;
	}
	newNode->_next = (*ppHead);
	*ppHead = newNode;//对头结点进行链接
}
void SListPopFront(SListNode** ppHead)//头删
{
	assert(*ppHead);//确保指针的有效性
	if ((*ppHead)->_next == NULL)
	{
		//链表只有一个节点
		free(*ppHead);
		*ppHead = NULL;
		return;
	}
	//删除头结点,然后更新头结点
	SListNode* newHead = (*ppHead)->_next;
	free(*ppHead);
	*ppHead = newHead;
	return;
}
void SListInsertAfter(SListNode*pos, SListDatatype data)//任意位置的插入,在pos之后插入
{
	assert(pos);//确保指针不为空
	SListNode* newNode = SlistBuyNode(data);
	SListNode* next = pos->_next;
	pos->_next = newNode;
	newNode->_next = next;
}
void SListErase(SListNode*pos)//任意位置的删除,pox位置之后的删除
{
	assert(pos);//确保节点的有效性
	//如果只有一个节点
	if (pos->_next )//pos节点的下一个节点存在
	{
		SListNode* next = pos->_next;
		SListNode* nextNext = next->_next;
		free(next);//删除节点,重新链接
		pos->_next = nextNext;
	}
}

SListNode* SListFind(SListNode* pHead, SListDatatype data)//查找
{
	SListNode* cur = pHead;
	while (cur)
	{
		if (cur->_data == data)
			return cur;
		cur = cur->_next;//迭代向后走
	}
	return NULL;//找不到
}
void SListPrint(SListNode* pHead)//显示链表数据
{
	assert(pHead);//确保指针的有效性
	SListNode* cur = pHead;
	while (cur)
	{
		printf("%d ", cur->_data);
		printf("->");
		cur = cur->_next;
	}
	printf("NULL\n");
}

//test.c

#include"SList.h"
void testSListBack()
{
	//尾插尾删的测试代码
	SListNode* pHead = NULL;
	SListPushBack(&pHead, 1);
	SListPushBack(&pHead, 2);
	SListPushBack(&pHead, 3);
	SListPushBack(&pHead, 4);
	SListPushBack(&pHead, 5);
	SListPushBack(&pHead, 6);
	SListPrint(pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);
	SListPopBack(&pHead);


}
void testSListFront()
{
	//头插头删的测试代码
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
	SListPopFront(&pHead);
}
void testSList()
{
	//查找和修改的测试
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListNode* node = SListFind(pHead, 5);//查找
	if (node)
	{
		//节点的数据
		node->_data = 50;
	}
	SListPrint(pHead);
}
void TestSList1()
{
	//对在pos节点之后进行插入和删除的测试
	SListNode* pHead = NULL;
	SListPushFront(&pHead, 1);
	SListPushFront(&pHead, 2);
	SListPushFront(&pHead, 3);
	SListPushFront(&pHead, 4);
	SListPushFront(&pHead, 5);
	SListPushFront(&pHead, 6);
	SListPrint(pHead);
	SListNode* node = SListFind(pHead, 5);//查找
	if (node)
	{
		
		//插入节点
		SListInsertAfter(node, -2);
		SListPrint(pHead);

		SListErase(node);
		SListPrint(pHead);

	}
	SListDestory(&pHead);
}
int main()
{
	TestSList1();
	return 0;
}

 

9.总结 

        链表与顺序表区别和联系。顺序表是在数组的基础上实现增删查改的。并且插入时可以动态增长。顺序表的缺陷:可能存在空间的浪费,增容有一定的效率损失,中间或者头部数据的删除,时间复杂度是O(n),因为要挪动数据。这些问题都是由链表来解决的,但是链表也有自己的缺陷,不能随机访问,存在内存碎片等问题。 其实没有哪一种数据结构是完美的,它们都有各自的缺陷,实际中的使用都是相辅相成的。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

java之junit Test

JUnit测试简介 1.什么是单元测试 单元测试是针对最小的功能单元编写测试代码Java程序最小的功能单元是方法单元测试就是针对单个Java方法的测试 2.测试驱动开发 3.单元测试的好处 确保单个方法运行正常如果修改了方法代码&#xff0c;只需确保其对应的单元测试通过测试代码…

【深度学习】【风格迁移】Visual Concept Translator,一般图像到图像的翻译与一次性图像引导,论文

General Image-to-Image Translation with One-Shot Image Guidance 论文&#xff1a;https://arxiv.org/abs/2307.14352 代码&#xff1a;https://github.com/crystalneuro/visual-concept-translator 文章目录 Abstract1. Introduction2. 相关工作2.1 图像到图像转换2.2. Di…

使用chatGPT生成提示词,在文心一言生成装修概念图

介绍 家是情感的港湾&#xff0c;而家居装修则是将情感融入空间的艺术。如何在有限的空间里展现个性与美感&#xff0c;成为了现代人关注的焦点。而今&#xff0c;随着人工智能的发展&#xff0c;我们发现了一个新的创意助手——ChatGPT&#xff0c;它不仅为我们带来了更多可能…

nodejs+vue+elementui招聘求职网站系统的设计与实现-173lo

&#xff08;1&#xff09;管理员的功能是最高的&#xff0c;可以对系统所在功能进行查看&#xff0c;修改和删除&#xff0c;包括企业和用户功能。管理员用例如下&#xff1a; 图3-1管理员用例图 &#xff08;2&#xff09;企业关键功能包含个人中心、岗位类型管理、招聘信息…

C语言每日一题:16:数对。

思路一&#xff1a;基本思路 1.x,y均不大于n&#xff0c;就是小于等于n。 2.x%y大于等于k。 3.一般的思路使用双for循环去遍历每一对数。 代码实现&#xff1a; #include <stdio.h> int main() {int n 0;int k 0;//输入scanf("%d%d", &n, &k);int x…

【深度学习注意力机制系列】—— ECANet注意力机制(附pytorch实现)

ECANet&#xff08;Efficient Channel Attention Network&#xff09;是一种用于图像处理任务的神经网络架构&#xff0c;它在保持高效性的同时&#xff0c;有效地捕捉图像中的通道间关系&#xff0c;从而提升了特征表示的能力。ECANet通过引入通道注意力机制&#xff0c;以及在…

【Plex】FRP内网穿透后 App无法使用问题

能搜索到这个文章的&#xff0c;应该都看过这位同学的分析【Plex】FRP内网穿透后 App无法使用问题_plex frp无效_Fu1co的博客-CSDN博客 这个是必要的过程&#xff0c;但是设置之后仍然app端无法访问&#xff0c;原因是因为网络端口的问题 这个里面的这个公开端口&#xff0c;可…

STM32 F103C8T6学习笔记1:开发环境与原理图的熟悉

作为一名大学生&#xff0c;学习单片机有一段时间了&#xff0c;也接触过嵌入式ARM的开发&#xff0c;但从未使用以及接触过STM32C8T6大开发使用&#xff0c;于是从今日开始&#xff0c;将学习使用它~ 本文介绍STM32C8T6最小系统开发环境搭建注意问题&#xff0c;STM32C8T6单片…

WPF上位机9——Lambda和Linq

Lambda Linq 操作集合 使用类sql形式查询 Linq To SQL

微服务学习笔记-基本概念

微服务是一种经过良好架构设计的分布式架构方案。根据业务功能对系统做拆分&#xff0c;每个业务功能模块作为独立项目开发&#xff0c;称为一个服务。 微服务的架构特征&#xff1a; 单一职责&#xff1a;微服务拆分粒度更小&#xff0c;每一个服务都对应唯一的业务能力&…

Vue实现详细界面里面有一个列表

目录 Vue实现详细界面里面有一个列表 理一下思路&#xff1a; 效果如下&#xff1a; 1、 主页面正常写 2、详细界面(重点) 3、详细界面里面的列表(重点) 要点&#xff1a; Vue实现详细界面里面有一个列表 理一下思路&#xff1a; 1、首先需要这条数据的主键id&#xff…

SpringSpringBoot常用注解

目录 一、核心注解二、Spring Bean 相关2.1 Autowired2.2 Component, Repository, Service, Controller2.3 RestController 与 Controller2.4 Configuration 与 Component2.5 Scope 三、处理常见的 HTTP 请求类型3.1 GET 请求3.2 POST 请求3.3 PUT 请求3.4 DELETE 请求3.5 PATC…

【Python】背景及环境搭建

文章目录 了解计算机一、Python背景知识一、Python环境搭建 努力经营当下 直至未来明朗 了解计算机 示例&#xff1a;使用电脑访问B站 1&#xff09; 本地的计算机会给B站服务器发送一个网络请求&#xff08;如&#xff1a;谁&#xff0c;想看哪个视频&#xff09; 2&#xf…

MySQL8安装教程 保姆级(Windows))

下载 官网: mysql官网点击Downloads->MySQL Community(GPL) Downloads->MySQL Community Server(或者点击MySQL installer for Windows) Windows下有两种安装方式 在线安装 一般带有 web字样 这个需要联网离线安装 一般没有web字样 安装 下载好之后,版本号可以不一样&…

《系统架构设计师教程》重点章节思维导图

内容来自《系统架构设计师教程》&#xff0c;筛选系统架构设计师考试中分值重点分布的章节&#xff0c;根据章节的内容整理出相关思维导图。 重点章节 第2章&#xff1a;计算机系统知识第5章&#xff1a;软件工程基础知识第7章&#xff1a;系统架构设计基础知识第8章&#xff1…

尚硅谷大数据项目《在线教育之采集系统》笔记003

视频地址&#xff1a;尚硅谷大数据项目《在线教育之采集系统》_哔哩哔哩_bilibili 目录 P036 P037 P038 P039 P041 P042 P043 P044 P045 P046 P036 先启动zookeeper&#xff0c;在启动kafka&#xff0c;启动hadoop中的hdfs node003启动flume&#xff0c;node001启动f…

云原生网关API标准背景及发展现状

Gateway API是一个开源的API标准&#xff0c;源自Kubernetes SIG-NETWORK兴趣组。从出身角度讲&#xff0c;可谓根正苗红&#xff0c;自从开源以来备受关注&#xff0c;被寄予厚望。Gateway API旨在通过声明式、可扩展性和面向角色的接口来发展Kubernetes服务网络&#xff0c;并…

Springboot开发常用注解

文章目录 1.RestController2.Data3.RequestMapping4.Builder5.RequestBody6.Slf4j7.execution写法8.http协议及servlet7.JoinPoint 1.RestController RestController注解其实就是将 return 中的内容以 JSON字符串的形式返回客户端 controller的详解 2.Data Data详解 3.Reque…

【佳佳怪文献分享】MVFusion: 利用语义对齐的多视角 3D 物体检测雷达和相机融合

标题&#xff1a;MVFusion: Multi-View 3D Object Detection with Semantic-aligned Radar and Camera Fusion 作者&#xff1a;Zizhang Wu , Guilian Chen , Yuanzhu Gan , Lei Wang , Jian Pu 来源&#xff1a;2023 IEEE International Conference on Robotics and Automat…

图像处理技巧形态学滤波之膨胀操作

1. 引言 欢迎回来&#xff0c;我的图像处理爱好者们&#xff01;今天&#xff0c;让我们继续研究图像处理领域中的形态学计算。在本篇中&#xff0c;我们将重点介绍腐蚀操作的反向效果膨胀操作。 闲话少说&#xff0c;我们直接开始吧&#xff01; 2. 膨胀操作原理 膨胀操作…