【数据结构】线性表之单链表(讲解实现——带动图理解)

news2024/11/23 5:12:52


单链表

  • 单链表的优点

1.头部和中间插入或删除数据效率高,无需挪动。

2.按照需求申请释放空间,无需担心空间不够用。

  • 单链表的缺点

1.不可以进行下标随机访问。

2.复杂度是O(n)

3.反向遍历困难

单链表是线性表的一种,单链表是链式存储的线性表,不同于单链表,链表在内存空间中不连续,而是由结构体内的next指针下一条数据进行链接🧐

PS:需要源码直接通过目录跳转到最后

单链表主体结构

默认大小与扩容大小还有类型都可以是任意大小或类型

typedef int LLDataType;				//数据类型

typedef struct LinkedList
{
	LLDataType data;					//用于存放数据
	struct LinkedList* next;			//指向下一个数据
}LinkedList;

在这里插入图片描述

单链表操作函数介绍

  1. void printLL(LinkedList* Phead); //打印单链表
  2. void LLPushBack(LinkedList** PPhead, LLDataType x); //尾插
  3. void LLPushFront(LinkedList** PPhead, LLDataType x); //头插
  4. void LLPopBack(LinkedList** PPhead); //尾删
  5. void LLPopFront(LinkedList** PPhead); //尾插
  6. LinkedList* LListFind(LinkedList* phead, LLDataType x); //查找
  7. void SListInsertAfter(LinkedList* pos, LLDataType x); //在指定位置后插入
  8. void SListEraseAfter(LinkedList* pos); //删除指定位置后的一个表
  9. void SListDestroy(LinkedList** Phead); //销毁单链表
  10. void SListInsertFront(LinkedList** Phead, LinkedList* pos, LLDataType x); //在指定位置前插入
  11. void SListErase(LinkedList** PPhead, LinkedList* pos); //删除指定位置数据

为了代码方便阅读,讲单链表操作函数全部放在LinkedList.c文件中,将头文件放在LinkedList.h,测试文件test.c 😮

单链表操作函数实现

为了方便调试,建议每写完1-2个函数就进行测试,初始化之后,首先实现print函数可以方便我们进行调试。

单链表的初始化:

LinkedList *Phead = NULL;	//未进行插入之前,头结点指针指向NULL

必须先进性初始化,讲链表指向NULL避免野指针问题。

打印函数

写完插入立马写打印,方便进行调试

void printLL(LinkedList* Phead)
{
	while (Phead!=NULL)
	{
		printf("%d->", Phead->data);
		Phead = Phead->next;
	}
	printf("NULL");
}

单链表插入函数:

头插

头插将新结点插入到头结点

1.如果头结点不为空,头结点改为新插入的结点,原本的第一个位置的结点需要连接到新结点的next。

2.如果头结点为空,那么直接将新结点给到头结点指针phead

  • 既然有插入就一定会有空间不够用的问题,这里用到检查空间是否够用的一个函数
LinkedList* BuyNewNode(LLDataType x)		//申请一个新结点并将next指向空
{
	LinkedList* ret = (LinkedList*)malloc(sizeof(LinkedList));
	if (ret == NULL)
	{
		perror("Malloc:");
		return NULL;
	}
	ret->data = x;
	ret->next = NULL;
	return ret;
}

void LLPushFront(LinkedList** PPhead, LLDataType x)
 // 使用二级指针是因为我们要改变头结点,所有必须要使用头结点的指针来改变。
{
	assert(PPhead);			
	LinkedList* NewNode = BuyNewNode(x);
	if (*PPhead == NULL)
	{
		*PPhead = NewNode;
	}
	else
	{
		NewNode->next = *PPhead;
		*PPhead = NewNode;
	}
}

头结点为空

  • 如果头结点为空,那么直接将新结点给到头结点指针phead

在这里插入图片描述

头结点不为空

  • 如果头结点不为空,头结点改为新插入的结点,原本的第一个位置的结点需要连接到新结点的next。

在这里插入图片描述

尾插

从最后一个结点后面插入新结点

  • 尾插也会用到申请新结点的函数,这里不重复了
void LLPushBack(LinkedList** PPhead, LLDataType x)
// 使用二级指针是因为我们要改变头结点,所有必须要使用头结点的指针来改变。
{
	assert(PPhead);
	LinkedList* NewNode = BuyNewNode(x);	//也用到了上面的BugNode不重复了
	if (*PPhead == NULL)
	{
		*PPhead = NewNode;
	}
	else
	{
		LinkedList* tail = *PPhead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = NewNode;
	}
}

头结点不为空

  • 如果头结点不为空,先找到最后一个结点(指向NULL的就是最后一个结点),然后将新结点插入到最后一个结点的后面

在这里插入图片描述

头结点为空

  • 如果头结点为空,那么直接将新结点给到头结点指针phead(与上面头插头结点为空相同)

在这里插入图片描述

指定结点后插入和查找函数

相对上面两个简单,下面插入函数都需搭配查找结点函数使用,查找函数就是遍历,找到返回地址,比较简单,就不多描述了。

LinkedList* LListFind(LinkedList* phead, LLDataType x)
{
	assert(phead);
	LinkedList *ret = phead;
	while (ret->data != x)
	{
		if (ret == NULL)
		{
			return NULL;
		}
		ret = ret->next;
	}
	return ret;
}

void SListInsertAfter(LinkedList* pos, LLDataType x)
{
	assert(pos);
	LinkedList *newnode = BuyNewNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

在这里插入图片描述

单链表结点之前插入数据

void SListInsertFront(LinkedList** PPhead, LinkedList* pos, LLDataType x)
{
	assert(pos);
	assert(PPhead);
	if (*PPhead == NULL||*PPhead==pos)
	{
		LLPushFront(PPhead, x);
	}
	else
	{
		LinkedList* cur = *PPhead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		LinkedList* newnode = BuyNewNode(x);
		newnode->next = cur->next;
		cur->next = newnode;
	}
}
  • 头结点为空或插入结点就是头结点指向的结点,直接复用头插函数即可

在这里插入图片描述

  • 头结点不为空或插入结点就是头结点指向的结点,找到pos前一个结点进行插入

在这里插入图片描述

单链表删除函数

头删

从头部删除数据,将头结点位置删除,将头结点的下一个位置指向头结点

void LLPopFront(LinkedList** PPhead)
{
	assert(PPhead);
	assert(*PPhead);
	LinkedList* temp = (*PPhead)->next;
	free(*PPhead);			//释放掉删除的结点
	*PPhead = temp;
}

在这里插入图片描述

尾删

void LLPopBack(LinkedList** PPhead)
{
	assert(PPhead);
	assert(*PPhead);
	if ((*PPhead)->next == NULL)
	{
		*PPhead = NULL;
	}
	else
	{
		LinkedList* tail = *PPhead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}
  • 如果头结点为空,不能进行删除。
  • 如果头结点下一个为空,直接进行删除
  • 如果头结点和头结点的下一个都不为空,删除下一个结点为空的结点,那就需要找到头结点下一个结点的下一个结点为空的结点

在这里插入图片描述

指定结点后删除

后面的数据依次向前移动

void SListEraseAfter(LinkedList* pos)
{
	if (pos == NULL)
	{
		printf("没发现此数据");
		return;
	}
	if (pos->next == NULL)
	{
		printf("链表后面已经为空了\n");
		return;
	}
	LinkedList* temp = pos->next;
	pos->next = pos->next->next;
	free(temp);
}
  • 如果pos为空或pos->next为空则提示并返回

在这里插入图片描述

指定结点删除

删除指定结点,将指定结点的next连接到指定结点前面的next结点,然后释放指定的结点。

void SListErase(LinkedList**PPhead,LinkedList* pos)
{
	assert(pos);
	assert(PPhead);
	assert(*PPhead);
	if (pos == *PPhead)
	{
		LLPopFront(PPhead);
		return;
	}
	LinkedList* cur = *PPhead;
	while (cur->next != pos)
	{
		cur = cur->next;
	}
	cur->next = cur->next->next;
	free(pos);
}
  • 头结点指针指向的结点等于pos,复用尾插函数

在这里插入图片描述

销毁单链表

将每个结点依次释放。最后将头结点制空。

void SListDestroy(LinkedList** PPhead)
{
	assert(*PPhead);
	if (*PPhead == NULL)
	{
		printf("已经是空链表了\n");
		return;
	}
	while ((*PPhead)->next != NULL)
	{
		LinkedList* tep = (*PPhead)->next;
		(*PPhead)->next = (*PPhead)->next->next;
		free(tep);
	}
	free(*PPhead);
	*PPhead = NULL;
}

在这里插入图片描述


文件分类

🌞🌞为了使代码更有阅读性,我们不建议把所有函数写在一个文件里,所以这里分成三个文件,模块化管理

test.c

测试文件

#include "LinkedList.h"

int main()
{
	LinkedList *Phead = NULL;
	LLPushFront(&Phead, 8);
	LLPushBack(&Phead,2);
	LLPushBack(&Phead,3);
	LLPushBack(&Phead,4);
	LLPushBack(&Phead,5);
	//LLPopBack(&Phead);
	//LLPopFront(&Phead);
	//LLPopBack(&Phead);
	//LLPopFront(&Phead);
	//LLPopFront(&Phead);
	LinkedList* temp = LListFind(Phead, 3);
	SListInsertAfter(temp,88);
	temp = LListFind(Phead, 4);
	SListEraseAfter(temp);
	temp = LListFind(Phead, 4);

	SListInsertFront(&Phead,temp, 99);
	printLL(Phead);
	temp = LListFind(Phead, 99);
	SListErase(&Phead, temp);
	SListDestroy(&Phead);
}

LinkedList.c

将所有单链表需要的函数封装在此文件下⭐

#include "LinkedList.h"

LinkedList* BuyNewNode(LLDataType x)
{
	LinkedList* ret = (LinkedList*)malloc(sizeof(LinkedList));
	if (ret == NULL)
	{
		perror("Malloc:");
		return NULL;
	}
	ret->data = x;
	ret->next = NULL;
	return ret;
}

void LLPushBack(LinkedList** PPhead, LLDataType x)
{
	assert(PPhead);
	LinkedList* NewNode = BuyNewNode(x);
	if (*PPhead == NULL)
	{
		*PPhead = NewNode;
	}
	else
	{
		LinkedList* tail = *PPhead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = NewNode;
	}
}

void LLPushFront(LinkedList** PPhead, LLDataType x)
{
	assert(PPhead);
	LinkedList* NewNode = BuyNewNode(x);
	if (*PPhead == NULL)
	{
		*PPhead = NewNode;
	}
	else
	{
		NewNode->next = *PPhead;
		*PPhead = NewNode;
	}
}

void LLPopBack(LinkedList** PPhead)
{
	assert(PPhead);
	assert(*PPhead);
	if ((*PPhead)->next == NULL)
	{
		*PPhead = NULL;
	}
	else
	{
		LinkedList* tail = *PPhead;
		while (tail->next->next != NULL)
		{
			tail = tail->next;
		}
		free(tail->next);
		tail->next = NULL;
	}
}

void LLPopFront(LinkedList** PPhead)
{
	assert(PPhead);
	assert(*PPhead);
	LinkedList* temp = (*PPhead)->next;
	free(*PPhead);
	*PPhead = temp;
}

void printLL(LinkedList* Phead)
{
	while (Phead!=NULL)
	{
		printf("%d->", Phead->data);
		Phead = Phead->next;
	}
	printf("NULL");
}

LinkedList* LListFind(LinkedList* phead, LLDataType x)
{
	assert(phead);
	LinkedList *ret = phead;
	while (ret->data != x)
	{
		if (ret == NULL)
		{
			return NULL;
		}
		ret = ret->next;
	}
	return ret;
}

void SListInsertAfter(LinkedList* pos, LLDataType x)
{
	assert(pos);
	LinkedList *newnode = BuyNewNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

void SListInsertFront(LinkedList** PPhead, LinkedList* pos, LLDataType x)
{
	assert(pos);
	assert(PPhead);
	if (*PPhead == NULL||*PPhead==pos)
	{
		LLPushFront(PPhead, x);
	}
	else
	{
		LinkedList* cur = *PPhead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		LinkedList* newnode = BuyNewNode(x);
		newnode->next = cur->next;
		cur->next = newnode;
	}
}

void SListEraseAfter(LinkedList* pos)
{
	if (pos == NULL)
	{
		printf("没发现此数据");
		return;
	}
	if (pos->next == NULL)
	{
		printf("链表后面已经为空了\n");
		return;
	}
	LinkedList* temp = pos->next;
	pos->next = pos->next->next;
	free(temp);
}


void SListErase(LinkedList**PPhead,LinkedList* pos)
{
	assert(pos);
	assert(PPhead);
	assert(*PPhead);
	if (pos == *PPhead)
	{
		LLPopFront(PPhead);
		return;
	}
	LinkedList* cur = *PPhead;
	while (cur->next != pos)
	{
		cur = cur->next;
	}
	cur->next = cur->next->next;
	free(pos);
}
 
//void SListDestroy(LinkedList** Phead)
//{
//	assert(Phead);
//	if (*Phead == NULL)
//	{
//		printf("已经是空链表了");
//		return;
//	}
//	while ((*Phead) != NULL)
//	{
//		LinkedList* temp = *Phead;
//		*Phead = (*Phead)->next;
//		free(temp);
//	}
//}

void SListDestroy(LinkedList** PPhead)
{
	assert(*PPhead);
	if (*PPhead == NULL)
	{
		printf("已经是空链表了\n");
		return;
	}
	while ((*PPhead)->next != NULL)
	{
		LinkedList* tep = (*PPhead)->next;
		(*PPhead)->next = (*PPhead)->next->next;
		free(tep);
	}
	free(*PPhead);
	*PPhead = NULL;
}

LinkedList.h

将主程序所需要的函数全部在头文件中声明,增加代码阅读性⭐

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

typedef int LLDataType;

typedef struct LinkedList
{
	LLDataType data;
	struct LinkedList* next;
}LinkedList;

void printLL(LinkedList* Phead);
void LLPushBack(LinkedList** PPhead, LLDataType x);
void LLPushFront(LinkedList** PPhead, LLDataType x);
void LLPopBack(LinkedList** PPhead);
void LLPopFront(LinkedList** PPhead);
LinkedList* LListFind(LinkedList* phead, LLDataType x);
void SListInsertAfter(LinkedList* pos, LLDataType x);
void SListEraseAfter(LinkedList* pos);
void SListDestroy(LinkedList** Phead);
void SListInsertFront(LinkedList** Phead, LinkedList* pos, LLDataType x);
void SListErase(LinkedList** PPhead, LinkedList* pos);

撒花

这就是实现单链表的全部内容了,创作不易,还请各位小伙伴多多点赞👍关注收藏⭐,以后也会更新各种关于c语言,数据结构的博客,撒花!

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

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

相关文章

【STM32】基础知识 第十课 CubeMx

【STM32】基础知识 第十课 CubeMx STM32 CubeMX 简介安装 JAVACubeMX 安装新建 STM32 CubeMX 工程步骤新建工程时钟模块配置GPIO 配置生成源码 main.c STM32 CubeMX 简介 CubeMX (全称 STM32CubeMX) 是 ST 公司推出的一款用于 STM32 微控制器配置的图形化工具. 它能帮助开发者…

「Bug」解决办法:Could not switchto this profil,无法使用节点的解决方法,彻底解决

♥️作者&#xff1a;白日参商 &#x1f935;‍♂️个人主页&#xff1a;白日参商主页 ♥️坚持分析平时学习到的项目以及学习到的软件开发知识&#xff0c;和大家一起努力呀&#xff01;&#xff01;&#xff01; &#x1f388;&#x1f388;加油&#xff01; 加油&#xff01…

二十五、OSPF高级技术——开销值、虚链路、邻居建立、LSA、静默接口

文章目录 调试指令&#xff08;三张表&#xff09;1、邻居表&#xff1a;dis ospf peer brief2、拓扑表&#xff08;链路状态数据库&#xff09;&#xff1a;dis ospf lsdb3、路由表&#xff1a;dis ip routing-table 一、OSPF 开销值/度量值&#xff08;cost&#xff09;1、co…

Python基础合集 练习15(内置函数 匿名函数)

匿名函数 以lambda开头表示这是匿名函数&#xff0c;之后的x,y是函数参数 def sub(a,b): return a-b print(sub(10,3)) print(lambda x,y:x-y) sublambda x,y:x-y print(sub(8,4)) def game(math,chinese,english): “”" 功能&#xff1a;计算三科的成绩 math&#xf…

谈谈多线程的上线文切换

大家好&#xff0c;我是易安&#xff01; 我们知道&#xff0c;在并发程序中&#xff0c;并不是启动更多的线程就能让程序最大限度地并发执行。线程数量设置太小&#xff0c;会导致程序不能充分地利用系统资源&#xff1b;线程数量设置太大&#xff0c;又可能带来资源的过度竞争…

【C++】隐式转换与explicit关键字、运算符及其重载、this关键字

C隐式转换与explicit关键字 隐式构造函数 隐含的意思是不会明确告诉你要做什么 隐式转换 C允许编译器对代码执行一次隐式转换&#xff0c;而不需要使用casr强制转换 例1 #include <iostream> #include <string>class Entity { private:std::string m_Name;in…

13 SQL——数值函数

1 ceil() 数值向上取整&#xff08;前提是小数位不是0&#xff09; select ceil(1.2);2 floor() 数值向下取整&#xff08;前提是小数位不是0&#xff09;select floor(1.8);3 mod() 取&#xff08;x%y&#xff09;的模运算&#xff08;求余数运算&#xff09; select …

10. hr 综合面试题汇总

10. hr 综合面试题汇总 C++软件与嵌入式软件面经解析大全(蒋豆芽的秋招打怪之旅) 本章讲解知识点 1.1 HR心理复盘1.2 HR常问问题——学校的表现怎么样啊?1.3 HR常问问题——了解我们公司吗?1.4 HR常问问题——个人情况1.5 HR常问问题——业余生活1.6 HR常问问题——薪资待…

【源码角度】为什么AQS这样设计

AQS&#xff08;AbstractQueuedSynchronizer&#xff0c;抽象同步队列器&#xff09;是 一个基于 FIFO的双端队列。它分为独占模式和共享模式&#xff0c;本文主要围绕独占模式进行讲解&#xff0c;共享模式的原理和独占模式相似&#xff0c;最后会提一嘴。 场景代入 其实AQS模…

云计算基础(持续更新)

文章目录 云计算云计算的定义第1关&#xff1a;云计算定义第2关&#xff1a;云计算的基本原理 云计算出现的背景第1关&#xff1a;云计算出现的背景第2关&#xff1a;云计算的特征第3关&#xff1a;云计算的优势与劣势 虚拟化的类型第1关&#xff1a;虚拟化的定义第2关&#xf…

第六章结构型模式—代理模式

文章目录 代理模式解决的问题概念结构 静态代理动态代理织入的概念JDK 动态代理JDK 动态代理分析 CGLIB 动态代理 三种代理的对比JDK 和 CGLIB 的区别动态代理和静态代理的对比代理模式的优缺点使用场景 结构型模式描述如何将类或对象按某种布局组成更大的结构&#xff0c;有以…

浅谈springboot启动过程

1. 知识回顾 为了后文方便&#xff0c;我们先来回顾一下spring的一些核心概念。 spring最核心的功能无非是ioc容器&#xff0c;这个容器里管理着各种bean。ioc容器反映在java类上就是spring的核心类ApplicationContext。ApplicationContext有众多的子接口和子类&#xff0c;不…

SAP重复制造入门到放弃系列之基本配置

目录 前言 主要配置清单&#xff1a; REM参数文件&#xff1a; 计划订单计划参数文件 维护输入项参数 维护行选择 确认和物流信息系统全局设置 定义确认处理 操作方法表 其他 前言 重复制造中的配置步骤包括创建重复制造配置文件、为运行计划数量&#xff08;计划订单&a…

Excel技能之打印,19+技巧超省纸

颜色太多&#xff0c;重新打印。 没有边框&#xff0c;重新打印。 少了几列&#xff0c;重新打印。 整个工作表打印出来&#xff0c;拿剪刀把自己需要的数据剪下来&#xff0c;用胶水贴到另一张新的A4纸。 你上班打印资料&#xff0c;浪费了多少纸&#xff0c;认真算一下&…

PyCharm with Remote Interpreters

文章目录 一: 利用 Docker 镜像搭建1.编写 Dockerfile2.构建并配置 Remote Interpreters3.结论0.FAQ 二: 利用 SSH Docker 镜像搭建1.编写 Dockerfile2.构建并运行3.构建镜像4.运行容器5.验证并配置0.FAQ 起因是需要在 M2 芯片的 Mac 上调试基于 Python3.6.1 构建的程序. 而 M…

每天一道算法练习题--Day18 第一章 --算法专题 --- ----------前缀树

前缀树 字典树也叫前缀树、Trie。它本身就是一个树型结构&#xff0c;也就是一颗多叉树&#xff0c;学过树的朋友应该非常容易理解&#xff0c;它的核心操作是插入&#xff0c;查找。删除很少使用&#xff0c;因此这个讲义不包含删除操作。 截止目前&#xff08;2020-02-04&a…

bootloader编写——MCU固件升级系列2(STM32)

本系列将从升级流程、boot代码编写、APP代码编写以及固件打包来介绍&#xff0c;硬件选用STM32F407ZGT6&#xff08;手里只有&#xff09;&#xff0c;来完成这系列教程。 前言 开发STM32固件升级并编写Bootloader时&#xff0c;需要注意以下几个关键点&#xff1a; 熟悉硬件和…

数据结构篇四:栈

文章目录 前言1.栈1.1 栈的概念及结构1.2 栈的实现 2.栈功能的解析及实现2.1 栈的创建2.2 初始化2.3 入栈2.4 出栈2.5 检查栈是否为空2.6 获取栈顶元素2.7 栈中的有效元素个数2.8 销毁 3.代码实现3.1 Stack.h3.2 Stack.c3.3 test.c 4.总结 前言 前面学习的一些结构都比较普通&a…

13 | visual studio与Qt的结合

1 前提 Qt 5.15.2 visual studio 2019 vsaddin 2.8 2 具体操作 2.1 visual studio tool 2.1.1 下载 https://visualstudio.microsoft.com/zh-hans/downloads/2.1.2 安装 开发

推荐算法实战项目:WideDeep原理以及案例实战(附完整 Python 代码)

本文要介绍的是Google于2016年提出的Wide&Deep模型&#xff0c;此模型的提出对业界产生了非常大的影响&#xff0c;不仅其本身成功地应用在多家一线互联网公司&#xff0c;而且其后续的改进工作也一直延续至今。 Wide&Deep模型正如其名&#xff0c;分别包含了Wide部分…