探索数据结构:单链表的实战指南

这篇具有很好参考价值的文章主要介绍了探索数据结构:单链表的实战指南。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

探索数据结构:单链表的实战指南
✨✨ 欢迎大家来到贝蒂大讲堂✨✨

🎈🎈养成好习惯,先赞后看哦~🎈🎈

所属专栏:数据结构与算法
贝蒂的主页:Betty‘s blog

前言

在上一章节中我们讲解了数据结构中的顺序表,知道了顺序表的空间是连续存储的,这与数组非常类似,为我们随机访问数据提供了便利的条件。但是同时当插入数据时可能存在移动数据与扩容的情况,这大大增加我们的时间与空间成本。为了解决这个问题,就要学习我们今天要讲解的链表。

1. 什么是链表

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

2. 链表的分类

链表的种类多种多样,其中最常见的有八种,它们大致可以分为三类:

2.1 单向或者双向

探索数据结构:单链表的实战指南

2.2 带不带头节点

探索数据结构:单链表的实战指南

2.3 循环不循环

探索数据结构:单链表的实战指南

本专栏将选择其中两种常见链表进行讲解,那就是无头单向非循环链表与与带头双向循环链表,这两种优点主要有:

  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多。
  • 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了。

3. 单链表的功能

单链表的主要功能有以下几个:

  1. 打印单链表中的数据。
  2. 对单链表进行头插(开头插入数据)。
  3. 对单链表进行头删(开头删除数据)。
  4. 对单链表进行尾插(末尾插入数据)。
  5. 对单链表进行尾删(末尾删除数据)。
  6. 对单链表进行查找数据。
  7. 对单链表数据进行修改。
  8. 任意位置的删除和插入数据。
  9. 销毁单链表。

4. 单链表的定义

单链表的结点定义方式与我们以往定义的方式都不同,它是一个结构体中包含两个成员。一个是存储数值,一个存放下一个结点的地址。

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

实际内存存储结构:

探索数据结构:单链表的实战指南

5. 单链表功能实现

5.1 打印单链表

打印链表需要对函数传参指向链表的指针,首先为了保证链表不为空(NULL),需要对其进行断言。

(1) 代码实现

void PrintSList(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}

(2) 复杂度分析

  • 时间复杂度:因为需要遍历整个链表,显然时间复杂度为O(N)。
  • 空间复杂度:打印链表不需要格外的空间,所以空间复杂度为O(1).

5.2 单链表头插

(1) 创建结点

单链表每次插入都需要插入一个节点,所以我们最好写一个创建节点的函数方便后续调用。

SListNode* SListCreatNode(SLTDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc fail:");
        	return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

(2) 头插代码实现

单链表的头插与顺序表头插最大的不同就是单链表传参需要二级指针。因为头插数据需要改变一级指针plist的指向,而形参的改变无法影响实参,所以需要二级指针才能改变一级指针plist的指向。

void SListPushFront(SListNode** plist, SLTDataType x)
{
	assert(plist);
	SListNode* newnode = SListCreatNode(x);
	newnode->next = *plist;
	*plist = newnode;
}

(3) 复杂度分析

  • 时间复杂度:不需要额外时间的消耗,时间复杂度为O(1)。
  • 空间复杂度:固定创造一个节点,空间复杂度为O(1)。

5.3 单链表头删

头删与头插类似,都可能需要改变plist的指向,所以也需要二级指针。并且也需要防止链表为空的情况发生。

(1) 代码实现

void SListPopFront(SListNode** plist)
{
	assert(plist);
	assert(*plist);
	SListNode* cur = (*plist)->next;
	free(*plist);
	*plist = cur;
}

(2) 复杂度分析

  • 时间复杂度:不需要额外时间的消耗,时间复杂度为O(1)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

5.4 单链表尾插

当链表为空时,单链表尾插就会变成单链表头插,也需要改变plist的指向。其余情况即可通过循环寻找尾节点。

(1) 代码实现

void SListPushBack(SListNode** plist, SLTDataType x )
{
	assert(plist);
	if (*plist== NULL)
	{
		*plist = SListCreatNode(x);
	}
	else
	{
		SListNode* ptail = *plist;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = SListCreatNode(x);
	}
}

(2) 复杂度分析

  • 时间复杂度:需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:固定创造一个节点,空间复杂度为O(1)。

5.5 单链表尾删

与单链表尾插类似,当链表只有一个头结点时需要将plist置为空,所以也需要二级指针。并且也需要防止链表为空的情况发生。

(1) 代码实现

void SListPopBack(SListNode** plist)
{
	assert(plist);
	assert(*plist);//防止链表为空
	if ((*plist)->next == NULL)
	{
		free(*plist);
		*plist = NULL;
	}
	else
	{
		SListNode* ptail = *plist;
		while (ptail->next->next)
		{
			ptail = ptail->next;
		}
		free(ptail->next);
		ptail->next = NULL;
	}	
}

(2) 复杂度分析

  • 时间复杂度:需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

5.6 单链表的查找

如果找到就返回这个节点的地址,否则就返回空指针NULL

(1) 代码实现

SListNode* SListSearch(SListNode* plist, SLTDataType x)
{
	assert(plist);
	SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}

(2) 时间复杂度

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

5.6 单链表的修改

单链表的修改可以与查找配套使用。

(1) 代码实现

void SListModify(SListNode*plist, SListNode* pos, SLTDataType x)
{
	assert(plist);
	assert(pos);
	SListNode* cur = plist;
	while (cur)
	{
		if (cur == pos)
		{
			cur->data = x;
			break;
		}
		cur = cur->next;
	}
}

(2) 复杂度分析

  • 时间复杂度:可能需要遍历整个链表,时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

5.7 单链表任意位置的插入

(1) 代码实现

某个位置之前插入,当链表为空或者在头节点插入时使用头插。

void SListInsertF(SListNode** plist, SListNode* pos, SLTDataType x)
{
	assert(plist);
	assert(pos);
	if (*plist == pos || *plist == NULL)
	{
		SListPushFront(plist, x);//头插
	}
	else
	{

		SListNode* newnode = SListCreatNode(x);
		SListNode* prev = *plist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}

某个位置之后插入,当链表为空或者在尾节点插入时使用尾插。

void SListInsertB(SListNode** plist, SListNode* pos, SLTDataType x)
{
	assert(plist);
	assert(pos);
	if (*plist == NULL||pos->next==NULL)
	{
		SListPushBack(plist, x);//尾插
	}
	else
	{
		SListNode* tmp = pos->next;
		SListNode* newnode = SListCreatNode(x);
		pos->next = newnode;
		newnode->next = tmp;
	}
}

(2) 复杂度分析

  • 时间复杂度:无论向前插入还是向后插入都可能需要遍历整个链表,所以时间复杂度为O(N)。
  • 空间复杂度:固定创造一个节点,空间复杂度为O(1)。

5.8 任意位置的删除

任意位置的删除需要提前保存好前一个节点与后一个节点的位置。

(1) 代码实现

void SListErase(SListNode** plist, SListNode* pos)
{
	assert(plist);
	assert(*plist);
	assert(pos);
	if (*plist == pos)
	{
		SListPopFront(plist);//头删
	}
	SListNode* prev = *plist;
	while (prev->next!=pos)
	{
		prev = prev->next;
	}
	SListNode* tmp = pos->next;
	prev->next = tmp;
	free(pos);
	pos = NULL;
}

(2) 复杂度分析

  • 时间复杂度:可能需要遍历整个链表,所以时间复杂度为O(N)。
  • 空间复杂度:固定创造一个节点,空间复杂度为O(1)。

5.9 销毁链表

销毁链表需要依次遍历释放节点。

(1) 代码实现

void SListDestroy(SListNode** plist)
{
	assert(plist);
	assert(*plist);
	SListNode* cur = *plist;
	while (cur)
	{
		SListNode* tmp = cur->next;
		free(cur);
		cur =tmp;
	}
	*plist = NULL;//防止野指针
}

(2) 复杂度分析

  • 时间复杂度:需要遍历整个链表,所以时间复杂度为O(N)。
  • 空间复杂度:不需要格外的空间消耗,空间复杂度为O(1)。

6. 完整代码

(1) SList.h

#include<stdio.h>
#include<assert.h>
#include<stdlib.h>
typedef int SLTDataType;
typedef struct SListNode
{
	SLTDataType data;
	struct SListNode* next;
}SListNode;
void PrintSList(SListNode* plist);//打印链表
void SListPushFront(SListNode** plist, SLTDataType x);//头插
void SListPopFront(SListNode** plist);//头删
void SListPushBack(SListNode** plist,SLTDataType x);//尾插
void SListPopBack(SListNode** plist);//尾删
SListNode* SListSearch(SListNode* plist, SLTDataType x);//查找
void SListModify(SListNode* plist, SListNode* pos, SLTDataType x);//修改
void SListInsertF(SListNode** plist, SListNode* pos, SLTDataType x);//任意位置之前插入
void SListInsertB(SListNode** plist, SListNode* pos, SLTDataType x);//任意位置之后插入
void SListErase(SListNode** plist, SListNode* pos);//任意位置的删除
void SListDestroy(SListNode** plist);//销毁链表

(2) SList.c

#include"SList.h"
SListNode* SListCreatNode(SLTDataType x)
{
	SListNode* newnode = (SListNode*)malloc(sizeof(SListNode));
	if (newnode == NULL)
	{
		perror("malloc fail:");
		return;
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}
void PrintSList(SListNode* plist)
{
	SListNode* cur = plist;
	while (cur)
	{
		printf("%d->", cur->data);
		cur = cur->next;
	}
	printf("NULL\n");
}
void SListPushFront(SListNode** plist, SLTDataType x)
{
	assert(plist);
	SListNode* newnode = SListCreatNode(x);
	newnode->next = *plist;
	*plist = newnode;
}
void SListPopFront(SListNode** plist)
{
	assert(plist);
	assert(*plist);//防止链表为空
	SListNode* cur = (*plist)->next;
	free(*plist);
	*plist = cur;
}
void SListPushBack(SListNode** plist, SLTDataType x )
{
	assert(plist);
	if (*plist== NULL)
	{
		*plist = SListCreatNode(x);
	}
	else
	{
		SListNode* ptail = *plist;
		while (ptail->next)
		{
			ptail = ptail->next;
		}
		ptail->next = SListCreatNode(x);
	}
}
void SListPopBack(SListNode** plist)
{
	assert(plist);
	assert(*plist);//防止链表为空
	if ((*plist)->next == NULL)
	{
		free(*plist);
		*plist = NULL;
	}
	else
	{
		SListNode* ptail = *plist;
		while (ptail->next->next)
		{
			ptail = ptail->next;
		}
		free(ptail->next);
		ptail->next = NULL;
	}
	
}
SListNode* SListSearch(SListNode* plist, SLTDataType x)
{
	assert(plist);
	SListNode* cur = plist;
	while (cur)
	{
		if (cur->data == x)
		{
			return cur;
		}
		cur = cur->next;
	}
	return NULL;
}
void SListModify(SListNode*plist, SListNode* pos, SLTDataType x)
{
	assert(plist);
	assert(pos);
	SListNode* cur = plist;
	while (cur)
	{
		if (cur == pos)
		{
			cur->data = x;
			break;
		}
		cur = cur->next;
	}
}
void SListInsertF(SListNode** plist, SListNode* pos, SLTDataType x)
{
	assert(plist);
	assert(pos);
	if (*plist == pos || *plist == NULL)
	{
		SListPushFront(plist, x);//头插
	}
	else
	{

		SListNode* newnode = SListCreatNode(x);
		SListNode* prev = *plist;
		while (prev->next != pos)
		{
			prev = prev->next;
		}
		newnode->next = pos;
		prev->next = newnode;
	}
}
void SListInsertB(SListNode** plist, SListNode* pos, SLTDataType x)
{
	assert(plist);
	assert(pos);
	if (*plist == NULL||pos->next==NULL)
	{
		SListPushBack(plist, x);//尾插
	}
	else
	{
		SListNode* tmp = pos->next;
		SListNode* newnode = SListCreatNode(x);
		pos->next = newnode;
		newnode->next = tmp;
	}
}
void SListErase(SListNode** plist, SListNode* pos)
{
	assert(plist);
	assert(*plist);
	assert(pos);
	if (*plist == pos)
	{
		SListPopFront(plist);//头删
	}
	SListNode* prev = *plist;
	while (prev->next!=pos)
	{
		prev = prev->next;
	}
	SListNode* tmp = pos->next;
	prev->next = tmp;
	free(pos);
	pos = NULL;
}
void SListDestroy(SListNode** plist)
{
	assert(plist);
	assert(*plist);
	SListNode* cur = *plist;
	while (cur)
	{
		SListNode* tmp = cur->next;
		free(cur);
		cur =tmp;
	}
	*plist = NULL;//防止野指针
}

本文由博客一文多发平台 OpenWrite 发布!文章来源地址https://www.toymoban.com/news/detail-837859.html

到了这里,关于探索数据结构:单链表的实战指南的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 【数据结构】-- 单链表的实现

    在前面我们学习了顺序表,顺序表在数组的基础上提供了很多现成的方法,方便了我们对数据的管理,但是我们也发现顺序表有着许多不足: 在处理大型的数据时,需要频繁的增容且在中间删除或插入数据时需要遍历顺序表,这些性质导致了顺序表的 效率较低 。这时我们就

    2024年04月27日
    浏览(52)
  • 【数据结构—单链表的实现】

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 目录 前言 1. 链表的概念及结构 2. 单链表的实现 2.1单链表头文件——功能函数的定义 2.2单链表源文件——功能函数的实现 2.3 单链表源文件——功能的测试 3.具体的理解操作图 4. 链表的分类 总结 世上

    2024年02月05日
    浏览(66)
  • 数据结构--单链表的插入&删除

    目标 单链表的插入(位插、前插、后插) 单链表的删除 按为序插入(带头结点) ListInsert(L,i,e):插入操作。在表L中的第i个位置上插入指定元素e。 思路:找到第i-1个结点,将新结点插入其后 代码实现 时间复杂度 最好时间复杂度 O(1) 最坏时间复杂度 O(1) 平均时间复杂度 O(1) 按位

    2024年02月07日
    浏览(45)
  • 【数据结构】单链表的层层实现!! !

    关注小庄 顿顿解馋(●’◡’●) 上篇回顾 我们上篇学习了本质为数组的数据结构—顺序表,顺序表支持下标随机访问而且高速缓存命中率高,然而可能造成空间的浪费,同时增加数据时多次移动会造成效率低下,那有什么解决之法呢?这就得引入我们链表这种数据结构 概念

    2024年03月12日
    浏览(162)
  • 【数据结构】单链表的简单实现

    提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档 单链表是一种链式存取的数据结构,用一组地址任意的存储单元存放线性表中的数据元素。链表中的数据是以结点来表示的,每个结点的构成:元素(数据元素的映象) + 指针(指示后继元素存储位置),元

    2024年02月04日
    浏览(57)
  • 【数据结构】—— 单链表的增删改查

    ❤️一名热爱Java的大一学生,希望与各位大佬共同学习进步❤️ 🧑个人主页:@周小末天天开心 各位大佬的点赞👍 收藏⭐ 关注✅,是本人学习的最大动力 感谢! 📕该篇文章收录专栏—数据结构 目录 方法重写 重写条件 重写好处 重写演示 单链表 介绍 单链表的增删改查

    2024年02月02日
    浏览(47)
  • 【数据结构】单链表的定义和操作

    目录 1.单链表的定义 2.单链表的创建和初始化 3.单链表的插入节点操作 4.单链表的删除节点操作 5.单链表的查找节点操作 6.单链表的更新节点操作 7.完整代码 🌈嗨!我是Filotimo__🌈。很高兴与大家相识,希望我的博客能对你有所帮助。 💡本文由Filotimo__✍️原创,首发于CS

    2024年02月02日
    浏览(56)
  • 【数据结构】实现单链表的增删查

    链表类和节点类的定义: 图解: 从中间位置插入: 图解:假定index=2 尾插: 删除当前线性表中索引为index的元素,返回删除的元素值: 图解: 删除当前线性表中第一个值为element的元素: 删除当前线性表中所有值为element的元素: 将当前线性表中index位置的元素替换为eleme

    2024年02月14日
    浏览(164)
  • 数据结构:单链表的实现(C语言)

    个人主页 : 水月梦镜花 个人专栏 : 《C语言》 《数据结构》 本博客将要实现的无头单向不循环链表。 我们将节点定义为如下结构: 其成员变量有data,next。 将int重命名为STLDataType,方便我们以后修改数据域的内容。 动态申明一个空间,来放置数据。如下: 将data的内容置

    2024年02月14日
    浏览(59)
  • 【数据结构】反转链表、链表的中间节点、链表的回文结构(单链表OJ题)

    正如标题所说,本文会图文详细解析三道单链表OJ题,分别为:  反转链表 (简单)  链表的中间节点 (简单)  链表的回文结构 (较难) 把他们放在一起讲的原因是:  反转链表 和  链表的中间节点 是  链表的回文结构 的基础 为什么这样说?请往下看: 目录 1. 反转链

    2024年02月13日
    浏览(72)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包