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

目录

  • 前言
  • 一、循环链表
    • 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是否小于1return ERROR;if (p == *R)			//p==R ,条件成立有两种情况,第一,i-1是否恰好等于尾指针指向;第二,i-1大于表长{ if (j != i - 1)    //如果i-1大于表长,返回ERRORreturn 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.mzph.cn/pingmian/41239.shtml

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

相关文章

c++习题05-斐波那契数列

目录 一&#xff0c;问题 二&#xff0c;思路 三&#xff0c;代码 一&#xff0c;问题 二&#xff0c;思路 根据题目&#xff0c;可以自己列出斐波那契数列&#xff08;前四个&#xff09;如下&#xff1a; 通过列出来的值&#xff0c;可以发现&#xff0c;前两个都是1&…

设置和取消Excel“打开密码”的3种方法

在日常工作中&#xff0c;Excel文件中常常包含敏感数据。为了防止未经授权的访问&#xff0c;给Excel文件设置打开密码是一个非常有效的方法。下面分享3种设置Excel打开密码的方法&#xff0c;以及如何取消这些密码。 先来看看设置Excel打开密码的3种方法。 方法一&#xff1…

hnust 1815: 算法10-6~10-8:快速排序

hnust 1815: 算法10-6~10-8&#xff1a;快速排序 题目描述 快速排序是对起泡排序的一种改进。它的基本思想是&#xff0c;通过一趟排序将待排序的记录分割成两个独立的部分&#xff0c;其中一部分记录的关键字均比另一部分的关键字小&#xff0c;在分成两个部分之后则可以分别…

【人工智能】--强化学习(2.0)

个人主页&#xff1a;欢迎来到 Papicatch的博客 课设专栏 &#xff1a;学生成绩管理系统 专业知识专栏&#xff1a; 专业知识 文章目录 &#x1f349;强化学习与有监督学习的区别 &#x1f348;数据特点 &#x1f348;学习目标 &#x1f348;反馈机制 &#x1f348;策略…

打赢网络免疫升级战!看聚铭铭察高级威胁检测系统如何重塑网络安全防线

在信息洪流的今天&#xff0c;企业如航行于暗礁密布的数字海洋&#xff0c;面对的不仅仅是已知的病毒与漏洞&#xff0c;更有高级威胁这股暗流&#xff0c;悄无声息地侵蚀着网络的肌理。常规的安全措施&#xff0c;犹如常规体检&#xff0c;虽能捕捉表面的异常&#xff0c;却难…

【论文速读】|FuzzAug:探索模糊测试作为神经网络测试生成的数据增强

本次分享论文&#xff1a;FuzzAug: Exploring Fuzzing as Data Augmentation for Neural Test Generation 基本信息 原文作者&#xff1a;Yifeng He, Jicheng Wang, Yuyang Rong, Hao Chen 作者单位&#xff1a;University of California, Davis 关键词&#xff1a;软件测试…

使用随机生成的随机数过程中,保存数据到数组中 出现很多null

如果 randomId 是一个较大的数字&#xff0c;那么会在 temp 数组中留下很多空位。可能会导致很多 null 值。将 temp 从数组改为对象&#xff0c;以避免稀疏数组的问题。 稀疏数组&#xff1a;当一个数组中大部分元素为0&#xff0c;或者为同一值&#xff08;也就是说可以不是0…

【鸿蒙学习笔记】Image迭代完备

Image Image($r(app.media.zhibo)).width(96) // 图片宽度.height(96) // 图片高度.borderRadius(12) // 图片圆曲度.objectFit(ImageFit.Fill) // 不明objectFit Column({ space: 20 }) {Row() {Image($r(app.media.startIcon)).width(66).height(66).borderRadius(12)}.bac…

结合现货黄金mt4平台 谈谈止损的使用

现在我们做现货黄金交易都可以通过MT4平台来实现从入场到出场的全程操作。所以利用这种网上交易平台&#xff0c;我们能更加好地做止损&#xff0c;下面我们就来讨论一下基于现货黄金MT4平台的止损技巧。 要在现货黄金MT4平台中做好止损&#xff0c;首先我们要确定风险的口子是…

160行代码实现代码雨效果

效果 序言 很喜欢黑客帝国里面那种代码雨的效果&#xff0c;为了锻炼自己的特效编写能力就尝试了一下&#xff0c;花了一下午写出来了。有需要的小伙伴拿去参考. 代码 package com.zgh.myapplication;import android.content.Context; import android.graphics.Canvas; impo…

File类常用构造方法及方法详解

File类是对文件或者目录的一系列操作。如文件和目录的创建、检查、删除、路径获取等。现介绍下常用构造方法和方法。 一、构造方法。 File类提供了多个构造方法来创建File对象&#xff0c;以表示文件或目录。 1. File(String pathname) 通过指定文件路径名创建File对象。 参…

Linux命令大全(面试必备)

前两节有说Git命令&#xff0c;反馈还不错&#xff0c;看来大家对这些必备的命令还挺感兴趣哈&#xff0c;这节就罗列一些Linux必须掌握的命令。 干货满满哦&#xff0c;直接发车... 一、常用的基本命令 1、关机开机 关机 shutdown-h now 立刻关机shutdown-h 3 3分钟后…

文件操作及部分文件函数的介绍学习(上)

目录 前言 1.为什么要要使用文件&#xff1f; 2.什么是文件&#xff1f; 2.1程序文件 2.2数据文件 2.3文件名 4.文件的打开和关闭 4.1 流和标准流 4.1.1流 4.1.2标准流 4.2文件指针 4.3文件的打开和关闭 结语 前言 Hello&#xff0c;亲爱的小伙伴们&#xff0c;作…

Dungeonborne卡顿怎么办 快速解决Dungeonborne卡顿问题

随着Dungeonborne游戏剧情的深入&#xff0c;玩家将逐渐解锁更多的地图和副本&#xff0c;每个区域都有其独特的生态和敌人。在探索的过程中&#xff0c;玩家不仅可以获得强大的装备和道具&#xff0c;还能结识到志同道合的伙伴&#xff0c;共同面对更强大的敌人。不过也有玩家…

新加坡很火的slots游戏代投Facebook广告新流量趋势

新加坡很火的slots游戏代投Facebook广告新流量趋势 在新加坡这片充满活力的土地上&#xff0c;Slots游戏以其独特的魅力和吸引力&#xff0c;迅速成为了许多玩家的心头好。而Facebook&#xff0c;作为全球最大的社交媒体平台之一&#xff0c;为Slots游戏的推广提供了得天独厚的…

三、数据库系统(考点篇)

1、三级模式一两级映像 内模式&#xff1a;管理如何存储物理的 数据 &#xff0c;对数据的存储方式、优化、存放等。 模式&#xff1a;又称为概念模式&#xff0c; 就是我们通常使用的表这个级别 &#xff0c;根据应用、需求将物理数据划分成一 张张表。 外模式&#xff1a;…

【算法:贪心】:贪心算法介绍+基础题(四个步骤);柠檬水找零(交换论证法)

&#x1f381;个人主页&#xff1a;我们的五年 &#x1f50d;系列专栏&#xff1a;C课程学习 &#x1f389;欢迎大家点赞&#x1f44d;评论&#x1f4dd;收藏⭐文章 前言&#xff1a; 暑假马上就要留校学习算法了&#xff0c;现在先学习一下基本的算法打打基础。本篇要讲的是…

深入浅出 LangChain 与智能 Agent:构建下一代 AI 助手

我们小时候都玩过乐高积木。通过堆砌各种颜色和形状的积木&#xff0c;我们可以构建出城堡、飞机、甚至整个城市。现在&#xff0c;想象一下如果有一个数字世界的乐高&#xff0c;我们可以用这样的“积木”来构建智能程序&#xff0c;这些程序能够阅读、理解和撰写文本&#xf…

基于DMAIC降低气缸体水套芯磕碰伤率

在制造业的激烈竞争中&#xff0c;产品质量的提升一直是企业追求的目标。气缸体作为汽车发动机的核心部件&#xff0c;其生产过程中的质量控制尤为重要。今天&#xff0c;深圳天行健企业管理咨询公司就来分享一下如何运用DMAIC&#xff08;定义、测量、分析、改进、控制&#x…

基于java+springboot+vue实现的电影院购票系统(文末源码+Lw)274

摘 要 传统办法管理信息首先需要花费的时间比较多&#xff0c;其次数据出错率比较高&#xff0c;而且对错误的数据进行更改也比较困难&#xff0c;最后&#xff0c;检索数据费事费力。因此&#xff0c;在计算机上安装电影院购票系统软件来发挥其高效地信息处理的作用&#xf…