[初阶数据结构】单链表

前言 

📚作者简介:爱编程的小马,正在学习C/C++,Linux及MySQL。

📚本文收录于初阶数据结构系列,本专栏主要是针对时间、空间复杂度,顺序表和链表、栈和队列、二叉树以及各类排序算法,持续更新!

📚相关专栏C++及Linux正在发展,敬请期待!

目录

前言 

1. 链表

1.1 链表的定义 

1.2 链表与顺序表相比的好处

1.3 链表的结构表示

1.3.1 链表的结构形式

1.3.2 链表的结构性质

1.4 单链表的实现

1.4.1 单链表的创建

 1.4.2 单链表的打印

1.4.3 单链表的动态内存申请

1.4.4 单链表的头插

1.4.5 单链表的尾插

1.4.6 单链表的头删

 1.4.7 单链表的尾删

1.4.8 单链表的查找

1.4.9 单链表的任意位置插入的前插

1.4.10 单链表任意位置的删除

2.单链表的完整代码

2.1 test.c测试函数代码

2.2 SList.h函数声明代码

2.3 SList.c函数实现代码

总结


1. 链表

1.1 链表的定义 

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

本文只介绍最简单的链表结构:单链表

1.2 链表与顺序表相比的好处

1、顺序表从中间插入/头部插入,时间复杂度是O(N),因为要一次往后挪动,但是单链表是O(1),大大节省了程序运行时间。

2、 顺序表每次需要增容,到后期增容很大的时候,需要拷贝数据、开辟新空间、释放旧空间。会有不小的损耗。链表直接开辟一个结构体大小的空间即可。

3、增容一般是两倍,但是我就想多插入几个仅此而已,会造成空间的大规模浪费。链表同样更加简单且占用空间小。

1.3 链表的结构表示

首先要给大家介绍一下,就是链表中的结点是一个结构体,结点中一个变量是存储数据的,另一个变量是存储结构体地址的,上一个结点存下一个结点的地址,从而链接起来。

1.3.1 链表的结构形式

1.3.2 链表的结构性质

1、从上图可以看出,链表在逻辑上是连续的,在物理地址上是不连续的

2、现实的结点是动态内存在堆区申请出来的

3、堆上申请的空间,是按照一定的规律来的,有些可能相同,有些可能不同。

1.4 单链表的实现

1.4.1 单链表的创建

上文我们提到了,结点是一个结构体,第一个结构体变量是存储数据的,第二个是存储下一个链表的地址的

typedef int SListDataType;

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

 1.4.2 单链表的打印

void PrintSList(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

为什么是这样子打印的?给大家说一下思想:

1、单链表定义了最后一个链表的地址为空指针,所以我们就定义了一个cur来遍历整个链表

2、每找到一个数据我们就打印,然后遍历链表指针cur就往后走一步, 怎么走?是不是next中存放了下一个结点的地址,那么把cur管理的结构体中next的地址赋值给cur是不是相当于向后走了一步。

1.4.3 单链表的动态内存申请

SLTNode* BuySLTNode(SListDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

 比方说,我想申请一块动态内存空间,里面存储x的值,那么这个时候,就通过malloc在堆上申请一块空间,交给newnode管理,这时候把newnode中data的值赋值为x,newnode中next的值赋值为NULL后返回这块空间的地址。这是不是就很好的开辟了一个结点。如果开辟失败了就返回空指针。

1.4.4 单链表的头插

void SListPushFront(SLTNode** pphead, SListDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	assert(newnode);
	newnode->next = *pphead;
	*pphead = newnode;
}	

这里我们用newnode来管理开辟的新结点,如果开辟了失败了就不用往下了,开辟成功了往下走,我画个图来帮助大家理解上面代码的意思

1.4.5 单链表的尾插

我先给大家介绍一下,尾插有两种情况,第一种空链表,第二种,非空链表

先分析第一种情况,如果是空链表,是不是直接把newnode的地址给pphead是不是就可以了,

第二种情况,我给大家画个图一起来分析

void SListPushBack(SLTNode** pphead, SListDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	//空链表
	
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	//非空链表
	SLTNode* tail = *pphead;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}
	tail->next = newnode;
}

第二种情况,首先我们要尾插,是不是要先找尾部在哪里?尾部在哪里?是不是说tail->next是空指针,这个就是链表中最后一个元素了,找到了之后,就把tail->next存newnode的地址就可以。

1.4.6 单链表的头删

给大家说一下啊,如何删除单链表中的数?是不是只需要让上个结点存下下个结点的地址就好了。

那么头删就是让*pphead指向下下个结点,然后释放第一个结点就好了。 

那么,应该这么做,看代码

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead);
	
	//链表只有一个值
	SLTNode* cur = *pphead;
	*pphead = cur->next;
	free(cur);
	cur = NULL;
}

画个图给大家理解一下:

 1.4.7 单链表的尾删

尾删就更简单了,还是第一步,找尾,第二部,释放空间即可。

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next->next != NULL)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}

1.4.8 单链表的查找

在单链表中查找一个数,找到了就返回这个结点的地址,没找到就返回空指针。

SLTNode* SListFind(SLTNode* pphead, SListDataType x)
{
	assert(pphead);
	SLTNode* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
			return cur;
		else
			cur = cur->next;
	}
	return NULL;
}

1.4.9 单链表的任意位置插入的前插

void SListInsertbefore(SLTNode** pphead, SLTNode* pos, SListDataType x)
{
	if (*pphead == NULL)
	{
		SListPushFront(pphead, x);
	}
	//在pos前插入
	else
	{
		SLTNode* newnode = BuySLTNode(x);
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;
	}
}

首先啊,如果pphead没有值,那么就相当于头插,如果链表中有值,画个图给大家理解

1.4.10 单链表任意位置的删除

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

2.单链表的完整代码

2.1 test.c测试函数代码

#include "SList.h"
SLTNode* Phead = NULL;
void Test1()
{
	//该函数测试头插和尾插
	SListPushFront(&Phead, 1);
	SListPushFront(&Phead, 2);
	SListPushFront(&Phead, 3);
	SListPushFront(&Phead, 4);
	SListPushFront(&Phead, 5);
	SListPushBack(&Phead, 2);
	SListPushBack(&Phead, 3);
	SListPushBack(&Phead, 4);
	PrintSList(Phead);
}

void Test2()
{
	//该函数测试头删和尾删
	SListPushFront(&Phead, 1);
	SListPopBack(&Phead);
	PrintSList(Phead);
}
void Test3()
{
	//查找
	SListPushFront(&Phead, 1);
	SListPushFront(&Phead, 2);
	SListPushFront(&Phead, 3);
	SListPushFront(&Phead, 4);
	SLTNode* find = SListFind(Phead, 3);
	if (find)
		find->data = 30;
	PrintSList(Phead);
}
void Test4()
{
	//任意位置插入(前插)
	SListPushFront(&Phead, 1);
	SListPushFront(&Phead, 2);
	SListPushFront(&Phead, 3);
	SListPushFront(&Phead, 4);
	SLTNode* find1 = SListFind(Phead, 3);
	SLTNode* find2 = SListFind(Phead, 4);
	if (find1)
	{
		SListInsertbefore(&Phead, find1, 40);
		SListEraseafter(find2);
		SListEraseafter(find1);
	}
	PrintSList(Phead);
}
int main()
{
	Test1();
	//Test2();
	//Test3();
	//Test4();
	return 0;
}

2.2 SList.h函数声明代码

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

typedef int SListDataType;

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




void PrintSList(SLTNode* phead);
//头插
void SListPushFront(SLTNode** pphead, SListDataType x);
//尾插
void SListPushBack(SLTNode** pphead, SListDataType x);
//头删
void SListPopFront(SLTNode** pphead);
//尾删
void SListPopBack(SLTNode** pphead);
//查找
SLTNode* SListFind(SLTNode* pphead, SListDataType x);
//在pos之后插入x
void SListInsertbefore(SLTNode** pphead,SLTNode* pos, SListDataType x);
//在pos之后插入x
void SListInsertafter(SLTNode* pos, SListDataType x);
//删除pos位置上的值
void SListErase(SLTNode** pphead, SLTNode* pos);
//删除pos之后的位置
void SListEraseafter(SLTNode* pos);

2.3 SList.c函数实现代码



#include "SList.h"
void PrintSList(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur != NULL)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL");
}

SLTNode* BuySLTNode(SListDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		perror("malloc fail");
		return NULL;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void SListPushFront(SLTNode** pphead, SListDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	assert(newnode);
	newnode->next = *pphead;
	*pphead = newnode;
}	


void SListPushBack(SLTNode** pphead, SListDataType x)
{
	SLTNode* newnode = BuySLTNode(x);
	//空链表
	
	if (*pphead == NULL)
	{
		*pphead = newnode;
		return;
	}
	//非空链表
	SLTNode* tail = *pphead;
	while (tail->next != NULL)
	{
		tail = tail->next;
	}
	tail->next = newnode;
}

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead);
	
	//链表只有一个值
	SLTNode* cur = *pphead;
	*pphead = cur->next;
	free(cur);
	cur = NULL;
}

void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next->next != NULL)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
}

SLTNode* SListFind(SLTNode* pphead, SListDataType x)
{
	assert(pphead);
	SLTNode* cur = pphead;
	while (cur)
	{
		if (cur->data == x)
			return cur;
		else
			cur = cur->next;
	}
	return NULL;
}
void SListInsertbefore(SLTNode** pphead, SLTNode* pos, SListDataType x)
{
	if (*pphead == NULL)
	{
		SListPushFront(pphead, x);
	}
	//在pos前插入
	else
	{
		SLTNode* newnode = BuySLTNode(x);
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = newnode;
		newnode->next = pos;
	}
}

void SListInsertafter(SLTNode* pos, SListDataType x)
{
	assert(pos);
	//只有一个
	SLTNode* newnode = BuySLTNode(x);
	newnode->next = pos->next;
	pos->next = newnode;
}

void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead);
	if ((*pphead)->next == NULL)
	{
		free(*pphead);
		*pphead = NULL;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

void SListEraseafter(SLTNode* pos)
{
	assert(pos);
	assert(pos->next);
	SLTNode* cur = pos->next;
	pos->next = cur->next;
	free(cur);
	cur = NULL;
}

总结

1、单链表其实不难,大家一定要搞清楚指针和结构体

2、一定要动手实践一下

3、其实数据结构就是围绕着数据的增删查改显示这几个点,所以一定要搞清楚每一个代码实现的逻辑。

如果这份博客对大家有帮助,希望各位给小马一个大大的点赞鼓励一下,如果喜欢,请收藏一下,谢谢大家!!!
制作不易,如果大家有什么疑问或给小马的意见,欢迎评论区留言。

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

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

相关文章

添砖Java之路其三——自增自减运算符,数据转换与原码反码补码。

目录 运算符&#xff1a; 转换&#xff1a; 隐式转换&#xff1a; 小范围数据可以直接可以给大范围数据&#xff1a; 这里做了一张图范围向下兼容表​编辑 运算时&#xff0c;数据范围小的和数据范围大的&#xff0c;需要讲运算范围小的提升为运算范围大的同类&#xff0c…

软考系列必过资料分享-系统架构师-系统分析师-信息系统项目管理师

建议,写在前面 知识点是公用的,原则上不分新旧。每年会有少部分的题目切合当前时间段&#xff08;也是通过旧的知识演变的&#xff09; 信息系统项目管理师证书 系统架构师证书 系统分析师证书 资料分享 关注公众号 回复 信息系统项目管理师资料 即可获取信息系统项目管理师资…

如何使用香草看涨期权进行投机?

如何使用香草看涨期权进行投机&#xff1f; 香草看涨期权&#xff0c;通常也称为香草期权&#xff0c;是金融市场上的一种金融衍生品&#xff0c;由券商或金融机构推出。使用香草看涨期权进行投机&#xff0c;主要依赖于对市场走势的预测和对杠杆效应的运用。以下是一些关键步…

【前端】-【前端文件操作与文件上传】-【前端接受后端传输文件指南】

目录 前端文件操作与文件上传前端接受后端传输文件指南 前端文件操作与文件上传 一、前端文件上传有两种思路&#xff1a; 二进制blob传输&#xff1a;典型案例是formData传输&#xff0c;相当于用formData搭载二进制的blob传给后端base64传输&#xff1a;转为base64传输&…

力扣HOT100 - 35. 搜索插入位置

解题思路&#xff1a; 二分法模板 class Solution {public int searchInsert(int[] nums, int target) {int left 0;int right nums.length - 1;while (left < right) {int mid left ((right - left) >> 1);if (nums[mid] target)return mid;else if (nums[mid…

spring模块(六)spring监听器(1)ApplicationListener

一、介绍 1、简介 当某个事件触发的时候&#xff0c;就会执行的方法块。 当然&#xff0c;springboot很贴心地提供了一个 EventListener 注解来实现监听。 2、源码&#xff1a; package org.springframework.context;import java.util.EventListener; import java.util.fu…

深入解析智能指针:从实践到原理

&#x1f466;个人主页&#xff1a;晚风相伴 &#x1f440;如果觉得内容对你有所帮助的话&#xff0c;还请一键三连&#xff08;点赞、关注、收藏&#xff09;哦 如果内容有错或者不足的话&#xff0c;还望你能指出。 目录 智能指针的引入 内存泄漏 RAII 智能指针的使用及原…

安卓LayoutParams浅析

目录 前言一、使用 LayoutParams 设置宽高二、不设置 LayoutParams2.1 TextView 的 LayoutParams2.2 LinearLayout 的 LayoutParams 三、getLayoutParams 的使用四、setLayoutParams 的作用五、使用 setWidth/setHeight 设置宽高 前言 先来看一个简单的布局&#xff0c;先用 x…

百元挂耳式耳机哪款好?五款高品质一流机型不容错过

开放式耳机以其独特的不入耳设计&#xff0c;大大提升了佩戴的舒适度。相较于传统的入耳式耳机&#xff0c;它巧妙地避免了对耳朵的压迫&#xff0c;降低了中耳炎等潜在风险。不仅如此&#xff0c;开放式耳机还能让你保持对周边声音的灵敏度&#xff0c;无论是户外跑步还是骑行…

Day08-JavaWeb开发-MySQL(多表查询内外连接子查询事务索引)Mybatis入门

1. MySQL多表查询 1.1 概述 1.2 内连接 -- 内连接 -- A. 查询员工的姓名, 及所属的部门名称(隐式内连接实现) select tb_emp.name, tb_dept.name from tb_emp,tb_dept where tb_emp.dept_id tb_dept.id;-- 起别名 select * from tb_emp e, tb_dept d where e.dept_id d.id…

信息化飞速发展下,源代码防泄密方案该如何选择?常见四种有效方案分享

信息化时代发展迅速&#xff0c;数据防泄露一词也频繁的出现在我们身边。无论企业或政府单位&#xff0c;无纸化办公场景越来越多&#xff0c;数据泄露的时间也层出不穷。例如&#xff1a;世界最大职业中介网站Monster遭到黑客大规模攻击&#xff0c;黑客窃取在网站注册的数百万…

The Sandbox 案例|Web3 项目引领娱乐业的发展

Web3 如何通过 RZR 系列等项目开创娱乐新纪元。 我们已经看到技术和 Web3 如何颠覆金融和银行等行业&#xff0c;然而娱乐业在不断变化的环境中似乎发展滞后。传统的制片厂生态系统、高成本制作以及历史悠久的运作模式一直占据主导地位&#xff0c;而 Web3 项目的出现为创作者提…

CondaHTTPError: HTTP 404 NOT FOUND for url xxx

今天在创建新环境的时候给我报这个错 根据报错内容大概猜测&#xff0c;连接不到清华源&#xff1f; 然后我去清华源那边重新复制了一下配置 点这里跳转清华源 复制这一块东西 然后打开C盘->用户->找到.condarc文件打开 复制粘贴把里面的东西覆盖了就行 然后保存退出…

【excel】统计单元格内数字/字符串的数量

文章目录 一、问题二、步骤&#xff08;一&#xff09;将A1中的数字分解出来&#xff0c;在不同的单元格中显示&#xff08;二&#xff09;统计每个数字出现的个数&#xff08;三&#xff09;去重 三、尾巴 一、问题 单元格中有如下数值&#xff1a;12345234534545&#xff0c…

给excel中某列数值前后分别添加单引号

将这一列的数值&#xff0c;复制到 Sublime Text中&#xff0c;并CtrlA全选内容后&#xff0c;按CtrlH 选中工具栏左上角的 如果要给前面添加符号&#xff08;如单引号&#xff09;&#xff0c;则在Fina查找内容中&#xff0c;填入 ^ 如果要给后面添加符号&#xff08;如单引…

【操作系统】内存管理——地址空间连续内存分配与非连续内存分配

内存管理——地址空间&连续内存分配与非连续内存分配 一、地址空间1.1 计算机存储层次1.2 地址和地址空间1.3 虚拟存储的作用 二、内存分配2.1 静态内存分配2.2 动态内存分配 三、连续内存分配3.1 动态分区分配3.2 伙伴系统&#xff08;Buddy System&#xff09; 四、非连续…

5.1 Java全栈开发前端+后端(全栈工程师进阶之路)-服务端框架-MyBatis框架-相信我看这一篇足够

0.软件框架技术简介 软件框架&#xff08;software framework&#xff09;&#xff0c;通常指的是为了实现某个业界标准或完成特定基本任务的软件组件规范&#xff0c;也 指为了实现某个软件组件规范时&#xff0c;提供规范所要求之基础功能的软件产品。 框架的功能类似于基础设…

cufftPlanMany参数说明

背景 最近在看cufft这个库&#xff0c;传统的cufftPlan3d()这种plan接口逐渐被nvidia舍弃了&#xff0c;说是要用最新的cufftPlanMany&#xff0c;这个函数呢又依赖一个什么Advanced Data Layout(地址)&#xff0c;最终把这个api搞得乌烟瘴气很难理解&#xff0c;为了理解自己…

瓷器三维虚拟展示编辑平台为您量身定制高效实惠的展示方案

在竞争激烈的机械产品行业中&#xff0c;如何脱颖而出、展现产品魅力与企业实力?深圳vr公司华锐视点以其独特的三维动画设计制作服务&#xff0c;为您量身定制全方位的展示方案&#xff0c;让您的机械产品在市场中熠熠生辉。 全方位展示&#xff0c;细节尽收眼底 我们的三维展…

医学论文摘要翻译 中译英哪里比较专业

论文摘要是对论文内容不加注释和评论的简短陈述&#xff0c;需要扼要说明论文的目的、研究方法和最终结论。在发表学术论文时&#xff0c;很多重要刊物会要求作者将文章的摘要翻译成英文。那么&#xff0c;针对医学论文摘要翻译&#xff0c;中译英哪里比较专业&#xff1f; 专…
最新文章