数据结构学习分享之单链表详解

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


1. 前言

    💓博主CSDN:杭电码农-NEO💓🎉🎉🎉

    ⏩专栏分类:数据结构学习分享(持续更新中🫵)⏪🎉🎉🎉

  让我们紧接上一章顺序表的概念,引出链表,我们说顺序表每次增容需要申请新的空间,会产生很多空间碎片,代价比较高,并且我们扩容一般是扩两倍,还是会有一些空间被我们浪费掉. 所以我们基于顺序表的缺点,引出了链表的概念


2. 链表的概念以及结构

概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 。我们光听这个概念可能有一点抽象,所以我们以画图的形式给大家说明一下;

数据结构学习分享之单链表详解


我们画的图是形象图,但是实际上我们链表没有箭头这一说法,箭头只是帮助大家理解它的结构,其实它的真实存储结构应该是这样的: 🔑🔑

数据结构学习分享之单链表详解注意:

  • 在上图中我们可以发现链式结构在逻辑上是连续的(即一个节点链接着一个节点),但是在物理上不一定连续(即地址不连续,节点1为A0,节点2为B0) 🔥🔥

  • 实际存储中的节点是从堆上申请出来的,在堆上申请的空间是按照一定的策略来分配的,两次手球的空间可能连续也可能不连续 🔥🔥


3. 链表的分类

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

  1. 单向或者双向:
    数据结构学习分享之单链表详解

  1. 带头或者不带头(也叫做哨兵位):
    数据结构学习分享之单链表详解

  1. 循环或非循环:

数据结构学习分享之单链表详解


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

数据结构学习分享之单链表详解

  • 无头单向非循环链表:结构简单,一般不会单独用来存数据。实际中更多是作为其他数据结构的子结构,如哈希桶、图的邻接表等等。另外这种结构在笔试面试中出现很多💬

  • 带头双向循环链表:结构最复杂,一般用在单独存储数据。实际中使用的链表数据结构,都是带头双向循环链表。另外这个结构虽然结构复杂,但是使用代码实现以后会发现结构会带来很多优势,实现反而简单了,后面我们代码实现了就知道了💬。

我们本节要介绍的就是无头单向非循环链表 🎉🎉🎉

4.链表的实现

4.1 初始化结构

和顺序表一样,在我们实现增删查改等功能之前,我们要先在.h文件中包含常见的头文件并且定义一个结构体后将它重命名:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;//想存储字符型就把int改成char
typedef struct SlistNode
{
	SLTDateType data;//链表节点中存储的数据
	struct SlistNode* next;//节点中存储的下一个节点的地址
}SLTNode;

然后在在我们的test.c文件和Slist.c文件中包含Slist.h.不同于顺序表的是我们这里在test.c文件中定义一个plist结构体指针就可以直接开始使用了.我们接下来先在.h文件中将我们所有的函数都声明一遍:

#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<assert.h>
typedef int SLTDateType;
typedef struct SlistNode
{
	SLTDateType data;
	struct SlistNode* next;
}SLTNode;

void SListPrint(SLTNode** phead);//打印链表元素

SLTNode* BuyListNode(SLTDateType x);//开辟空间

void SListPushBack(SLTNode** phead, SLTDateType x);//尾插

void SListPopBack(SLTNode** phead);//尾删

void SListPushFront(SLTNode** phead, SLTDateType x);//头插

void SListPopFront(SLTNode** phead);//头删

SLTNode* SListFind(SLTNode* phead, SLTDateType x);//查找

void SListInsert(SLTNode** phead, SLTNode* pos, SLTDateType x);//在pos位置前插入数据

😄😄😄

我们发现这里的增删查改都是传的二级指针,这是因为我们在test.c中定义结构体变量时定义的是一个结构体指针指向我们链表的第一个节点,我们在进行增删查改的过程中可以会改变这个结构体指针的指向(比如头插后,我们的结构体指针指向新的链表的头),所以我们需要传二级指针过去才能改变它的值.如果听到这儿你还不明白,可以先去我的码云看看所有的代码再慢慢理解gitee-杭电码农


4.2 尾插函数

既然是要插入数据,那么与数组不同的是链表每插入一次数据就开辟一块与这个数据大小相同的空间,所以我们第一步要做的就是动态开辟空间:

void SListPushBack(SLTNode** phead, SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//定义一个新节点后动态开辟空间
	newnode->data = x;//将要插入的数据x赋值到节点的data上
	newnode->next = NULL;//将尾插后的节点的next置空.
}

当我们开辟好空间之后,我们得先找到尾,才能尾插,所以我们这里定义一个变量取遍历链表来找到尾:

void SListPushBack(SLTNode** phead, SLTDateType x)
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));//定义一个新节点后动态开辟空间
	newnode->data = x;//将要插入的数据x赋值到节点的data上
	newnode->next = NULL;//将尾插后的节点的next置空.

    SLTNode* cur = *phead; //定义一个变量指向链表得头节点
    while (cur->next!=NULL)//当cur->next等于NULL时,此时cur就是最后一个节点
	   {
		  cur = cur->next;//cur不断往后遍历
	   }
	   cur->next = newnode;//这时cur已经等于最后一个节点后出了while循环,将最后一个节点与新节点链接起来
}

值得注意的是这里有一种特殊情况,就当整个链表没有存储数据时,我们得phead为空指针,cur也是空指针,然后我们使用了cur->next相当于对空指针解引用了,所以这个地方得特殊情况要特殊考虑,优化代码后为:

void SListPushBack(SLTNode** phead, SLTDateType x)
{
    assert(*phead);//确保链表不为空
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;
	newnode->next = NULL;
	if (*phead == NULL)
	{
		*phead = newnode;//当链表为空时直接将phead给给newnode
	}
	else
	{
	   SLTNode* cur = *phead; 
       while (cur->next)
	   {
		cur = cur->next;
	   }
	   cur->next = newnode;
	}
}


4.3 尾删函数

和尾插类型,先找到尾再把尾节点的空间给释放掉,然后将尾节点的前一个节点的next置空使它变成新的尾节点:

void SListPopBack(SLTNode** phead)
{
		SLTNode* cur = *phead;
		SLTNode* prev = NULL;//定义一个prev指向cur的前一个结点
		while (cur->next!=NULL)//当cur->next为空时就代表cur是尾节点了,此时prev是尾节点的前一个结点
		{
			prev = cur;//prev是cur结点的前面一个结点
			cur = cur->next;
		}
		prev->next = NULL;//将prev结点置空成为新的尾
		free(cur);//将要尾删的空间给释放掉
		cur = NULL;
}

还是和之前同一个问题,当我们链表中只有一个节点时,我们的prev还是NULL,后面讲prev->=NULL,也是对空指针解引用,也会遇见对空指针解引用的错误操作,所以这个地方我们优化代码:

void SListPopBack(SLTNode** phead)
{
	if ((*phead)->next == NULL)
	{
		free(*phead);//当链表中只有一个节点时,直接free掉就行
		*phead = NULL;
	}
	else
	{
		SLTNode* cur = *phead;
		SLTNode* prev = NULL;
		while (cur->next)
		{
			prev = cur;
			cur = cur->next;
		}
		prev->next = NULL;
		free(cur);
		cur = NULL;
	}
}


4.4 头插函数

有了前面尾插,尾删的基础,这里我们直接上代码:

void SListPushFront(SLTNode** phead, SLTDateType x)
{

	SLTNode* newhead = (SLTNode*)malloc(sizeof(SLTNode));//只要是插入数据就要开辟新空间
	newhead->next = *phead;
	newhead->data = x;
	*phead = newhead;//把新的头给phead
}


4.5 头删函数

这里头部的删除要考虑链表中是否还存在节点,并且链表中只有一个节点的情况也要拿出来特殊考虑

void SListPopFront(SLTNode** phead)
{
	assert(*phead);//确保链表不为空
	if ((*phead)->next == NULL)//链表只有一个节点
	{
		free(*phead);//直接释放掉空间后置空
		*phead = NULL;
	}
	else
	{
		SLTNode* newhead = (*phead)->next;//定义头节点的下一个节点,不然把头节点的空间释放掉后会找不到下一个节点
		free(*phead);
		*phead = newhead;//把phead给上新的头节点
	}
}


4.6 开辟新节点

我们发现,在操作链表时,只要涉及到插入数据就会用到开辟动态内存这一个步骤,所以我们可以把这个步骤单独拿出来,遇见头插尾插时可以在函数中调用这个开辟空间的函数:

SLTNode* BuyListNode(SLTDateType x)//返回一个节点的地址
{
	SLTNode* newnode = (SLTNode*)malloc(sizeof(SLTNode));
	newnode->data = x;//将插入的数据给上
	newnode->next = NULL;
	return newnode;//将开辟好后的空间返回
}

当我们写了这个函数过后,我们的头插和尾插就可以简化一点了🥳🥳🥳
比如我们的尾插可以改为:

void SListPushBack(SLTNode** phead, SLTDateType x)
{
    assert(*phead);//确保链表不为空
	SLTNode* newnode = BuyListNode(SLTDateType x);//定义一个节点来接受开辟的空间
	if (*phead == NULL)
	{
		*phead = newnode;//当链表为空时直接将phead给给newnode
	}
	else
	{
	   SLTNode* cur = *phead; 
       while (cur->next)
	   {
		cur = cur->next;
	   }
	   cur->next = newnode;
	}
}


4.7 销毁链表

当我们使用完链表后,需要销毁它里面的数据和空间:

void SListDestroy(SLTNode** phead)
{
	assert(phead);
	SLTNode* cur = *phead;
	while(cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*phead = NULL;
}


5. 单链表OJ题目

在我的专栏单链表面试题分享中其实就给大家分享了很多个单链表面试题的题解思路以及一些衍生问题的探讨,有兴趣的同学可以点击前面跳转.或者如果你想自己做一遍题不想先看答案的,我给大家把这些题的链接放出来 :

  ✍🏼✍🏼✍🏼

  • 删除链表中等于给定值 val 的所有节点。OJ题

  • 反转一个单链表.OJ题

  • 给定一个带有头结点 head 的非空单链表,返回链表的中间结点。如果有两个中间结点,则返回第二个
    中间结点
    。OJ题

  • 输入一个链表,输出该链表中倒数第k个结点OJ题

  • 将两个有序链表合并为一个新的有序链表并返回。新链表是通过拼接给定的两个链表的所有节点组成
    。OJ题

  • 链表的回文结构。OJ题

  • 输入两个链表,找出它们的第一个公共结点。OJ题

  • 给定一个链表,判断链表中是否有环。OJ题

  • 给定一个链表,返回链表开始入环的第一个节点。 如果链表无环,则返回 NULLOJ题

  • 给定一个链表,每个节点包含一个额外增加的随机指针,该指针可以指向链表中的任何节点或空节点。
    要求返回这个链表的深度拷贝
    。OJ题

在我往期的博客当中有这些题目的画图详解,当你们做题时遇见问题可以跳转我的专栏单链表面试题分享

6. 顺序表和链表的区别

我们用一个表格来阐述:

不同点 顺序表 链表
存储空间上 物理上一定连续 逻辑上一定连续,物理上不一定
随机访问 支持O(1) O(N)
任一位置插入或删除 需要挪动元素,效率低 只需要修改指针指向
插入 动态顺序表,空间不够需扩容 没有容量的概念
应用场景 元素高效存储和快速访问 频繁的任一位置插入或删除
缓存利用率

后期遇见既可以用顺序表作为结构也可以用链表作为结构的数据时,再详细讨论到底用哪个好



7. 总结

链表是为了弥补顺序表的缺陷而出现的一种数据结构,当然链表本身也有缺陷,它不能解决所有问题,所以我们后期还会学一些数据结构来完善我们对于结构的理解 有关于链表中最简单的结构:单链表的实现我们就讲到这里,如果有帮到你请点点赞吧.📝📝📝

💕 我的码云:gitee-杭电码农-NEO💕文章来源地址https://www.toymoban.com/news/detail-430678.html

🔎 下期预告:双向带头循环链表 🔍

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

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

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

相关文章

  • 【数据结构】单链表详解

    当我们学完顺序表的时候,我们发现了好多问题如下: 中间/头部的插入删除,时间复杂度为O(N) 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 增容一般是呈2倍的增长,势必会有一定的空间浪费。例如当前容量为100,满了以后增容到200,我们 再继续插入了

    2024年02月09日
    浏览(34)
  • 数据结构 | 单链表专题【详解】

    顺序表遗留下来的问题 中间/头部的插⼊删除,时间复杂度为O(N) 增容需要申请新空间,拷贝数据,释放旧空间。会有不小的消耗。 增容⼀般是呈2倍的增长,势必会有⼀定的空间浪费。例如当前容量为100,满了以后增容到 200,我们再继续插入了5个数据,后⾯没有数据插入了

    2024年02月06日
    浏览(40)
  • 数据结构之单链表详解

    😽博主CSDN主页:小源_😽 🖋️个人专栏:《数据结构》🖋️ 😀努力追逐大佬们的步伐~ 目录 1.前言 2. 链表 2.1 链表的概念及结构 2.2 链表的结构分类 3. 无头单向非循环链表的模拟实现 3.1 定义IList接口 3.2 重写IList中的方法 3.3 display(打印方法) 3.4 size方法 3.5 contains方法

    2024年02月03日
    浏览(37)
  • 数据结构学习分享之堆的详解以及TopK问题

    💓博主CSDN主页:杭电码农-NEO💓   ⏩专栏分类:数据结构学习分享⏪   🚚代码仓库:NEO的学习日记🚚   🌹关注我🫵带你了解更多数据结构的知识   🔝🔝 本章就给大家带来久违的堆的知识,如果你还不知道数的相关知识,或者什么是完全二叉树,请跳转 树的介绍, 本章的堆结

    2024年02月05日
    浏览(109)
  • 【数据结构】单链表(带图详解)

    概念 :链表是一种 物理存储结构 上 非连续 、 非顺序 的存储结构,但链表在逻辑上是连续的,顺序的,而数据元素的逻辑顺序是通过链表中的指针连接次序实现的。 单链表 (Singly Linked List)是一种常用的数据结构,它由若干个节点组成,每个节点包含两部分: 数据域 和

    2024年02月12日
    浏览(42)
  • [数据结构]链表之单链表(详解)

    在学习 链表 之前,我们已经学习了 顺序表 了 根据 顺序表 的特点,我们可以发现 顺序表 有优点,也有一些缺陷。 所以根据 顺序表 的缺点,链表就横空出世啦~ **概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次

    2023年04月08日
    浏览(55)
  • 数据结构入门 — 链表详解_单链表

    数据结构入门 — 单链表详解 * 博客主页链接:https://blog.csdn.net/m0_74014525 关注博主,后期持续更新系列文章 文章末尾有源码 *****感谢观看,希望对你有所帮助***** 第一篇:数据结构入门 — 链表详解_单链表 第二篇:数据结构入门 — 链表详解_双向链表 第三篇:数据结构入门

    2024年02月11日
    浏览(55)
  • 数据结构之单链表详解(C语言手撕)

    ​ 🎉文章简介: 概念:链表是一种物理存储结构上非连续、非顺序的存储结构,数据元素的逻辑顺序是通过链表中的指针链接次序实现的 从图片中可以看出,链表的每个节点都是一个结构体,该结构体中有一个存储数据的变量和一个指向下一节点的结构体指针; 在逻辑上

    2024年03月10日
    浏览(45)
  • 【算法与数据结构】 C语言实现单链表队列详解

    前面我们学习了队列的顺序表的实现,本节将用单链表实现队列。 队列也可以数组和链表的结构实现, 使用链表的结构实现更优一些,因为如果使用数组的结构,出队列在数组头上出数据,效率会比较低 。下面我们先复习一下队列的基本概念: 队列:只允许在一端进行插入

    2024年04月11日
    浏览(59)
  • 【数据结构与算法】顺序表与链表(单链表和双链表)超详解图示与源码。

                                                       大家好,今天我们来学习数据结构中的顺序表与链表!源码在最后附上 首先我们先来认识一下 顺序表 :                                       **如上图所示:很多人会以为数组就是顺序表,顺序表就是数组,这

    2024年02月21日
    浏览(59)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包