线性表之单链表(详解)

这篇具有很好参考价值的文章主要介绍了线性表之单链表(详解)。希望对大家有所帮助。如果存在错误或未考虑完全的地方,请大家不吝赐教,您也可以点击"举报违法"按钮提交疑问。

🍕博客主页:️自信不孤单

🍬文章专栏:数据结构与算法

🍚代码仓库:破浪晓梦

🍭欢迎关注:欢迎大家点赞收藏+关注


🍥前言

在前一文章我们已经学习了顺序表,但是我们发现顺序表还有一些小缺点满足不了我们的需求,例如:

  • 中间/头部的插入删除,时间复杂度为O(N)。
  • 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。
  • 增容一般是呈2倍的增长,势必会有一定的空间浪费。

基于这些问题,我们来学习一种新的数据结构——链表,而链表就可以完美解决以上问题了。
在这篇文章中我们来重点学习一下单链表的实现。

🍉链表

1. 链表的概念及结构

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

理解

链表是由一系列结点组成的,结点可动态的生成。每个节点是一个结构体,而且每一个节点在堆上的开辟是随机的,我们可以通过结构体指针来维护每一个节点,将每一个节点链接起来,这样就形成了一条链。
链表中的节点是这样的:

线性表之单链表(详解)

  • DataType 表示要存放的某类型的数据。
  • *next 表示该结构体类型的指针,一般将此指针赋值为下一个结点的地址,这样就可以通过这个节点的指针找到下一个节点了。

链表的结构:

线性表之单链表(详解)

2. 链表的分类

实际中链表的结构非常多样,以下情况组合起来就有8种链表结构:

  1. 单向或者双向

线性表之单链表(详解)

  1. 带头或者不带头

线性表之单链表(详解)

  1. 循环或者非循环

线性表之单链表(详解)

虽然有这么多的链表的结构,但是我们实际中最常用还是两种结构:

线性表之单链表(详解)

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

3. 单链表的实现

首先创建两个文件来实现单链表:

  1. SList.h(节点的声明、接口函数声明、头文件的包含)
  2. SList.c(单链表接口函数的实现)

接着创建 test.c 文件来测试各个接口

如图:
线性表之单链表(详解)

SList.h 文件内容如下:

#pragma once

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

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

// 动态申请一个节点
SListNode* BuySListNode(SLTDataType x);
// 单链表打印
void SListPrint(SListNode* plist);
// 单链表尾插
void SListPushBack(SListNode** pplist, SLTDataType x);
// 单链表的头插
void SListPushFront(SListNode** pplist, SLTDataType x);
// 单链表的尾删
void SListPopBack(SListNode** pplist);
// 单链表头删
void SListPopFront(SListNode** pplist);
// 单链表查找
SListNode* SListFind(SListNode* plist, SLTDataType x);
// 在pos位置之后插入x
void SListInsertAfter(SListNode* pos, SLTDataType x);
// 在pos位置之前插入x
void SListInsert(SListNode** pplist, SListNode* pos, SLTDataType x);
// 删除pos位置之后的值
void SListEraseAfter(SListNode* pos);
// 删除pos位置的值
void SListErase(SListNode** pplist, SListNode* pos);
// 单链表的销毁
void SListDestroy(SListNode* plist);

接下来,我们在 SList.c 文件中实现各个接口函数。

3.1 动态申请一个节点

在堆上申请一个节点结构体大小的空间,并用该节点存放数据 x,节点的 next 指针指向 NULL,返回节点的地址。

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

3.2 打印单链表

注意:这里不需要的 plist 进行断言。plist 为空,则打印 NULL。

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

3.3 单链表尾插

尾插分为两种情况:

  1. 当链表为空时,头指针 plist 指向 NULL。此时要改变 plist 的指向,让 plist 指向新开辟好的结点,就需要用二级指针来改变一级指针的值(如果传参传的是 plist,并用一级指针作为形参,形参的改变不会影响实参,那么 plist 就不会被改变)。
  2. 当链表非空时,只需通过循环找到尾节点,并将为节点的 next 指针赋值为新开辟好节点的地址。
void SListPushBack(SListNode** pplist, SLTDataType x)
{
	assert(pplist);
	if (*pplist)
	{
		SListNode* cur = *pplist;
		while (cur->next)
		{
			cur = cur->next;
		}
		cur->next = BuySListNode(x);
	}
	else
	{
		*pplist = BuySListNode(x);
	}
}

3.4 单链表尾删

分两种情况讨论即可。

void SListPopBack(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);
	SListNode* cur = *pplist;
	if (cur->next)
	{
		while (cur->next->next)
		{
			cur = cur->next;
		}
		free(cur->next);
		cur->next = NULL;
	}
	else
	{
		free(cur);
		cur = NULL;
		*pplist = NULL;
	}
}

3.5 单链表头插

void SListPushFront(SListNode** pplist, SLTDataType x)
{
	assert(pplist);
	SListNode* tmp = *pplist;
	*pplist = BuySListNode(x);
	(*pplist)->next = tmp;
}

3.6 单链表头删

void SListPopFront(SListNode** pplist)
{
	assert(pplist);
	assert(*pplist);
	SListNode* del = *pplist;
	*pplist = (*pplist)->next;
	free(del);
	del = NULL;
}

3.7 单链表查找

返回所找到节点的指针,没找到则返回 NULL。

注:查找函数可以配合指定位置操作函数来使用。

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

3.8 在指定位置后插入数据

void SListInsertAfter(SListNode* pos, SLTDataType x)
{
	assert(pos);
	SListNode* tmp = pos->next;
	pos->next = BuySListNode(x);
	pos->next->next = tmp;
}

3.9 在指定位置前插入数据

注意分情况讨论,判断 pos 位置是否为头指针的位置。

void SListInsert(SListNode** pplist, SListNode* pos, SLTDataType x)
{
	assert(pos);
	assert(pplist);
	if (*pplist == pos)
	{
		SListPushFront(pplist, x);
		return;
	}
	SListNode* cur = *pplist;
	while (cur)
	{
		if (cur->next == pos)
		{
			SListNode* tmp = cur->next;
			cur->next = BuySListNode(x);
			cur->next->next = tmp;
			return;
		}
		cur = cur->next;
	}
}

3.10 删除指定位置之后的数据

void SListEraseAfter(SListNode* pos)
{
	assert(pos);
	assert(pos->next);
	SListNode* del = pos->next;
	pos->next = del->next;
	free(del);
	del = NULL;
}

3.11 删除指定位置的数据

注意分情况讨论,判断 pos 位置是否为头指针的位置。

void SListErase(SListNode** pplist, SListNode* pos)
{
	assert(pos);
	assert(pplist);
	SListNode* cur = *pplist;
	if (*pplist == pos)
	{
		*pplist = (*pplist)->next;
		free(cur);
		return;
	}
	while (cur)
	{
		if (cur->next == pos)
		{
			SListNode* del = pos;
			cur->next = del->next;
			free(del);
			return;
		}
		cur = cur->next;
	}
}

3.12 单链表的销毁

不要忘记把 plist 置空。

void SListDestroy(SListNode** pplist)
{
	assert(pplist);
	SListNode* del = *pplist;
	while (del)
	{
		SListNode* cur = del->next;
		free(del);
		del = cur;
	}
	*pplist = NULL;
}

注:在每个接口函数中一定要合理地使用assert函数断言防止对空指针的引用。

4. 接口测试

test.c 文件内容如下:

#include "SList.h"

void Test()
{
	SListNode* plist = NULL;

	//头插
	SListPushFront(&plist, 3);
	SListPrint(plist);
	SListPushFront(&plist, 1);
	SListPrint(plist);

	//尾插
	SListPushBack(&plist, 5);
	SListPrint(plist);

	//指定位置后插
	SListNode* insert = SListFind(plist, 3);
	SListInsertAfter(insert, 4);
	SListPrint(plist);

	//指定位置前插
	SListInsert(&plist, insert, 2);
	SListPrint(plist);

	//头删
	SListPopFront(&plist);
	SListPrint(plist);
	SListPopFront(&plist);
	SListPrint(plist);

	//尾删
	SListPopBack(&plist);
	SListPrint(plist);

	//指定位置后删
	SListNode* del = SListFind(plist, 3);
	SListEraseAfter(del);
	SListPrint(plist);

	//指定位置删除
	SListErase(&plist, del);
	SListPrint(plist);

	SListDestroy(&plist);
}

int main()
{
	Test();
	return 0;
}

运行结果:

线性表之单链表(详解)文章来源地址https://www.toymoban.com/news/detail-447368.html

到了这里,关于线性表之单链表(详解)的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

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

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

相关文章

  • 数据结构:线性表之-循环双向链表(万字详解)

    数据结构:线性表之-循环双向链表(万字详解)

    目录 基本概念 1,什么是双向链表 2,与单向链表的区别 双向链表详解 功能展示: 1. 定义链表 2,创建双向链表 3,初始化链表 4,尾插 5,头插 6,尾删 判断链表是否被删空 尾删代码 7,头删 8,pos位置之前插入 优化后的头插 优化后的尾插 9,删除pos位置的节点 优化后的尾删 优

    2024年02月09日
    浏览(13)
  • 【数据结构】- 链表之单链表(下)

    【数据结构】- 链表之单链表(下)

    未来藏在迷雾中 叫人看来胆怯 带你踏足其中 就会云开雾散 本章是关于数据结构中的链表之单链表(下) 提示:以下是本篇文章正文内容,下面案例可供参考 1.2.1 在pos位置插入(也就是pos位置之前) 流程图 多个节点 一个节点 1.2.2 在pos位置之后插入 流程图: 注意: 下面这种写

    2023年04月23日
    浏览(57)
  • 【数据结构】:单链表之头插法和尾插法(动图+图解)

    【数据结构】:单链表之头插法和尾插法(动图+图解)

    链表的定义 什么是头插法❓ 在插入时,新的结点插入到当前链表的表头。 怎么实现头插法❓ 💤思考一:头插法的核心是什么❓ 以有头结点为例: 只需要将新的节点插在头结点和首元结点之间。所以核心代码为: 注意:①②能否交换顺序❓ 假设可以,那么代码为: ② L-n

    2024年02月03日
    浏览(15)
  • 线性表之链表

    线性表之链表

    在计算机科学中,链表是一种常见的数据结构,用于存储和组织数据。相比于顺序表,链表具有更高的灵活性和动态性。 在本博客中,我们将深入讨论链表的概念、分类以及实现方法。我们将从链表的基本概念开始,了解链表是如何组织数据的,并分析链表的优势和劣势。

    2024年02月11日
    浏览(13)
  • DS线性表之链表

    DS线性表之链表

    我们上一期介绍了顺序表,它的底层就是数组,我们也分别对顺序表的动态版本和静态版本进行了实现!并且分析了顺序表的优缺点,优点是:尾插、尾删效率很高,其时间复杂度是O(1);缺点是:在头部插入、删除的时候效率低,其时间复杂度是O(N);而且即使是动态版本的扩

    2024年02月08日
    浏览(11)
  • 【数据结构】线性表之链表

    【数据结构】线性表之链表

    上一篇文章讲述了线性表中的顺序表,这篇文章讲述关于链表的定义、类别、实现、多种不同链表的优缺点和链表与顺序表的优缺点。 关于上一篇文章的链接:线性表之顺序表 链表是一种物理存储结构上 非连续、非顺序 的存储结构,数据元素的逻辑顺序是通过链表中的指针

    2024年02月05日
    浏览(50)
  • 数据结构:线性表之-顺序表

    数据结构:线性表之-顺序表

    目录 1.线性表概念 1.1 什么是顺序列表 1.2 线性表 2.顺序表实现 将有以下功能: 详细过程 顺序表的动态存储 顺序表初始化 尾插 扩容 头插 更改后的尾插 尾删 头删 打印 释放内存 优化顺序表 (任意位置插入删除) 优化后的头插尾插 优化后的头删尾删 查找和删除 进行装饰(菜单

    2024年02月10日
    浏览(51)
  • 【数据结构】线性表之栈、队列

    【数据结构】线性表之栈、队列

    前面两篇文章讲述了关于线性表中的顺序表与链表,这篇文章继续讲述线性表中的 栈和队列。 这里讲述的两种线性表与前面的线性表不同,只允许在一端入数据,一段出数据,详细内容请看下面的文章。 顺序表与链表两篇文章的链接: 线性表之顺序表 线性表之链表 注意:

    2024年02月06日
    浏览(47)
  • 【数据结构】线性表之顺序表

    【数据结构】线性表之顺序表

    线性表是 n (n = 0) 个具有相同特性的数据元素的有限序列。线性表是一种在实际中广泛使用的数据结构,常见的线性表:顺序表、链表、栈、队列… 线性表在 逻辑上是线性结构 ,也就说是连续的一条直线。但是在 物理结构 上并 不一定 是连续的线性表在物理上存储时,通常以

    2024年02月04日
    浏览(46)
  • 数据结构——线性表之顺序表

    数据结构——线性表之顺序表

    目录 一.线性表 二.顺序表实现  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 修改函数 三.完整代码 四.力扣

    2024年02月07日
    浏览(46)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包