【数据结构初阶】由浅入深学习链表

news2024/11/26 2:32:17

目录

前言

链表的概念及结构

 链表的分类

单链表的实现

接口实现

1.结构体

2.创建一个新结点

3.打印链表数据

4.尾插数据

5.尾删数据

6.头插数据

7.头删数据

8.任意位置删除

9.查找位置

10.pos之前插入

11.pos之后插入

12.释放内存

完整源码

总结


前言

在我们学习了顺序表之后,我们发现了顺序表有很多的不足之处,例如顺序表在空间不足之后需要扩容,扩容会对空间造成浪费,并且当需要头部或者中部操作数据时,顺序表必须进行挪动数据,挪动数据会造成浪费。

链表的概念及结构

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

链表就像一辆火车一样,一节车厢接着一节车厢,这每一节车厢都可以被看做是一个结点,每一个结点链接在一起就构成了链表。

链表中在每一个结点都是一个结构体,结构体中分为数据域和指着域,就是一个结点同时存储数据和下一个结点的地址,这样我们才能找到下一个结点。

 虽然我们在逻辑上看到一个结点是接着一个结点的,但是在物理上他们不一定是连续的,只是在结点中存储下一个结点的地址,所以才看上去是连续的。

 链表的分类

根据链表结构的不同,我们可以将链表分为八类,分别从以下三个方面进行区分。

1.带头或不带头

2.循环或不循环

3.双向或单向

我们这篇文章主要来探究不带头单向不循环链表,也就是我们最常见到的单链表,还有就是结构复杂但是效率更高的带头循环双向链表。

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

单链表的实现

接口实现

1.结构体

定义结构体来存储当前结点数据以及下一个结点的地址。

typedef int ElementType;

typedef struct SLTNode
{
	ElementType date;
	struct SLTNode* next;
}SLTNode;

2.创建一个新结点

使用malloc创建一个新结点,但是切记最后必须free掉。

SLTNode* BuyNewNode(ElementType x)
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newNode->date = x;
	newNode->next = NULL;

	return newNode;
}

3.打印链表数据

直接顺序打印,直到遍历到空为止。

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

4.尾插数据

当链表为空时,直接令头结点等于新结点就好,当链表不为空时,我们要通过遍历来找尾,当cur的next为空时找到尾,插入到尾的后边。

void SListPushBack(SLTNode** pphead, ElementType x)
{
	assert(pphead);
	SLTNode* newNode = BuyNewNode(x);

	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next!=NULL)
		{
			cur = cur->next;
		}
		cur->next = newNode;
	}
}

5.尾删数据

当只有一个结点时,我们将这个结点释放掉就好了,如果有多个结点,还是先来找尾,但是切记要保存上一个结点的地址,我们free掉尾结点后,还要将尾的上一个结点的next置为空,否则就会造成野指针。

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

6.头插数据

只需将新结点的next指向原本的头结点,再将头结点变为新结点。

void SListPushFront(SLTNode** pphead, ElementType x)
{
	SLTNode* newNode = BuyNewNode(x);

	newNode->next = (*pphead);
	*pphead = newNode;
}

7.头删数据

保存头结点的next,free头结点,将新的头指向保存的next。

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

8.任意位置删除

当pos就是头结点时,只需头删就好了,如果pos不是头,就逐一向后遍历,要记录pos位置的前一个结点,方便删除pos位置结点。

void SListDel(SLTNode** pphead,SLTNode* pos)
{
	assert(pphead && (*pphead));
	assert(pos);
	if ((*pphead)==pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}

9.查找位置

使用数据来查找,逐一向后遍历,如果相等,返回该结点。

SLTNode* SListFind(SLTNode** pphead,ElementType x)
{
	assert(pphead && *pphead);
	SLTNode* cur = *pphead;
	while (cur->date != x)
	{
		cur = cur->next;
	}
	return cur;
}

10.pos之前插入

在pos位置之前插入必须要保存上一个结点prev,当查找到pos位置时,只需要将新结点的next指向pos,prev的next指向新结点。

void SListInsert(SLTNode** pphead, SLTNode* pos, ElementType x)
{
	assert(pphead && pos);
	SLTNode* newNode = BuyNewNode(x);
	if ((*pphead)== pos)
	{
		newNode->next = (*pphead);
		*pphead = newNode;
	}
	else
	{
		SLTNode* cur = *pphead, * prve = NULL;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		newNode->next = pos;
		cur->next = newNode;
	}
}

11.pos之后插入

直接插入,但是要记得先连接后边再连接前边。

void SListInsertAfter(SLTNode** pphead, SLTNode* pos, ElementType x)
{
	assert(pphead && pos);
	SLTNode* newNode = BuyNewNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}

12.释放内存

void SListDestory(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
	printf("内存释放完毕\n");
}

完整源码

slist.h

#define _CRT_SECURE_NO_WARNINGS 1
#include<stdio.h>
#include<assert.h>
typedef int ElementType;

typedef struct SLTNode
{
	ElementType date;
	struct SLTNode* next;
}SLTNode;
//创建新节点
SLTNode* BuyNewNode(ElementType x);
//打印链表
void SListPrint(SLTNode* phead);
//尾插
void SListPushBack(SLTNode** pphead, ElementType x);
//尾删
void SListPopBack(SLTNode** pphead);
//头删
void SListPopFront(SLTNode** pphead);
//头插
void SListPushFront(SLTNode** pphead, ElementType x);
//pos位置删除
void SListDel(SLTNode** pphead,SLTNode* pos);
//查找位置
SLTNode* SListFind(SLTNode** pphead, ElementType x);
//之前插入数据
void SListInsert(SLTNode** pphead, SLTNode* pos, ElementType x);
//之后插入数据
void SListInsertAfter(SLTNode** pphead, SLTNode* pos, ElementType x);
//释放内存
void SListDestory(SLTNode** pphead);

slist.c

#define _CRT_SECURE_NO_WARNINGS 1
#include"slist.h"

SLTNode* BuyNewNode(ElementType x)
{
	SLTNode* newNode = (SLTNode*)malloc(sizeof(SLTNode));
	if (newNode == NULL)
	{
		perror("malloc");
		exit(-1);
	}
	newNode->date = x;
	newNode->next = NULL;

	return newNode;
}
void SListPrint(SLTNode* phead)
{
	SLTNode* cur = phead;
	while (cur)
	{
		printf("%d->", cur->date);
		cur = cur->next;
	}
	printf("NULL\n");
}
//尾插
void SListPushBack(SLTNode** pphead, ElementType x)
{
	assert(pphead);
	SLTNode* newNode = BuyNewNode(x);

	if (*pphead == NULL)
	{
		*pphead = newNode;
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next!=NULL)
		{
			cur = cur->next;
		}
		cur->next = newNode;
	}
}

//尾删
void SListPopBack(SLTNode** pphead)
{
	assert(*pphead);
	if ((*pphead)->next== NULL)
	{
		free(*pphead);
	}
	else
	{
		SLTNode* cur = *pphead, * prev = NULL;
		while (cur->next)
		{
			prev = cur;
			cur = cur->next;
		}
		free(cur);
		prev->next = NULL;
	}
}

//头插

void SListPushFront(SLTNode** pphead, ElementType x)
{
	SLTNode* newNode = BuyNewNode(x);

	newNode->next = (*pphead);
	*pphead = newNode;
}

//头删

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


//在任意位置删除

void SListDel(SLTNode** pphead,SLTNode* pos)
{
	assert(pphead && (*pphead));
	assert(pos);
	if ((*pphead)==pos)
	{
		SListPopFront(pphead);
	}
	else
	{
		SLTNode* cur = *pphead;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		cur->next = pos->next;
		free(pos);
	}
}

//查找位置
SLTNode* SListFind(SLTNode** pphead,ElementType x)
{
	assert(pphead && *pphead);
	SLTNode* cur = *pphead;
	while (cur->date != x)
	{
		cur = cur->next;
	}
	return cur;
}
//pos之前插入
void SListInsert(SLTNode** pphead, SLTNode* pos, ElementType x)
{
	assert(pphead && pos);
	SLTNode* newNode = BuyNewNode(x);
	if ((*pphead)== pos)
	{
		newNode->next = (*pphead);
		*pphead = newNode;
	}
	else
	{
		SLTNode* cur = *pphead, * prve = NULL;
		while (cur->next != pos)
		{
			cur = cur->next;
		}
		newNode->next = pos;
		cur->next = newNode;
	}
}


//pos之后插入

void SListInsertAfter(SLTNode** pphead, SLTNode* pos, ElementType x)
{
	assert(pphead && pos);
	SLTNode* newNode = BuyNewNode(x);
	newNode->next = pos->next;
	pos->next = newNode;
}

//释放内存
void SListDestory(SLTNode** pphead)
{
	assert(*pphead);
	SLTNode* cur = *pphead;
	while (cur)
	{
		SLTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	*pphead = NULL;
	printf("内存释放完毕\n");
}

总结

今天主要学习了单链表的原理以及实现,希望可以帮到大家。

 

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

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

相关文章

Java Web 实战 07 - 多线程基础之单例模式

大家好 , 这篇文章给大家带来的是单例模式 , 单例模式中分为懒汉模式和饿汉模式 , 懒汉模式是需要用的到的时候才去创建实例 , 而饿汉模式是程序一启动就立刻创建实例 , 在这其中还有很多其他问题需要我们去研究 推荐大家跳转到这里 , 观看效果更加 上一篇文章的链接我也贴在这…

1641_strchr函数的功能分析以及peek功能实现分析

全部学习汇总&#xff1a; GreyZhang/g_unix: some basic learning about unix operating system. (github.com) 继续分析shell例程代码&#xff0c;再次遇到了一个陌生的库函数strchr。 1. 从这里看&#xff0c;这个是一个库函数无疑了。 2. 这个函数&#xff0c;或者说这三个…

2个步骤就能批量给视频添加滚动字幕

现在很多小伙伴在剪辑视频的时候都会给自己的视频添加适配的字幕&#xff0c;但是有很多的视频想要添加一样的滚动字幕时&#xff0c;有一个能批量添加剪辑的工具非常重要&#xff0c;今天小编就给大家分享一个可以批量剪辑大量视频的工具&#xff0c;下面一起看看具体的操作步…

超导百年:物理学“圣杯”是如何诞生的?

最近科技圈流传的大新闻&#xff0c;大家都知道了吧&#xff1f;简单来说&#xff0c;美国物理学会的三月会议上&#xff0c;来自罗彻斯特大学的Ranga Dias宣布&#xff0c;他们团队在近环境压强下实现了室温超导。这个消息在中文互联网流传之后&#xff0c;很快就有了详细的解…

刷题(第三周)

目录 [CISCN2021 Quals]upload [羊城杯 2020]EasySer [网鼎杯 2020 青龙组]notes [SWPU2019]Web4 [Black Watch 入群题]Web [HFCTF2020]BabyUpload [CISCN2021 Quals]upload 打开界面以后&#xff0c;发现直接给出了源码 <?php if (!isset($_GET["ctf"]))…

网络工程师面试题(面试必看)(3)

作者简介:一名云计算网络运维人员、每天分享网络与运维的技术与干货。 座右铭:低头赶路,敬事如仪 个人主页:网络豆的主页​​​​​​ 前言 本系列将会提供网络工程师面试题,由多位专家整合出本系列面试题,包含上百家面试时的问题。面试必考率达到80%,本系列共86道题…

银行管理系统--课后程序(Python程序开发案例教程-黑马程序员编著-第7章-课后作业)

实例1&#xff1a;银行管理系统 从早期的钱庄到现如今的银行&#xff0c;金融行业在不断地变革&#xff1b;随着科技的发展、计算机的普及&#xff0c;计算机技术在金融行业得到了广泛的应用。银行管理系统是一个集开户、查询、取款、存款、转账、锁定、解锁、退出等一系列的功…

一文分析Linux v4l2框架

说明&#xff1a; Kernel版本&#xff1a;4.14 ARM64处理器&#xff0c;Contex-A53&#xff0c;双核 使用工具&#xff1a;Source Insight 3.5&#xff0c; Visio 1. 概述 V4L2(Video for Linux 2)&#xff1a;Linux内核中关于视频设备驱动的框架&#xff0c;对上向应用层提供…

Transformer-XL:打破序列长度限制的Transformer模型

❤️觉得内容不错的话&#xff0c;欢迎点赞收藏加关注&#x1f60a;&#x1f60a;&#x1f60a;&#xff0c;后续会继续输入更多优质内容❤️&#x1f449;有问题欢迎大家加关注私戳或者评论&#xff08;包括但不限于NLP算法相关&#xff0c;linux学习相关&#xff0c;读研读博…

Redis经典五种数据类型底层实现原理解析

目录总纲redis的k,v键值对新的三大类型五种经典数据类型redisObject结构图示结构讲解数据类型与数据结构关系图示string数据类型三大编码格式SDS详解代码结构为什么要重新设计源码解析三大编码格式hash数据类型ziplist和hashtable编码格式ziplist详解结构剖析ziplist的优势(为什…

TypeScript 基础学习之泛型和 extends 关键字

越来越多的团队开始使用 TS 写工程项目&#xff0c; TS 的优缺点也不在此赘述&#xff0c;相信大家都听的很多了。平时对 TS 说了解&#xff0c;仔细思考了解的也不深&#xff0c;借机重新看了 TS 文档&#xff0c;边学习边分享&#xff0c;提升对 TS 的认知的同时&#xff0c;…

Qt静态扫描(命令行操作)

Qt静态扫描&#xff08;命令行操作&#xff09; 前沿&#xff1a; 静态代码分析是指无需运行被测代码&#xff0c;通过词法分析、语法分析、控制流、数据流分析等技术对程序代码进行扫描&#xff0c;找出代码隐藏的错误和缺陷&#xff0c;如参数不匹配&#xff0c;有歧义的嵌…

Linux查看UTC时间

先了解一下几个时间概念。 GMT时间&#xff1a;Greenwich Mean Time&#xff0c;格林尼治平时&#xff0c;又称格林尼治平均时间或格林尼治标准时间。是指位于英国伦敦郊区的皇家格林尼治天文台的标准时间。 GMT时间存在较大误差&#xff0c;因此不再被作为标准时间使用。现在…

数据传输服务DTS的应用场景(阿里巴巴)

数据传输服务DTS的应用场景(阿里巴巴) 数据传输服务DTS&#xff08;Data Transmission Service&#xff09;支持数据迁移、数据订阅和数据实时同步功能&#xff0c;帮助您实现多种典型应用场景。 不停机迁移数据库 传输方式&#xff1a;数据迁移 为了保证数据的一致性&#…

【17】组合逻辑 - VL17/VL19/VL20 用3-8译码器 或 4选1多路选择器 实现逻辑函数

VL17 用3-8译码器实现全减器 【本题我的也是绝境】 因为把握到了题目的本质要求【用3-8译码器】来实现全减器。 其实我对全减器也是不大清楚,但是仿照对全加器的理解,全减器就是低位不够减来自低位的借位 和 本单元位不够减向后面一位索要的借位。如此而已,也没有很难理解…

Python3简单实现图像风格迁移

导语T_T之前似乎发过类似的文章&#xff0c;那时候是用Keras实现的&#xff0c;现在用的PyTorch&#xff0c;而且那时候发的内容感觉有些水&#xff0c;于是我决定。。。好吧我确实只是为了写点PyTorch练手然后顺便过来水一篇美文~~~利用Python实现图像风格的迁移&#xff01;&…

Python实现性能测试(locust)

一、安装locustpip install locust -- 安装&#xff08;在pycharm里面安装或cmd命令行安装都可&#xff09;locust -V -- 查看版本&#xff0c;显示了就证明安装成功了或者直接在Pycharm中安装locust:搜索locust并点击安装&#xff0c;其他的第三方包也可以通过这种方式二、loc…

JavaScript Math(算数)对象

Math&#xff08;算数&#xff09;对象的作用是&#xff1a;执行常见的算数任务。在线实例round()如何使用 round()。random()如何使用 random() 来返回 0 到 1 之间的随机数。max()如何使用 max() 来返回两个给定的数中的较大的数。&#xff08;在 ECMASCript v3 之前&#xf…

站外seo优化有用吗?值得投入时间和精力吗?

随着互联网的普及和竞争的激烈化&#xff0c;SEO&#xff08;Search Engine Optimization&#xff0c;搜索引擎优化&#xff09;已经成为各种网站推广的必备技能。 而站外SEO优化就是指通过在其他网站上增加链接和引用等方式&#xff0c;来提高自己网站的搜索引擎排名和曝光度…

【6G 新技术】6G数据面介绍

博主未授权任何人或组织机构转载博主任何原创文章&#xff0c;感谢各位对原创的支持&#xff01; 博主链接 本人就职于国际知名终端厂商&#xff0c;负责modem芯片研发。 在5G早期负责终端数据业务层、核心网相关的开发工作&#xff0c;目前牵头6G算力网络技术标准研究。 博客…