带头双向循环链表及链表总结

news2024/10/6 10:40:17

1、链表种类大全

1、链表严格来说可能用2*2*2=8种结构,从是否带头,是否循环,是否双向三个角度区分。

2、无头单向循环链表一般不会在实际运用中直接存储数据,而会作为某些更复杂结构的一个子结构,毕竟它只在头插、头删时具有效率上的优势。

3、带哨兵卫的头有利于解决尾插时多种讨论的复杂情况。

双向有利于insert、erase的实现,这两个函数涉及到对pos位置结点的前一个结点的操作,而双向链表由于存放了prev指针,可以轻松找到前一个结点(不用为了找前一个结点而再次遍历),从而完成相关功能。

循环有利于直接通过phead->prev找到tail,不用遍历,提高尾部操作的效率。

2、接口函数

//打印
void LTPrint(LTNode* phead);
//初始化
LTNode* LTInit();
//销毁
void LTDestroy(LTNode* phead);
//尾插
void LTPushBack(LTNode* phead,LTDataType x);
//尾删
void LTPopBack(LTNode* phead);
//头插
void LTPustFront(LTNode* phead,LTDataType x);
//头删
void LTPopFront(LTNode* phead);
//查找链表中某个值的位置
LTNode* LTFind(LTNode* phead,LTDataType x);
//pos位置之前插入
void LTInsert(LTNode* pos, LTDataType x);
//pos位置删除
void LTErase(LTNode* pos);

结点的创建

typedef int LTDataType;

typedef struct LTNode
{
	struct LTNode* prev;
	struct LTNode* next;
	LTDataType data;

}LTNode;

与之前相同,以int类型作为链表存储的数据类型。

3、初始化

在之前的无头单向非循环链表中,我们用一个plist指针指向链表,创建时只需要将plist置空即可,操作简单,不需要通过函数实现。

在顺序表的实现中,要考虑到sz(顺序表当前存储的数据个数),capacity(顺序表当前容量),还有一个指向顺序表的初始指针(避免使用数组导致的一些缺点),过程较复杂需要通过函数。

在带头双向循环链表中,由于需要创建一个哨兵卫的头结点,且为循环结构,next,prev都指向tail,无元素时tail为自己,可以通过init函数来实现。 

//初始化
LTNode* LTInit()
{
	//初始化时创建一个哨兵卫头结点
	LTNode* phead = (LTNode*)malloc(sizeof(LTNode));
	if (NULL == phead)
	{
		perror("malloc fail");
		return NULL;
	}
	phead->next = phead->prev = phead;
	phead->data = -1;

	return phead;

}

phead的data暂且设置一个-1,其它值也可以。

4、打印

//打印
void LTPrint(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	printf("<head>");
	while (cur != phead)
	{
		printf("%d<=>", cur->data);
		cur = cur->next;
	}
	printf("\n");
}

phead一定不为NULL,因此可以assert(phead)防止使用者误传入NULL。

phead头结点不为实际值,因此从phead->next开始遍历,cur只要不为phead的都要进入循环完成打印。

5、尾部操作

1、创建新结点

LTNode* BuyLTNode(LTDataType x)
{
	LTNode* newnode = (LTNode*)malloc(sizeof(LTNode));
	if (NULL == newnode)
	{
		perror("malloc fail");
		return NULL;
	}

	newnode->data = x;
	newnode->next = newnode->prev = NULL;
	return newnode;

}

插入操作前,需要malloc一个新结点。为防止野指针,在创建过程中就将其next和prev置为空

2、判断链表是否为空

删除操作前,需要判断链表是否为空。

//判断链表原来是否为空的函数
bool IsEmpty(LTNode* phead)
{
	return phead->next == phead;
}

这里采用bool值,返回值为true(非0),和false(0)两种,需要包含stdbool.h头文件,这里的phead->next如果等于phead,即链表中只有哨兵卫头结点,即为空,返回为1(非0),代表链表的状态为空

3、尾插

//尾插
void LTPushBack(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
	if (NULL == newnode)
	{
		perror("BuyLTNode fail");
		return;
	}

	//按照之前的思路,尾插需要先找尾
	// 这里phead的prev直接找尾   需要4步指针操作
	LTNode* tail = phead->prev;
	tail->next = newnode;
	newnode->prev = tail;
	phead->prev = newnode;
	newnode->next = phead;

}

先利用phead->prev找到原来的尾结点tail,然后创建新的尾结点,随后通过4步指针操作建立newnode与tail和phead的联系。(不用遍历找尾,提高了效率)

 

 

带哨兵卫的头结点的优势,不用讨论原链表为空的情况。 

4、尾删

//尾删
void LTPopBack(LTNode* phead)
{
	assert(phead);
	//先判断原来是否为空
	assert(!IsEmpty(phead));
	LTNode* tail = phead->prev;
	LTNode* prev = tail->prev;
	free(tail);
	tail = NULL;
	phead->prev = prev;
	prev->next = phead; 


}

assert内为!IsEmpty,即为空时报错。

分别用tail和prev标记要删除的尾结点以及前一个结点,free掉tail,利用两个指针建立prev与phead的联系,使其成为新尾。

 

 

 多删除时assert报错

6、头部操作

1、头插

//头插
void LTPustFront(LTNode* phead, LTDataType x)
{
	assert(phead);
	LTNode* newnode = BuyLTNode(x);
	if (NULL == newnode)
	{
		perror("BuyLTNode fail");
		return;
	}

	newnode->next = phead->next;
	phead->next->prev = newnode;
	newnode->prev = phead;
	phead->next = newnode;

}

通过4个指针建立newnode与phead、phead->next的联系,要注意顺序,先建立与phead->next

的,否则其会被改变无法找到。

当然,在开始时新建立一个next指针指向phead->next的结点也可以。

 

2、头删


//头删
void LTPopFront(LTNode* phead)
{
	assert(phead);
	assert(!IsEmpty(phead));
	LTNode* first = phead->next;

	phead->next = first->next;
	first->next->prev = phead;
	free(first);
	first = NULL;

}

先利用first标记要删除的头结点,然后用两个指针建立phead与first的next结点之间的关系,最后free掉first。

头部操作本身就是链表的优势,因此仍不需要遍历

 7、查找及指定位置pos操作

1、查找函数

//查找链表中某个值的位置
LTNode* LTFind(LTNode* phead,LTDataType x)
{
	assert(phead);
	assert(!IsEmpty(phead));

	LTNode* pos = phead->next;

	while (pos != phead)
	{
		if (pos->data == x)
		{
			return pos;
		}
		pos = pos->next;
	}
	printf("没找到\n");
	exit(0);
}

从phead->next的位置开始遍历,找到了则用pos返回该结点的地址,使用者可以用一个ret指针在外部接收,找不到则终止程序。查找前先确认链表不为空。

通过调试观察到,ret拿到了值为3的结点的地址 

还可以继续展开,观察带头双向循环链表的内部结构,是怎样完成循环的。 

2、insert指定位置前插入

//pos位置之前插入
void LTInsert(LTNode* pos, LTDataType x)
{
	assert(pos);
	LTNode* newnode = BuyLTNode(x);
	LTNode* prevptr = pos->prev;
	prevptr->next = newnode;
	newnode->next = pos;
	newnode->prev = prevptr;
	pos->prev = newnode;

}

先用prevptr找到pos的前一个结点,然后利用4个指针建立newnode与这两个结点的关系,完成中间位置插入。

可以用pos->prev指针直接找到前一个结点,不需要查找外的额外遍历,提高了效率。 

 

3、erase指定位置删除

//pos位置删除
void LTErase(LTNode* pos)
{
	assert(pos);

	//LTNode* prevpos = pos->prev;
	//LTNode* nextpos = pos->next;

	//prevpos->next = nextpos;
	//nextpos->prev = prevpos;

	//free(pos);
	//pos = NULL;

	pos->prev->next = pos->next;
	pos->next->prev = pos->prev;
	free(pos);
	pos = NULL;
}

同样找到pos前一个位置的结点,连接pos前一个与后一个结点,然后free掉pos

 

4、优化头尾操作

只要有了insert和erase两个函数,就可以轻易完成头尾的4个操作函数,只需要复用上述两个即可

例如在pos前插入一个节点,实际效果为尾插。

insert(pos->next)效果为头插

erase(phead->next)为头删

erase(phead->prev)为尾删

 

8、链表的销毁

//销毁
void LTDestroy(LTNode* phead)
{
	assert(phead);
	LTNode* cur = phead->next;
	while (cur != phead)
	{
		LTNode* next = cur->next;
		free(cur);
		cur = next;
	}
	free(phead);
	printf("销毁成功\n");
}

从phead->next位置开始遍历销毁即可,最后使用者在外部对创建的链表手动置空

 掌握了以上操作,20分钟写一个链表不是难事

目录

1、链表种类大全

2、接口函数

3、初始化

4、打印

5、尾部操作

1、创建新结点

2、判断链表是否为空

3、尾插

4、尾删

6、头部操作

1、头插

2、头删

 7、查找及指定位置pos操作

1、查找函数

2、insert指定位置前插入

3、erase指定位置删除

4、优化头尾操作

8、链表的销毁


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

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

相关文章

【数据结构之二叉树】——二叉树的概念及结构,特殊的二叉树和二叉树性质

文章目录一、二叉树的概念及结构1.概念2.现实中的二叉树3. 特殊的二叉树&#xff1a;3.二叉树的性质二、二叉树练习题总结一、二叉树的概念及结构 1.概念 一棵二叉树是结点的一个有限集合&#xff0c;该集合: 或者为空由一个根节点加上两棵别称为左子树和右子树的二叉树组成…

线性表的链式表示

文章目录1.单链表1.1单链表的表示1.1.1构建 带头结点的单链表1.2基本操作1.2.1 头插法1.2.2 尾插法1.2.3 按序号查找结点1.2.4 按值查找表结点1.2.5 插入结点操作扩展&#xff1a;前插操作1.2.6 删除结点操作扩展&#xff1a;删除结点*p1.2.7 求表长操作2.双链表2.1 双链表的表…

JVM相关知识

JVM类加载过程类什么时候被加载什么情况下会发生栈内存溢出JVM内存模型常量池回收方法区垃圾回收流程圾收集算法分代收集理论标记-清除算法标记-复制算法标记-整理算法类加载过程 加载–验证–准备–解析–初始化–使用–卸载 ​ 加载&#xff1a;通过全类名获取类的二进制流…

【C++】非类型的模板参数,特化

目录 1.类型模板参数和非类型模板参数 2.特化 3. 模板的分离编译 4.模板的优缺点 1.类型模板参数和非类型模板参数 之前写模板传的都是类型——类型模板参数 现在想定义两个静态数组&#xff0c;数组长度不同&#xff0c;就可以用模板参数传数值而不是传类型 非类型模板…

Docker与微服务实战2022

基础篇(零基小白)1.Docker简介1.1 是什么问题&#xff1a;为什么会有docker出现&#xff1f;您要如何确保应用能够在这些环境中运行和通过质量检测&#xff1f;并且在部署过程中不出现令人头疼的版本、配置问题&#xff0c;也无需重新编写代码和进行故障修复&#xff1f; 答案就…

Android源码分析 - View的触摸事件分发

0. 相关分享 Android源码分析 - InputManagerService与触摸事件 1. 接收Input系统发送来的事件 时序图源&#xff1a;稀土掘金 在注册Window的时候&#xff0c;来到ViewRootImpl&#xff0c;其中不仅发起窗口注册&#xff0c;还开启了输入事件的监听&#xff1a; //ViewRoo…

nuxt3使用总结

目录 背景 安装 项目配置 路由 Tailwindcss引入 全局样式配置 css预处理器 安装 Tailwindcss 项目的配置 部署上线 seo优化 背景 新入职了一家公司&#xff0c;刚进入公司第一个需求就是先做一个公司的官网&#xff0c;需要使用vue写&#xff0c;作为祖师爷的粉丝…

Java 电话号码的组合

电话号码的字母组合中等给定一个仅包含数字 2-9 的字符串&#xff0c;返回所有它能表示的字母组合。答案可以按 任意顺序 返回。给出数字到字母的映射如下&#xff08;与电话按键相同&#xff09;。注意 1 不对应任何字母。示例 1&#xff1a;输入&#xff1a;digits "23…

案例学习--016 消息队列作用和意义

简介MQ全称为Message Queue, 是一种分布式应用程序的的通信方法&#xff0c;它是消费-生产者模型的一个典型的代表&#xff0c;producer往消息队列中不断写入消息&#xff0c;而另一端consumer则可以读取或者订阅队列中的消息。主要产品有&#xff1a;ActiveMQ、RocketMQ、Rabb…

【RV1126】RKMedia模块简介

文章目录简介源码与编译rkmedia log等级配置目录参考文档&#xff1a;【Rockchip RKMedia Development Guide】rkmedia的手册在sdk目录下/docs/RV1126_RV1109/Multimedia rkmedia的代码在sdk目录下/external/rkmedia rkmedia的demo在sdk目录下/external/rkmedia/examples&…

antlr4-maven-plugin简单学习

1. 序言 antlr4-maven-plugin的官方介绍为&#xff1a; The ANTLR 4 plugin for Maven can generate parsers for any number of grammars in your project.博客《 mac上的Antlr4环境搭建》&#xff0c;有介绍如何通过antlr4-maven-plugin实现.g4文件的编译 这里将介绍antlr4-…

弹性存储-对象存储OSS部分

对象存储介绍 对象存储&#xff08;object storage service&#xff0c;简称oss&#xff09;&#xff0c;具备与平台无关的rest api接口&#xff0c;可提供99.9999999999%&#xff08;12个9&#xff09;的数据持久性和99.995%的数据可用性。 OSS优势 功能介绍 存储空间bucke…

秒杀高并发解决方案

秒杀高并发解决方案 1.秒杀/高并发方案-介绍 秒杀/高并发 其实主要解决两个问题&#xff0c;一个是并发读&#xff0c;一个是并发写并发读的核心优化理念是尽量减少用户到 DB 来"读"数据&#xff0c;或者让他们读更少的数据, 并 发写的处理原则也一样针对秒杀系统需…

麒麟服务器V10 版本 安装 Anaconda教程,也就是安装Python环境的教程(亲测有效)

目录1 Anaconda 是什么2 安装1 Anaconda 是什么 你可以理解为一个软件&#xff0c;和QQ一样的软件&#xff0c;你安装之后&#xff0c;里面就有naconda包括Conda、Python以及一大堆安装好的工具包&#xff0c;比如&#xff1a;numpy、pandas等 1&#xff09;包含conda&#x…

【C++学习】类和对象(上)

前言&#xff1a; 由于之前电脑“嗝屁”了&#xff0c;导致这之前一直没有更新博客&#xff0c;今天才拿到电脑&#xff0c;在这里说声抱歉。接下来就进入今天的学习&#xff0c;在之前我们已经对【C】进行了初步的认识&#xff0c;有了之前的知识铺垫&#xff0c;今天我们将来…

初识BFC

初识BFC 先说如何开启BFC&#xff1a; 1.设置display属性&#xff1a;inline-block&#xff0c;flex&#xff0c;grid 2.设置定位属性&#xff1a;absolute&#xff0c;fixed 3.设置overflow属性&#xff1a;hidden&#xff0c;auto&#xff0c;scroll 4.设置浮动&#xf…

英雄算法学习路线

文章目录零、自我介绍一、关于拜师二、关于编程语言三、算法学习路线1、算法集训1&#xff09;九日集训2&#xff09;每月算法集训2、算法专栏3、算法总包四、英雄算法联盟1、英雄算法联盟是什么&#xff1f;2、如何加入英雄算法联盟&#xff1f;3、为何会有英雄算法联盟&#…

Linux系统安装mysql(rpm版)

目录 Linux系统安装mysql&#xff08;rpm版&#xff09; 1、检测当前系统中是否安装MySQL数据库 2、将mysql安装包上传到Linux并解压 3、按照顺序安装rpm软件包 4、启动mysql 5、设置开机自启 6、查看已启动的服务 7、查看临时密码 8、登录mysql&#xff0c;输入临时密…

C++ STL学习之【vector的使用】

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f38a;每篇一句&#xff1a; 图片来源 The power of imagination makes us infinite. 想象力的力量使我们无限。 文章目录&#x1f4d8;前言&#x1f4d8;正文1、默认成员函数1.1、默认构造…

STM32之SPI

SPISPI介绍SPI是串行外设接口(Serial Peripherallnterface)的缩写&#xff0c;是一种高速的&#xff0c;全双工&#xff0c;同步的通信总线&#xff0c;并且在芯片的管脚上只占用四根线&#xff0c;节约了芯片的管脚&#xff0c;同时为PCB的布局上节省空间&#xff0c;提供方便…