数据结构入门指南:链表(新手避坑指南)

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

目录

前言

1.链表

1.1链表的概念

 1.2链表的分类

1.2.1单向或双向

1.2.2.带头或者不带头

1.2.33. 循环或者非循环

1.3链表的实现

 定义链表

总结


前言

        前边我们学习了顺序表,顺序表是数据结构中最简单的一种线性数据结构,今天我们来学习链表,难度相较于顺序表会大幅增加,非常考验大家对结构体、指针的理解。但是也不要害怕,我会一一向大家解答疑惑,本期的内容先给初学者预预热,主要介绍在刚开始学习链表时需要注意的点、涉及的基础知识以及逻辑基础,下期会将功能接口具体实现。


1.链表

1.1链表的概念

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

 逻辑图:

数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

 而现实中的链表是这样的:

数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

         通过图我们可以观察到,链式数据结构在逻辑上是连续的,在物理上不一定连续。链表的好处在于对内存更高效的使用(加入一个节点就开辟一个节点的空间)。

注意:

  • 链表的节点是在堆上开辟的,程序不结束就不会主动释放。
  • 从堆上申请的空间是按照一定策略分配的,两次申请的空间可能连续,也可能不连续。

 1.2链表的分类

        链表主要分为以下几种:

1.2.1单向或双向

数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

         单向的链表简称为单链表,单链表只可以进行单向遍历,而双向链表完美的弥补了这个缺陷,可以向前遍历也可以向后遍历

1.2.2带头或者不带头

数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

        它们本质上并没有太大的区别,在链表功能实现过程中,有头节点的不需要对特殊节点进行特殊操作,相对简单,但对于大部分的刷题网站上链表的题目都是默认为无头节点的,所以对于无头节点链表的理解更为重要。

1.2.3 循环或者非循环

 数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

        循环链表可以用于解决一些特定的问题,非循环链表一般用于普通的链表操作,例如插入、删除、查找等。

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

数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

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

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

1.3链表的实现

         对于初学者来说,我个人建议先从无头单向非循环链表学起,因为在很多的刷题场景中都是单项无头非循环链表,理解了它也可以帮助你更快的适应链表的刷题。今天我们主要从这种单链表讲起。

         单链表的实现过程中会存在很多的坑,对于初学者来说是很困难的,我会一一列举帮助大家避免这些错误,刚开始我会从基础知识层面进行逐个分析,当然内容也会很多,我会分期进行讲解。

 定义链表

typedef int Datatype;
typedef struct SLNode
{
	Datatype data;
	SLNode* next;
}SLNode;

         定义链表这里就迎来了第一个坑,上述的这种形式很常见,对于初学者来说这里就有一个坑,这个链表定义的是否正确呢?

         这种方式是不正确的,为什么?typedef是重命名,结构体是我们自定义类型,SLNode是重命名后的名字,但是这里需要注意,这个重命名是从上述代码第六行结束后才开始生效,在结构体中提前使用是不允许的。

正确的定义:

typedef int Datatype;
typedef struct SLNode
{
	Datatype data;
	struct SLNode* next;
}SLNode;

         定义之后就需要创建节点,创建节点这里我们要明白:我们是通过函数的形式来创建节点,创建时顺便赋值,初学者或许会有这样的疑问:那为什么函数的类型是结构体指针类型?例如:

SLNode* NewNode(Datatype x)
{
}

        为了后续节点的链接,我们最后需要返回新建节点的地址,而节点地址的类型就是SLNode* ,函数的类型也只是函数的返回类型。

 接下来就是功能实现:

SLNode* NewNode(Datatype x)
{
	SLNode* newnode = (SLNode*)malloc(sizeof(Datatype));
	if (newnode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newnode->data = x;
	newnode->next = NULL;
	return newnode;
}

 这里就迎来了第二个坑, 这段代码哪里有问题?

        首先我们要清楚链表开辟空间是给谁开辟的,链表的空间是给整个节点(结构体)开辟的,而不是仅仅给数据开辟空间。

 所以说这里malloc的大小应该是结构体的大小,改为

SLNode* newnode = (SLNode*)malloc(sizeof(SLNode));

就可以啦!

         在其他功能实现之前,我们先理解以下打印链表的函数接口。

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

         打印链表首先需要遍历链表,phead为链表的头指针,但是一般情况下我们并不会直接的使用头指针去遍历,因为那样可能会造成头节点的丢失,所以我们需要创建另一个变量来接收头指针去执行遍历操作。我们要想让cur向后移动就只需把下一个节点的地址赋值给cur,我们知道一个链表的节点内会存储着下一个节点的地址,也就是当前节点中的next。注意这里的遍历需要好好的理解,这是进行后续理解的前提。

         理解完链表的的遍历,接下我们来说说它们是如何将每个新建的节点链接的。初学链表可能会有这样的疑惑:创建的节点是如何一个个链接起来的呢?节点的链接是属于后续头插,尾插等操作的内容,但是为了解答大家内心的疑惑,我们先写一个简单的测试接口,来演示它是如何链接的

void test1()
{
	SLNode* plist = NULL;//链表的头指针,没有节点的情况下指向NULL
	int n = 0;
	printf("请输入链表的长度\n");
	scanf("%d", &n);
	printf("请输入数据\n");
	for (int i = 0; i < n; i++)
	{
		int val = 0;
		scanf("%d", &val);
		SLNode *newnode= NewNode(val);//创建结构体指针变量来接收新节点的地址
        //头插的方式链接
		newnode->next = plist;
		plist = newnode;
	}
    SLprint(plist);
}



int main()
{
	test1();


	return 0;
}

         首先我们把头指针指向的内容(可能是NULL,也可能是第一个节点)赋值给新节点。

数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

         初始情况下,plist指向NULL,把plist指向的内容赋值给新节点newnode的next(指针域),然后把整个新节点的地址赋给plist。

数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

         这样plist就成功指向了第一个节点。那么后续插入增加节点也是这样。假设上一步链接的节点地址是0x0012ff40。

数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

         把plist指向的节点(地址)赋值给新建节点newnode的next(结构体内的成员)。

数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

         然后把整个新节点的地址赋给plist。这样就通过头插将节点链接了。但是这里需要注意,测试接口的操作是在同一个函数内进行创建头指针,节点链接操作的(不需要传值),没有调用函数进行头插链接(在后续实现头插,尾插接口的过程中需要使用二级指针传参进行操作)。

 接着我们继续,尾插操作的实现

        尾插的原理是什么?找到最后一节点。

数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

 将新节点的地址赋给最后一个节点的next(指针域)就可以了。

 数据结构入门指南:链表(新手避坑指南),数据结构,链表,c语言,其他,经验分享

 注意:新节点的next(指针域)在创建初始化时就已经置为NULL。

        但这并不完整,前边我们提到,尾插也可以用来初始化,那么对于没有节点的情况,上述逻辑就无法使用了。所以我们需要特殊处理一下,判断链表是否为NULL。

void SLPushBlack(SLNode* phead, Datatype x)
{
	SLNode* newnode = NewNode(x);
	SLNode* tail = *pphead;
	if (phead == NULL)
	{
		phead = newnode;
	}
	else
	{
		while (tail->next)//找到最后一个节点
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}

         以上的逻辑可以这样实现,那段代码是否正确呢?这里就是遇到的第三个坑。运行测试一下我们会发现链表为空的时候没有插入数据。这是为什么呢?

        那是因为传进来的头节点是形参,形参是实参的临时拷贝,出了函数phead就被销毁了,在函数内将新节点地址赋给形参 头节点并不会对实参中的头节点造成影响。那这里要想修改实参中头节点的指向就只能传头节点的地址(二级指针),通过实参中头指针的地址来修改头指针的指向。

 所以正确的代码应该是:

void SLPushBlack(SLNode** pphead, Datatype x)
{
	SLNode* newnode = NewNode(x);
	SLNode* tail = *pphead;
	if (*pphead == NULL)
	{
		*pphead = newnode;
	}
	else
	{
		while (tail->next)
		{
			tail = tail->next;
		}
		tail->next = newnode;
	}
}
//调用时要传结构体指针的地址
SLPushBlack(&plist,100);

 那或许会有人疑惑,为什么链表不为的时候就不使用二级指针?

        这里我们总结一下,使用二级指针是因为要修改结构体指针(头指针)的指向,而链表不为空时,只需要链接增加一个节点就好,这时修改的是结构体成员的内容。

对头插操作进行实现

         使用二级指针是因为要修改结构体指针(头指针)的指向,那按照逻辑,头插每次都是修改头指针的指向,所以头插独立实现成一个函数时也需要二级指针,尾插是只有第一次链表为空的时候需要二级指针,而头插次次都需要二级指针。

        注意前边测试的接口头插数据没有使用二级指针是因为创建头指针和修改头指针指向在同一个函数中,不存在函数调用头指针。

前边我们已经了解头插的逻辑,这里就不再讲解。代码实现:

void SLPushFront(SLNode** pphead, Datatype x)
{
	SLNode* newnode = NewNode(x);
	newnode->next = *pphead;
	*pphead = newnode;
}

         pphead就是指向plist(头指针)的指针,*pphead就等价于plist。通过对pphead解引用找到plist(头指针),直接修改plist(头指针)的指向。


总结

        本期内容主要介绍在刚开始学习链表时需要注意的点、涉及的基础知识以及逻辑基础,一定要理解透彻,否则后续的接口实现将会寸步难行,好的,本期内容到此就要结束啦,希望对你有所帮助,最后,感谢阅读!文章来源地址https://www.toymoban.com/news/detail-617779.html

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

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

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

相关文章

  • C语言笔记 | 数据结构入门指南

    文章目录 0x00 前言 0x01 百鸡百钱 0x1 题目描述 0x2 问题分析 0x3 代码设计 0x4 完整代码 0x5 运行效果 0x6 举一反三 [兔鸡百钱] 0x02 借书方案知多少 0x1 题目描述 0x2 问题分析 0x3 代码设计 0x4 完整代码 0x5 运行效果 0x6 举一反三 [领导小组方案] 0x03 打鱼还是晒网 0x1 题目描述 0x2 问题分

    2024年02月08日
    浏览(48)
  • 数据结构入门指南:单链表(附源码)

    目录 前言 尾删 头删 查找 位置前插入  位置后插入  位置删除  位置后删除  链表销毁 总结         前边关于链表的基础如果已经理解透彻,那么接下来就是对链表各功能的实现,同时也希望大家能把这部分内容熟练于心,这部分内容对有关链表部分的刷题很有帮助。废话

    2024年02月14日
    浏览(60)
  • Python基础数据结构入门必读指南

    作者主页:涛哥聊Python 个人网站:涛哥聊Python 大家好,我是涛哥,今天为大家分享的是Python中常见的数据结构。 含义:数组是一种有序的数据结构,其中的元素可以按照索引来访问。数组的大小通常是固定的,一旦创建就不能更改。 基本操作: 含义:列表是Python中内置的

    2024年02月07日
    浏览(55)
  • 数据结构入门指南:带头双向循环链表

    目录 文章目录 前言 1.结构与优势 2.链表实现       2.1 定义链表 2.2 创建头节点 2.3 尾插 2.4 输出链表 2.5 尾删 2.6 头插 2.7头删 2.8 节点个数 2.9 查找 2.10 位置插入 2.11 位置删除 2.12 销毁链表  3. 源码 总结         链表一共有8种结构,但最常用的就是无头单向链表、和带头

    2024年02月14日
    浏览(51)
  • 【数据结构入门指南】二叉树顺序结构: 堆及实现(全程配图,非常经典)

    普通的二叉树是不适合用数组来存储的,因为可能会存在大量的空间浪费。 而完全二叉树更适合使用顺序结构存储。   现实中我们通常把堆(一种二叉树)使用顺序结构的数组来存储 ,需要注意的是这里的堆和操作系统虚拟进程地址空间中的堆是两回事,一个是数据结构,一

    2024年02月12日
    浏览(38)
  • 【数据结构入门指南】二叉树链式结构的实现(保姆级代码思路解读,非常经典)

    其他数据结构不同,二叉树的增删查改接口实现的意义不大(后续搜索树的增删查改才有意义)。普通初阶二叉树更重要的是学习控制结构,为后续的AVL树、红黑树等高级数据结构打下基础。同时大部分OJ题也出在此处。 所谓二叉树遍历(Traversal)是按照某种特定的规则,依次

    2024年02月11日
    浏览(46)
  • 【Java--数据结构】提升你的编程段位:泛型入门指南,一看就会!

    泛型是一种编程概念,它允许我们编写可以适用于多种数据类型的代码。通过使用泛型,我们可以在编译时期将具体的 数据类型作为参数 传递给代码,从而实现代码 的复用和灵活性 。 在传统的编程中,我们通常需要为不同的数据类型编写不同的代码,这样会导致代码冗余

    2024年04月26日
    浏览(64)
  • Midjourney新手入门指南

    我们来看一下百度百科的回复 是不是有点蒙,没关系,一句话概括:用描述来生成图像的AI工具。 你可能又有一门了,discord是什么?为什么要下载它?我们来看看百度百科 原因:Midjouney 没有自己的客户端,它是搭载在Discord上。 Discord 简单来说,就是一个聊天应用。

    2024年02月10日
    浏览(72)
  • PyCharm新手入门指南

    安装好Pycharm后,就可以开始编写第一个函数:Hello World啦~我们就先来学习一些基本的操作,主要包含新建Python文件,运行代码,查看结果等等。 文章主要包含五个部分: 一、界面介绍 主要分为菜单栏、项目目录、编辑区域、终端区和运行/调试代码区域。 1、菜单栏:一些新

    2024年02月13日
    浏览(55)
  • 什么是智能合约?新手入门指南

    智能合约,也称为数字合约,在计算机网络中使用 区块链技术来履行预编程的合约 当合同的条件得到满足时,智能合同就会执行,例如向合同的一方发送付款。 智能合约之所以具有吸引力有多种原因: 不信任。 由于智能合约及其条款已经预先约定,智能合约可以通过区块

    2023年04月08日
    浏览(47)

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

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

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

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

二维码1

领取红包

二维码2

领红包