数据结构与算法之《单链表》详解

news2025/1/13 7:37:52

标题:单链表的思路及代码实现

作者:@Ggggggtm

寄语:与其忙着诉苦,不如低头赶路,奋路前行,终将遇到一番好风景

文章目录:

引入

一、链表的概念及结构

1.1 链表的概念

1.2 链表的结构

二、链表的思路及代码实现详解

2.1 单链表的实现思路

2.2 单链表的思路及代码实现

2.2.1 定义结构体

2.2.2 打印链表数据 

2.2.3 开辟节点

2.2.4 头插 

2.2.5 尾插

2.2.6 头删

2.2.7 尾删

2.2.8 查找

2.2.9 任意位置插入

2.2.10 任意位置删除

2.2.11 销毁链表

三、单链表和顺序表的优缺点总结对比


 

引入

  上篇文章我们讲述了动态的顺序表 (顺序表文章详解)。
通过上篇文章我们知道,动态的顺序表当空间不足够时需要扩容,扩容是有消耗的,同时可能会造成空间浪费。因此,我们针对顺序表的缺点,我们这里引入链表,我们来看链表的思路及代码的实现。

一、链表的概念及结构

1.1 链表的概念

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

  通俗的解释一下链表。链表就是定义一个结构体,在链表中我们通常称结构体为节点,一个节点中包含一个要存储的数据,同时会有下个节点的地址。我们就是通过这个地址来访问下个节点。当然,最后一个节点存储的地址为空指针。但是它们在物理结构上,不是连续的。而逻辑结构上是连续的。 

1.2 链表的结构

  通过上面给出的链表的概念,我们这里给出链表的结构,我们可以结合着先简单理解一下,下面我们会给出各个模块的实现,进而有更深层次的理解。

  注意,上图的结构中实际上是没有箭头的。这里我们为了能够更加方便的理解,我们给出箭头。实际上,是通过地址访问到下个节点。 

二、链表的思路及代码实现详解

  链表有很多种类,例如有单向链表、双向链表、带有哨兵位的链表等等。我们这里先给大家详解一下链表入门的单向链表,也称为单链表。 

2.1 单链表的实现思路

  我们学习完顺序表后,我们会很容易学习链表。两者的实现思路大同小异。具体到每个模块细节的实现还是有所差别的。我们先来看单链表整体思路分为多种不同的模块有哪几个:

  1. 定义一个结构体,该结构体包含一个可以存放数据的变量和一个能够指向下一个节点的指针。
  2. 打印链表数据。
  3. 开辟节点
  4. 头插。
  5. 尾插。
  6. 头删。
  7. 尾删。
  8. 查找
  9. 任意位置插入。
  10. 任意位置删除。
  11. 销毁链表

   以上就是整个单链表的不同模块,那我们接下来看一下不同模块思路及代码实现的细节吧。

2.2 单链表的思路及代码实现

2.2.1 定义结构体

  上面我们提到,定义结构体时该结构体包含一个可以存放数据的变量和一个能够指向下一个节点的指针,那么代码的实现就很简单了。

typedef int SLTDataType;
struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
};
typedef struct SListNode SLTNode;

   通过上面我们看到:在定义结构体时,我们可以用typedef进行数据类型简化,同时方便我们后期更改存储类型的时候直接更改typedef处即可。同时我们也用typedef进行结构体类型简化,方便我们以后编辑代码。

2.2.2 打印链表数据 

  打印链表数据时,我们从第一个节点依次往后访问打印即可。直到当前节点存储的下一个节点的地址为空时结束。我们来看代码。  

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

2.2.3 开辟节点

   在我们插入数据之前,不管是头插还是尾插,还是任意插入,我们都需要新开辟一个节点。然后把要插入的数据存储到新节点当中,然后进行连接即可。所以这里我们把开辟节点单独分为一个模块来解释。我们来看一下代码的是实现。

SLTNode* BuySlistNode(SLTDataType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newnode == NULL)
	{
		printf("malloc failed\n");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

2.2.4 头插 

  头插的实现相对较为简单。开辟一个新的节点,然后新的节点中的next指向第一个节点即可,也就是存储了第一个节点的地址。当头插结束之后,要更新头节点所指向的位置。

void SListPushFront(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode = BuySlistNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

  那上面的代码当头插时是空链表可以吗?一样可以的。当是空链表时,*pphead也为空(NULL),分析后依然可以的。 

2.2.5 尾插

  尾插同样需要开辟节点。我们需要找到链表的最后一个节点,然后最后一个节点的next指向新节点即可。但是尾插时,当链表为空的时候需要另外考虑。我们结合着代码一起理解一下。

void SListPushBack(SLTNode** pphead, SLTDataType x)
{
	SLTNode* newnode=BuySlistNode(x);
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		SLTNode* tail = *pphead;
		while (tail->next != NULL)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

2.2.6 头删

  头删相对简单。我们只要保存了第二个节点,然后删除第一个节点,改变头节点的位置即可。但是我们删除节点时应该确保链表不为空。

void SListPopFront(SLTNode** pphead)
{
	assert(*pphead != NULL);
	SLTNode* cur = (*pphead)->next;
	free(*pphead);
	*pphead = cur;
}

2.2.7 尾删

  尾删的思路相对较为麻烦,因为我们不仅要找到尾节点,同时还需要找到尾节点的前一个节点,因为我们需要把尾节点的前一个节点的next指向空(NULL)。同样因此尾删有一种特殊的情况。当链表只有一个节点时,此节点就是尾节点,这种情况不需要找前一个结点,直接删除此节点即可。我们结合代码一起理解,我这里提供了两种思路。 

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

2.2.8 查找

  当我们任意插入或者任意删除时,当我们不知道某个元素的位置的时候我们可以先通过查找再进行任意插入或任意删除的操作。这也是我们把查找单独列为一个板块的原因。 当我们找到该节点时,我们就返回该节点的地址,找不到返回空。

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

2.2.9 任意位置插入

  我们这里的任意位置插入是:在某个节点前面插入一个节点。我们先查找一个位置,再进行插入。当然插入需要开辟新节点。我们插入时,我们需要找到pos位置的前一个节点进行连接插入即可。当然,当链表只有一个节点或者没有节点时,我们直接头插就行。我们看代码。

SLTNode* pos=SListFind(phead,x);
if (pos)
{
	SListInsert(&plist, pos, 0);
}
void SListInsert(SLTNode** pphead, SLTNode* pos, SLTDataType x)
{
	SLTNode* newnode = BuySlistNode(x);
	if (*pphead == pos)
	{
		newnode->next = *pphead;
		*pphead = newnode;
	}
	else
	{
		SLTNode* PosPrev = *pphead;
		while (PosPrev->next != pos)
		{
			PosPrev = PosPrev->next;
		}
		PosPrev->next = newnode;
		newnode->next = pos;
	}
}

2.2.10 任意位置删除

   删除时,我们首先要判断的时链表是否为空。任意删除我们需要找到pos位置的前一个节点,把pos的前一个结点连接到pos的后一个节点 ,删除pos位置的节点即可。当链表只有一个节点时,我们直接头删就行。

SLTNode* pos=SListFind(phead,x);
if (pos)
{
	SListInsert(&plist, pos);
}
void SListErase(SLTNode** pphead, SLTNode* pos)
{
	assert(*pphead != NULL);
	if (*pphead==pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* prev = *pphead;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		prev->next = pos->next;
		free(pos);
		pos = NULL;
	}
}

2.2.11 销毁链表

  因为链表是我们动态开辟出来的,所以我们要释放掉,避免空间泄露。销毁链表时,我们不只是释放头节点的空间,每个节点的空间都要释放。 

void SListDestory(SLTNode** pphead)
{
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
}

  以上解释我们的整个单链表的不同模块细节实现,那么单链表和动态顺序表有什么区别呢?各自的优缺点是什么呢?接下来我给大家再总结对比一下。 

三、单链表和顺序表的优缺点总结对比

单链表的优缺点对比
单链表优点单链表缺点

按需申请空间,不需要了释放空间,更加合理的利用空间。

每插一个数据,都需要连接后面的一个节点。
头部或者中间插入数据时不需要挪动数据,插入时效率较高。不支持随机访问。
不存在空间浪费。

顺序表表的优缺点对比
顺序表优点顺序表缺点

支持随机访问。在有些算法中需应用随机访问。

空间不够时需要扩容,扩容是有消耗的。
头部或者中间插入或者删除时需要挪动数据,挪动数据也是有消耗的。
一次扩容2倍,可能存在空间浪费。

  通过对比发现,单链表和顺序表都有各自的优缺点,都不可被替代的。当然,单链表和顺序表都是我们需要重点掌握的。

  单链表的学习就到这里,希望以上内容对你有所帮助,感谢阅读ovo! 

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

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

相关文章

单文件组件:dom高亮插件、在父组件中引入子组件、App.vue代码代码写法

输入<template>等dom为什么会有高亮显示&#xff1f; 下载Vetur插件&#xff1a; 模板会自动帮你导出&#xff0c;但是js文件不会&#xff0c;需要你手动导出&#xff0c;启动的服务器&#xff0c;只会热更新&#xff0c;如果想要刷新整个页面就自己手动刷新&#xff1b…

2022.11.29总结

今天写了条件查询 虽然思路上还说是比较顺&#xff0c;但是还是写了一晚上&#xff0c;因为老是在细节上出现bug&#xff0c;改了好久&#xff0c;踩了好几个坑。 首先大概是因为组件不是确定的&#xff0c;我把ref属性绑定在router-view上&#xff0c;导致我获取不到条件选择…

[附源码]Python计算机毕业设计SSM基于Java的校园二手平台交易系统(程序+LW)

环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 Maven管理等…

SpringBoot+html+vue模板开发备忘录

除了对某个表基本的增删改查以外&#xff0c;可能需要额外的增加操作&#xff0c;这里是通过按钮来实现的 1、新增一个测试按钮 <el-button type"primary" class"butT" click"test()">测试</el-button> 2、这个按钮绑定一个方法t…

CFDP:聚类算法

Clustering by Fast Search and Find of Density Peaks(CFDP) - 发表于2014 science期刊 聚类算法&#xff0c;作为机器学习里常用的一种无监督方法&#xff0c;一直以来都受到很大的关注。聚类算法&#xff0c;是希望把同一类的样本或者样本聚到一起&#xff0c;比如说常见的…

BIRT 横向分栏

【问题】 I have a table which will displays User ids in one column. I want to display that column as two columns Ex. In First Row, First user id in Column 1 and Second user id in Column2, In Second Row, second user id in Column 1 and third user id in Co…

教育机构客户管理系统功能方案详解!

前言&#xff1a; 教育机构客户管理系统怎么做&#xff1f;哪个好&#xff1f;还在为客户流失而烦恼的你&#xff0c;一定很困恼&#xff0c;别人是怎么留住客户的呢&#xff1f;今天就来告诉你。 随着我国产业结构升级&#xff0c;在政策顶层推动和经济主体需求转变的共同影…

nlp工具库spacy

文章目录spacy能做什么如何安装案例 分词功能spacy是一个辅助自然语言处理的工具库。 spacy能做什么 它集成了各种实用的句子分析功能&#xff0c;包括分词、词性分析、词性还原等等&#xff0c;所有功能特性可参考官网 spacy-101的features一章&#xff0c;有Tokenization、…

ROS MarkerArray的几种常见用法

ros使用过程中&#xff0c;经常会用到rviz可视化各种数据。而在rviz中&#xff0c;marker与markerarray是常用的一种可视化工具&#xff0c;最近也用到过几次了&#xff0c;这里随手记录一下。 1、makerarray画线 在marker中常见的就是表示两点之间的连线&#xff0c;而marke…

【LeetCode】1752. 检查数组是否经排序和轮转得到

题目描述 给你一个数组 nums 。nums 的源数组中&#xff0c;所有元素与 nums 相同&#xff0c;但按非递减顺序排列。 如果 nums 能够由源数组轮转若干位置&#xff08;包括 0 个位置&#xff09;得到&#xff0c;则返回 true &#xff1b;否则&#xff0c;返回 false 。 源数组…

怎么提高外贸开发信的回复率?

写客户开发信是每个外贸人都必备的技能。对于成本预算小的企业来讲&#xff0c;开发信是性价比非常高的一种方式。但是&#xff0c;很多人在写完开发信之后并没有收获到比较好的回复效果&#xff0c;可能是因为他们没有把握写开发信的技巧。怎么提高外贸开发信的回复率&#xf…

挖掘数据价值,华为云大数据BI解决方案有绝招

在没有接触电子商务时&#xff0c;就一直很好奇&#xff0c;他们是怎么知道我需要什么并及时推送给我的&#xff0c;直到后来自己也开始做电商的时候&#xff0c;才知道原来电子商务必不可少的是大数据分析方案。在这里我强烈给同业者们安利一下华为云的大数据BI解决方案&#…

大厂都在用MyBatis,跳槽的时候MyBatis更是面试必问的内容,那你对于MyBatis又掌握了多少呢?这份MyBatis源码解析值得拥有!

MyBatis作为一个流行的半自动ORM框架&#xff0c;里面融合了许多优秀的设计理念&#xff0c;分析其源码骨架能够帮你建立良好的项目设计经验。由于其比较复杂&#xff0c;我会分成几篇来讲&#xff0c;一起踏上征服的旅程吧&#xff01; 首先把MyBatis源码包导入到idea&#x…

PC_非连续内存分配方式@分页存储管理@地址变换机构@快表@有效访存时间

文章目录非连续内存分配方式&#x1f388;分页存储管理基本分页存储管理页面和页面大小分块和碎片逻辑地址结构页表页表项结构页表项和地址比较&#x1f388;页表项地址地址变换机构基本地址变换机构结构图映射过程Note:页表长度页表项长度页表大小例小结ref具有快表的地址变换…

xxl-job安装部署

一、简介 XXL-JOB是一个分布式任务调度平台&#xff0c;其核心设计目标是开发迅速、学习简单、轻量级、易扩展。现已开放源代码并接入多家公司线上产品线&#xff0c;开箱即用。 中文文档English Documentation 二、安装 xxl-job需要的提前安装好以下环境&#xff1a;jdk、m…

A-Level物理例题解析及练习Projectile Motion

今日知识点&#xff1a;Magnetic Force例题 Q: A proton p and an electron e, with the same velocity, enter a magnetic field which is perpendicular to the direction of their motion. The field acts into the page. Which of the following diagrams best represents …

Spire.XLS for Java 12.11.8 Excel to PDF bug Fix

谷歌找破解版Spire.XLS for Java is a professional Java Excel API that enables developers to create, manage, manipulate, convert and print Excel worksheets without using Microsoft Office or Microsoft Excel. Spire.XLS for Java supports both for the old Excel …

Spark系列之Spark的Shuffle详解及相关参数调优

title: Spark系列 第七章 Spark的Shuffle详解及相关参数调优 ​ 大多数Spark作业的性能主要就是消耗在了shuffle环节&#xff0c;因为该环节包含了大量的磁盘IO、序列化、网 络数据传输等操作。因此&#xff0c;如果要让作业的性能更上一层楼&#xff0c;就有必要对shuffle过程…

数图互通高校房产管理——公房管理功能详解

数图互通房产管理系统在这方面做得比较全面&#xff1b; 公用房管理 1 房屋档案 可维护校区信息、公房的楼栋信息、公房的房间信息&#xff0c;记录校区的名称、建成年份、占地面积、建筑面积等基本信息。记录楼栋的编号、名称、建筑面积、使用面积等基本信息&#xff0c;同…

[附源码]计算机毕业设计springboot超市商品管理

项目运行 环境配置&#xff1a; Jdk1.8 Tomcat7.0 Mysql HBuilderX&#xff08;Webstorm也行&#xff09; Eclispe&#xff08;IntelliJ IDEA,Eclispe,MyEclispe,Sts都支持&#xff09;。 项目技术&#xff1a; SSM mybatis Maven Vue 等等组成&#xff0c;B/S模式 M…