数据结构-循环链表和双向链表

news2024/10/6 0:29:45

目录

  • 前言
  • 一、循环链表
    • 1.1 循环链表的介绍
    • 1.2 循环链表的实现
  • 二、双向链表
    • 2.1 双向链表的介绍
    • 2.2 双向链表的实现
  • 三、循环双链表
  • 总结

前言

本篇文章介绍数据结构中的循环链表和双向链表

一、循环链表

1.1 循环链表的介绍

将单链表的形式稍作改变,单链表的最后一个结点指向第一个结点
对第一个结点概念的说明:
对于不带头结点的链表,第一个结点指首元结点(如图1.1所示)
在这里插入图片描述

图1.1 不带头结点

对于带头结点的链表,第一个结点指头结点(如图1.2所示)
在这里插入图片描述

图1.2 带头结点

本篇文章以带头结点的循环链表为研究对象
与单链表相比,循环链表没有增加新的存储空间,但是,从循环链表中任一结点出发,都能访问到所有结点。
带头结点的循环链表又有两种表示方式,分别是
头指针表示循环链表:头指针指向头结点(如图1.3所示)
在这里插入图片描述

图1.3 头指针表示

头指针表示循环单链表 { 找 a 1 的时间复杂度 O ( 1 ) 找 a n 的时间复杂度 O ( n ) 头指针表示循环单链表 \begin{cases} 找a_1的时间复杂度O(1) \\ 找a_n的时间复杂度O(n) \end{cases} 头指针表示循环单链表{a1的时间复杂度O(1)an的时间复杂度O(n)

尾指针表示循环链表:尾指针指向最后一个结点(如图1.4所示)
在这里插入图片描述

图1.4 尾指针表示

尾指针表示循环单链表 { a 1 的存储位置: R → n e x t → n e x t a n 的存储位置: R 找 a 1 和 a n 的时间复杂度都为 O ( 1 ) 尾指针表示循环单链表 \begin{cases} a_1的存储位置:R \rightarrow next \rightarrow next \\ a_n的存储位置:R \\ 找a_1和a_n的时间复杂度都为O(1) \end{cases} 尾指针表示循环单链表 a1的存储位置:Rnextnextan的存储位置:Ra1an的时间复杂度都为O(1)

1.2 循环链表的实现

这里使用带头结点的循环链表,并且使用尾指针表示循环链表。
循环链表的定义和表示

//定义返回值常量
#define SUCCESS 1
#define ERROR 0


//假设数据元素类型为char
typedef char ElemType;

//定义结点类型
struct Node;				  	
typedef struct Node* PNode;   //假设作为结点指针类型
struct Node {
	ElemType data;   //数据域
	PNode next;		//指针域
};

typedef struct Node* LinkList;  //假设作为单链表类型

  1. 创建空表
    step1 使用malloc()函数创建一个sizeof(struct Node)大小的空间作为头结点
    step2 将头结点的指针域指向自身,表示一个空表

    //4.1 创建一个空表,返回头指针
    LinkList createNullList_loopLink(void)
    {
    	LinkList linkList = (LinkList)malloc(sizeof(struct Node));
    	if (NULL == linkList)
    	{
    		printf("malloc fail!\n");
    		return NULL;
    	}
    	linkList->next = linkList;  //头结点的指针域指向自身表示空表
    	return linkList;
    }
    
  2. 销毁循环链表
    step1 首先从头结点开始销毁
    step2 最后销毁尾指针指向的结点

    //4.2 销毁一个循环单链表
    void destroyList_loopLink(LinkList* R)
    {
    	assert(R && *R);
    	PNode phead = (*R)->next;
    	while (phead != *R)        //销毁除了尾结点的其余结点
    	{
    		PNode q = phead->next;
    		free(phead);
    		phead = q;
    	}
    	//销毁尾结点
    	free(*R);
    	*R = NULL;
    }
    
  3. 循环链表的插入
    step1 查找 a i − 1 a_{i-1} ai1的存储位置p
    step2 使用flag标记 a i − 1 a_{i-1} ai1的存储位置是否恰好为尾指针*R指向的位置
    step3 生成一个数据域为e的结点newNode
    step4 插入新结点,newNode指针域指向 a i a_i ai a i − 1 a_{i-1} ai1指针域指向newNode
    step5 根据flag的值改变R的指向,flag=1,*R = newNode

    //4.5 在第i个元素之前插入数据元素e
    int insertElem_loopLink(LinkList* R, int i, ElemType e)
    {
    	assert(R && *R);
    	PNode p = (*R)->next;          //头指针
    	int j = 0;
    	int flag = 0;				//用于标记位置i-1是否恰好等于尾指针指向的结点
    	while (p != *R && j < i - 1) //寻找第i-1位置的结点
    	{
    		p = p->next;
    		j++;
    	}
    	if (j > i - 1)           //判断i是否小于1
    		return ERROR;
    	if (p == *R)			//p==R ,条件成立有两种情况,第一,i-1是否恰好等于尾指针指向;第二,i-1大于表长
    	{ 
    		if (j != i - 1)    //如果i-1大于表长,返回ERROR
    			return ERROR;
    		else               //i-1位置恰好在尾指针指向的结点
    			flag = 1;
    	}
    	//新建结点
    	PNode newNode = (PNode)malloc(sizeof(struct Node));
    	if (NULL == newNode)
    	{
    		printf("malloc fail!\n");
    		return ERROR;
    	}
    	newNode->data = e;
    	newNode->next = p->next;
    	p->next = newNode;
    	if (flag)
    		*R = newNode;
    	return SUCCESS;
    }
    
  4. 循环链表的删除
    step1 查找 a i − 1 a_{i-1} ai1的存储位置 p p p
    step2 保存要删除的结点存储位置 q q q,即 q = p → n e x t q = p \rightarrow next q=pnext
    step3 使结点 a i − 1 a_{i-1} ai1的指针域指向 a i + 1 a_{i+1} ai+1,即 p → n e x t = q → n e x t p \rightarrow next = q \rightarrow next pnext=qnext
    step4 判断 q q q的指向是否与 ∗ R *R R的指向相同,若相同,则 ∗ R = p *R = p R=p
    step5 free(q),返回SUCCESS

    //4.6 删除第i个元素
    int deleteElem_loopLink(LinkList* R, int i)
    {
    	assert(R && *R);
    	PNode p = (*R)->next;
    	int j = 0;
    	while (p != *R && j < i - 1)
    	{
    		p = p->next;
    		j++;
    	}
    	if (p == *R || j > i - 1)
    		return ERROR;
    	PNode q = p->next;
    	p->next = q->next;
    	if (q == *R)  //判断删除结点是否为尾指针指向的结点
    		*R = p;
    	free(q);
    	return SUCCESS;
    }
    
  5. 循环链表的建立(头插法)
    step1 新建头结点phead并将phead的指针域指向自身
    step2 定义尾指针R,并指向phead
    step3 循环新建结点,插入头结点之后
    step4 判断插入的结点是否为第一个,若是,则R = newNode
    step5 最后返回R

    //4.7 循环单链表的建立(头插法)
    //带头结点
    //返回尾指针
    LinkList createLoopLinkList_head(ElemType* element, int length)
    {
    	assert(element);
    	//创建头结点
    	LinkList phead = (LinkList)malloc(sizeof(struct Node));
    	if (NULL == phead)
    	{
    		printf("malloc fail!\n");
    		return NULL;
    	}
    	phead->next = phead;
    	PNode R = phead;  //声明尾指针
    	int i = 0;
    	for (; i < length; i++)
    	{
    		//新建结点
    		PNode newNode = (PNode)malloc(sizeof(struct Node));
    		if (NULL == newNode)
    		{
    			printf("malloc fail!\n");
    			free(phead);
    			return NULL;
    		}
    		newNode->data = element[i];
    		newNode->next = phead->next;
    		phead->next = newNode;
    		if (newNode->next == phead)              //判断是否为第一个插入结点
    		{
    			R = newNode;
    		}
    	}
    	return R;
    }
    
  6. 合并两个循环链表
    step1 保存Ta的头指针
    step2 Ta的尾指针指向Tb的首元结点
    step3 释放Tb的头结点
    step4 Tb的尾指针指向Ta的头结点

    //4.8 合并两个循环单链表(Tb合并在Ta之后)
    //返回合并后的尾指针
    LinkList mergeLoopLinkList(LinkList Ta, LinkList Tb)
    {
    	if (NULL == Ta)
    		return Tb;
    	if (NULL == Tb)
    		return Ta;
    	PNode head_Ta = Ta->next;		//保存Ta的头指针
    	Ta->next = Tb->next->next;		//Ta尾指针指向Tb的首元结点
    	free(Tb->next);					//释放Ta的头结点
    	Tb->next = head_Ta;				//Tb的指针域指向Ta的头结点
    	return Tb;
    }
    

二、双向链表

2.1 双向链表的介绍

在双向链表中,每个结点除了数据域以外,还有两个指针域,一个指向其前驱结点,另一个指向其后继结点。每个结点的结构可以形象地描述如下:
在这里插入图片描述

图2.1 双向链表结点结构

为了方便处理,使用带头结点的双向链表
在这里插入图片描述

图2.2 带头结点的双向链表

2.2 双向链表的实现

双向链表的结构及其结构定义如下

//定义返回值常量
#define SUCCESS 1
#define ERROR 0
//定义双链表及其结点
//数据元素结构
typedef char ElemType;

struct DoubleNode;						//双链表结点
typedef struct DoubleNode* PDoubleNode; //结点指针类型
struct DoubleNode {						//双链表结点结构
	ElemType data;						//数据域
	PDoubleNode prior;					//前驱指针
	PDoubleNode rear;					//后继指针
};
typedef struct DoubleNode* DoubleLinkList; //假设为双链表类型
  1. 创建空表
    当头结点的两个指针域为NULL时,表示空表

    //5.1 创建空双向链表
    DoubleLinkList createNullList_doubleLink(void)
    {
    	//创建头结点
    	DoubleLinkList doubleLinkList = (DoubleLinkList)malloc(sizeof(struct DoubleNode));
    	if (NULL == doubleLinkList)
    	{
    		printf("malloc fail\n");
    		return NULL;
    	}
    	doubleLinkList->prior = NULL;
    	doubleLinkList->rear = NULL;
    	return doubleLinkList;
    }
    
  2. 获取第i个数据元素的存储位置

    //5.3 获取第i个数据元素的位置
    PDoubleNode locateElem_doubleLink(DoubleLinkList doubleLinkList, int i)
    {
    	assert(doubleLinkList);
    	PDoubleNode p = doubleLinkList->rear;
    	int j = 1;
    	while (p && j < i)
    	{
    		p = p->rear;
    		j++;
    	}
    	if (!p || j > i)   //判断i的合法性
    		return NULL;
    	return p;
    }
    
  3. 双向链表的插入
    step1 找到第i个位置的结点存储位置p
    step2 创建新结点 n e w N o d e newNode newNode n e w N o d e → p r i o r = p → p r i o r , p → p r i o r → r e a r = n e w N o d e , n e w N o d e → r e a r = p , p → p r i o r = n e w N o d e newNode \rightarrow prior = p \rightarrow prior,p \rightarrow prior \rightarrow rear=newNode,\\newNode \rightarrow rear = p,p \rightarrow prior = newNode newNodeprior=pprior,ppriorrear=newNode,newNoderear=p,pprior=newNode

    //5.4 双向链表的插入
    //在第i个位置前插入数据元素
    int insertElem_doubleLink(DoubleLinkList doubleLinkList, int i, ElemType e)
    {
    	assert(doubleLinkList);
    	PDoubleNode p = NULL;
    	p =	locateElem_doubleLink(doubleLinkList, i);
    	if (!p) return ERROR;
    	PDoubleNode newNode = (PDoubleNode)malloc(sizeof(struct DoubleNode));
    	if (NULL == newNode)
    	{
    		printf("malloc fail!\n");
    		return ERROR;
    	}
    	newNode->data = e;
    	newNode->prior = p->prior;    //新结点的前驱指向p的前驱
    	p->prior->rear = newNode;	  //p的前驱结点的后继指向新结点	
    	newNode->rear = p;			  	
    	p->prior = newNode;
    	return SUCCESS;
    }
    

在这里插入图片描述

图2.3 双向链表插入
  1. 双向链表的删除
    step1 找到第i个结点的存储位置p
    step2 p → p r i o r → r e a r = p → r e a r , p → r e a r → p r i o r = p → p r i o r p \rightarrow prior \rightarrow rear = p \rightarrow rear,p \rightarrow rear \rightarrow prior = p \rightarrow prior ppriorrear=prear,prearprior=pprior

    //5.5 双向链表的删除
    //删除第i个数据元素
    int deleteElem_doubleLink(DoubleLinkList doubleLinkList, int i)
    {
    	assert(doubleLinkList);
    	PDoubleNode p = NULL;
    	p = locateElem_doubleLink(doubleLinkList,i);
    	if (!p) return ERROR;
    	p->prior->rear = p->rear;
    	p->rear->prior = p->prior;
    	free(p);
    	return SUCCESS;
    }
    
  2. 双向链表的建立(尾插法)

    //5.6 双向链表的建立(尾插法)
    DoubleLinkList createDoubleLinkList_tail(ElemType* element, int length)
    {
    	assert(element);
    	//建立头结点
    	DoubleLinkList doubleLinkList = (DoubleLinkList)malloc(sizeof(struct DoubleNode));
    	if (NULL == doubleLinkList)
    	{
    		printf("malloc fail!\n");
    		return NULL;
    	}
    	doubleLinkList->prior = NULL;
    	doubleLinkList->rear = NULL;
    	PDoubleNode tail = doubleLinkList;  //定义尾指针
    	int i = 0;
    	for (i = 0; i < length; i++)
    	{
    		//创建数据元素结点
    		PDoubleNode newNode = (PDoubleNode)malloc(sizeof(struct DoubleNode));
    		if (NULL == newNode)
    		{
    			printf("malloc fail!\n");
    			free(doubleLinkList);
    			return NULL;
    		}
    		newNode->data = element[i];
    		newNode->rear = NULL;
    		newNode->prior = NULL;
    		tail->rear = newNode;
    		newNode->prior = tail;
    		tail = newNode;
    	}
    	return doubleLinkList;
    }
    

三、循环双链表

与循环单链表类型,在双向链表的基础上,使双向链表的第一个结点的前驱指向最后一个结点,最后一个结点的后继指向第一个结点,构成循环双链表。
在这里插入图片描述

图3.1 带头结点的循环双链表

循环双链表有一个性质,若 p p p为指向链表中某结点的指针,则总有
p → r e a r → p r i o r = p = p → p r i o r → r e a r p\rightarrow rear \rightarrow prior = p = p \rightarrow prior \rightarrow rear prearprior=p=ppriorrear

总结

完整代码:https://gitee.com/PYSpring/data-structure/tree/master

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

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

相关文章

vue3 使用JsMind的方法,JsMind节点点击事件,以及引入提示报错,无法找到模块“jsmind”的声明文件

最终结果&#xff1a; 一、使用&#xff1a;使用yarn或者npm 安装 yarn add jsmind npm install vue-jsmind 二、引入 两种方法&#xff1a;&#xff08;如果这样引入没问题按照这样引入&#xff09; import "jsmind/style/jsmind.css"; import JsMind from &quo…

LitelDE安装---附带每一步截图以及测试

LiteIDE LiteIDE 是一款专为Go语言开发而设计的开源、跨平台、轻量级集成开发环境&#xff08;IDE&#xff09;&#xff0c;基于 Qt 开发&#xff08;一个跨平台的 C 框架&#xff09;&#xff0c;支持 Windows、Linux 和 Mac OS X 平台。LiteIDE 的第一个版本发布于 2011 年 …

华为RH2288H V2服务器,远程端口安装Linux操作系统

1、管理口 每台服务器的管理口不一样的&#xff0c;假如我的管理IP地址为&#xff1a;192.168.111.201 使用网线&#xff0c;将管理口和自己电脑连接起来&#xff0c;自己ip地址设置成和管理ip同一网段。 使用 ie 浏览器&#xff0c;如果是Edge&#xff0c;必须在Internet Exp…

weiyang**2.部署

一、官方文档 一键部署可以在 同机 快速搭建WeBASE管理台环境&#xff0c;方便用户快速体验WeBASE管理平台。 一键部署会搭建&#xff1a;节点&#xff08;FISCO-BCOS 2.0&#xff09;、管理平台&#xff08;WeBASE-Web&#xff09;、节点管理子系统&#xff08;WeBASE-Node-…

20240630 每日AI必读资讯

&#x1f4da;全美TOP 5机器学习博士发帖吐槽&#xff1a;实验室H100数量为0&#xff01; - 普林斯顿、哈佛「GPU豪门」&#xff0c;手上的H100至少三四百块&#xff0c;然而绝大多数ML博士一块H100都用不上 - 年轻的研究者们纷纷自曝自己所在学校或公司的GPU情况&#xff1a…

MDA管理层讨论与分析内容信息披露情感分析数据(2010-2022年)

数据简介&#xff1a;MD&A通常是指管理层讨论与分析&#xff08;Management Discussion & Analysis&#xff09;&#xff0c;是上市公司年报中一个重要的部分&#xff0c;主要包含公司经营业绩的讨论&#xff0c;以及未来前景的预测等。MD&A可以帮助投资者更好地理…

Python数据分析-股票分析和可视化(深证指数)

一、内容简介 股市指数作为衡量股市整体表现的重要工具&#xff0c;不仅反映了市场的即时状态&#xff0c;也提供了经济健康状况的关键信号。在全球经济体系中&#xff0c;股市指数被广泛用于预测经济活动&#xff0c;评估投资环境&#xff0c;以及制定财政和货币政策。在中国…

Java教程之IO模式精讲,NIO+BIO

第一章 BIO、NIO、AIO介绍 背景 在java的软件设计开发中&#xff0c;通信架构是不可避免的&#xff0c;我们在进行不同系统或者不同进程之间的数据交互&#xff0c;或 者在高并发下的通信场景下都需要用到网络通信相关的技术&#xff0c;对于一些经验丰富的程序员来说&#x…

从0到1搭建Java开发环境(内涵超详细教程、软件、提供网盘链接直接一步到位!!!!)

软件部分 需要的软件为下面两个&#xff1a; IDEANavicat 需要的可以自行拿&#xff08;安装教程和软件&#xff09;&#xff1a; 链接&#xff1a;https://pan.baidu.com/s/1y3RoMt0ZapyJsj3P0DPaVA?pwdxr4p 提取码&#xff1a;xr4p 环境部分 需要的环境为以下几个&…

什么是自然语言处理(NLP)?详细解读文本分类、情感分析和机器翻译的核心技术

什么是自然语言处理&#xff1f; 自然语言处理&#xff08;Natural Language Processing&#xff0c;简称NLP&#xff09;是人工智能的一个重要分支&#xff0c;旨在让计算机理解、解释和生成人类的自然语言。打个比方&#xff0c;你和Siri对话&#xff0c;或使用谷歌翻译翻译一…

Rocketmq-集群部署(Master-Slave)

使用中间件版本:rocketmq-4.5.2环境介绍及角色划分 这里采用俩台机器做集群的搭建&#xff0c;172.0.0.1 以及 172.0.0.2 服务器172.0.0.1 做为a-master与b-slave。 服务器172.0.0.2 做为b-master与a-slave。 配置讲解图(主要说明区分点) 配置rocketmq环境变量&#xff0c;这里…

【嵌入式CLion】进阶调试——WSL下的Linux体验

说明&#xff1a; 1&#xff0c;这里所指的嵌入式其实是指嵌入式微控制器MCU&#xff0c;即单片机 2&#xff0c;万事开头难&#xff0c;本文目前提供了WSL工具链的搭建&#xff0c;后面会持续更新 一、启用RTOS集成 在搭建WSL工具链之前&#xff0c;先讲一下集成的RTOS功能&a…

产品是应该有生命力的

产品是应该有生命力的 在日新月异的商业环境中&#xff0c;产品被寄予厚望&#xff0c;不仅仅满足基本功能需求&#xff0c;而是要能够自我革新&#xff0c;适应市场和技术的快速变化&#xff0c;以及持续吸引并留住用户。 这种生命力体现在产品的迭代升级能力、对用户需求的精…

计算机毕业设计Flink+Hadoop广告推荐系统 广告预测 广告数据分析可视化 广告爬虫 大数据毕业设计 Spark Hive 深度学习 机器学

专业 小四号宋体 班级 小四号宋体 姓名 小四号宋体 学号 小四号宋体 指导教师 小四号宋体 题目 基于大数据的B站广告投放分析及可视化 &#xff08;1.内容包括&#xff1a;课题的来源及意义&#xff0c;国内外发展状况&#xff0c;本课题的研究目标、内容、方法、手…

Python Theano库:符号定义与自动微分的神奇魅力!

更多Python学习内容&#xff1a;ipengtao.com Theano是一个Python库&#xff0c;用于定义、优化和评估涉及多维数组的数学表达式。它是深度学习领域的早期先驱之一&#xff0c;广泛用于高性能计算和神经网络的研究与开发。本文将详细介绍Theano库的安装、主要功能、基本操作、高…

AI奥林匹克竞赛:Claude-3.5-Sonnet对决GPT-4o,谁是最聪明的AI?

目录 实验设置 评估对象 评估方法 结果与分析 针对学科的细粒度分析 GPT-4o vs. Claude-3.5-Sonnet GPT-4V vs. Gemini-1.5-Pro 结论 AI技术日新月异&#xff0c;Anthropic公司最新发布的Claude-3.5-Sonnet因在知识型推理、数学推理、编程任务及视觉推理等任务上设立新…

LabVIEW材料样本结构缺陷检测

本文介绍了一种基于LabVIEW的实验室振动特性分析测试装置&#xff0c;通过分析振动特性来检测结构缺陷。文章详细描述了具体案例、硬件型号、工作原理、软件功能以及注意事项。 硬件型号 振动传感器&#xff1a;PCB Piezotronics 352C33加速度计 数据采集卡&#xff1a;NI PXI…

笔灵AI写作:释放创意,提升写作效率的秘诀

内容为王&#xff0c;在内容创作的世界中尤为重要。然而&#xff0c;面对写作时常常感到无从下手&#xff1a;有时缺乏灵感&#xff0c;有时难以表达清楚自己的想法。AI写作助手的出现&#xff0c;为这些问题提供了创新的解决方案&#xff0c;极大地改变了内容创作的过程。 今…

从零开始三天学会微信小程序开发(三)

看到不少入门的小程序开发者不断的问重复性的问题&#xff0c;我们从实战角度开发了这个课程&#xff0c;希望能够帮助大家了解小程序开发。 课程分三天&#xff1a; 第一天&#xff1a;微信小程序开发入门第二天&#xff1a;给小程序接入云端数据第三天&#xff1a;完善我的…

STM32——使用TIM输出比较产生PWM波形控制舵机转角

一、输出比较简介&#xff1a; 只有高级定时器和通用寄存器才有输入捕获/输出比较电路&#xff0c;他们有四个CCR&#xff08;捕获/比较寄存器&#xff09;&#xff0c;共用一个CNT&#xff08;计数器&#xff09;&#xff0c;而输出比较功能是用来输出PWM波形的。 红圈部分…