【玩转408数据结构】线性表——双链表、循环链表和静态链表(线性表的链式表示 下)

知识回顾

        在前面的学习中,我们已经了解到了链表(线性表的链式存储)的一些基本特点,并且深入的研究探讨了单链表的一些特性,我们知道,单链表在实现插入删除上,是要比顺序表方便的,但是,单链表中每个结点仅存在一个指针指向其后续结点,那么如果我们想要找到其前面的节点,则需要从头部开始遍历,这是十分不方便的;那么,是不是对其添加一些元素或特性,使其的操作更加简单呢?那么我们就来看下这节将要学习的一些链表。 

双链表

        我们知道,单链表中每个结点只有一个指向其后续的指针,这边可以使得单链表可以从前往后的遍历整个链表,但如果我们想要去访问某个结点的前驱节点时,则需要从头开始遍历,这样就会消耗较多的时间;那么为了解决这一问题我们引入双链表。

         我们不难观察到,双链表则是在单链表的基础上,在结点中又添加了一个指针,用于指向该结点的前驱结点,那么这样的话,我们也就可以通过一个结点很好的去查询其前驱和后继结点了,虽然我们增加了存储密度,但在对链表的操作上就更加的灵活。

结点初始化:

typedef struct DNode{
	int data ;
	struct DNode *prior, *next ;
}DNode, *DLinkList;

双链表的插入

        实际上,双链表的插入是类似于单链表的插入的,只不过,由于双链表存在指向后续结点以及前驱结点的指针,所以在进行插入操作时,我们需要对这两个指针分别进行类似的操作即可。

        如图所示,我们若要在双链表中插入x,那么我们首先需要让x结点的后续指针指向插入位置后结点,之后再将插入位置后结点(c)的前驱指针指向x;之后再让x结点的前驱指针指向插入位置的前驱结点(a),之后再将插入位置的前驱结点(a)的后继指针指向x;这样我们就成功的将x插入到链表之中了。

        需要思考的是,当我们的插入位置为链表的末尾时,我们应该怎么去操作呢?这当然也不难操作,通过对双链表的观察,我们知道,在其末尾结点的后续结点会指向NULL;那么这时就不会存在一个后面的结点指向末尾结点了,也就是说,在之前我们的结点是存在两个指向结点的箭头和两个指出的箭头;但到了末尾就缺少一个指向的箭头。

        知道了这层差异,那么我们就要对其进行相应的处理,首先我们需要判断我们插入位置前的结点是否指向NULL,当其指向NULL时,则证明我们插入的为末尾结点,这里我们对前续的操作不变,只不过在对其后续操作时,我们只需要让其指向NULL(也就前末结点指向的位置)即可,不需要进行指向插入结点的操作。

        当然,插入首位的方法也同上所示,只不过从特殊处理后续操作转变为特殊处理前驱操作即可。

//插入
bool InsertNextDNode(DNode *p, DNode *s) {
	if(p == NULL || s == NULL)	return false;
	
	s->next = p->next;
	if(p->next != NULL) {
		p->next->prior = s;
	}
	p->next = s;
	s->prior = p;
	return true;
}

双链表的删除

        对于删除操作,其原理也与单链表的操作类似,只不过其具有两个指针,所以我们需要更改两个指针的指向即可。

        对于双俩表的删除操作,例如我们需要删除b结点,那么我们只需要让a的后续指针指向c(b后续指针指向位置),让c的前驱指针指向a(b前驱指针指向位置),这样我们就可以删除b结点了。

        例如:删除p指针后的结点b。

p->next=q->next;
q->next->prior=p;
free(q);

        当然,删除前驱结点的方法也与之类似,我们更改其相应的指针指向即可。

        那我们如果删除的结点位于双链表的头部或者尾部呢?这样对于我们的操作是否存在什么影响?下面我们来看一下:(在这里我们以末结点为例,实际上头结点删除方法与其类似)

 

        当我们遇到尾结点时,由于我们末尾元素后指向NULL,所以其不存在一个指向前的指针,也就是说,如果我们想要删除末尾元素的话,我们就只需要让其前面的元素结点向后指向NULL即可,之后再释放我们想要删除的末尾结点,就可以解决这种问题了。

//删除(p后的节点) 
bool DeleteNextDNode(DNode *p) {
	if(p == NULL)	return false;
	DNode *q = p->next;
	if(q == NULL)	return false;
	
	p->next = q->next;
	if(q->next != NULL)	q->next->prior = p;
	
	free(q);
	return true;
}

双链表的遍历 

        至于遍历双链表这里由于我们前面的学习,这部分的内容就十分的简单了。我们就像单链表遍历一样,通过头指针或头结点开始从头部或者我们所指定的某一点,以此遍历其next指针,直到其指向NULL为止,那么这样我们是不是就可以成功的从头部遍历一遍链表了呢!

        乍一听,这与单链表是没什么不同的,但由于我们双链表是一个结点存在两个指针的(也就是指向前的prior以及指向后续的next)指针,所以我们在进行遍历的时候,也就可以去尝试更多的遍历方法了,我们可以尝试从后向前遍历。

        这与前面的从前向后遍历并没有什么不同,只是我们需要从尾部向头部遍历的时候需要不停的访问改结点的prior指针,直到其指向NULL为止。

//遍历
//从前向后遍历
void PriorFindList(DLinkList L) {
	DNode *p = L->next;
	while(p != NULL) {
		cout << p->data << " " ;
		p = p->next;
	}
	cout << endl;
}
//从后向前遍历
void AfterFindList(DLinkList L) {
	DNode *p = L;
	while(p->next != NULL) {
		p = p->next;
	}
	
	while(p!=L) {
		cout << p->data << " " ;
		p = p->prior;
	}
	cout << endl;
}

循环链表

        循环链表又可以分为循环单链表和循环双链表。其原理是十分相同的。

循环单链表

        通过图我们不难看出,上面的链表是我们已经讨论过多次的单链表,其尾结点指针指向NULL,那么我们怎么使其变成循环链表呢?

        循环、循环,顾名思义,就是当我们访问某一序列最后一个元素时,紧接着我们就可以以相同的操作去访问该序列第一个元素;在这里对于链表也是相同的:也就是当我们访问的某链表的尾结点时,我们依旧可以通过常规的访问该结点的next指针的方法去访问到该链表的头结点。

        那么这样我们的思路就十分清晰了,我们就只需要在创立链表时,使其的末结点的next指针指向该链表的头结点,这样我们就可以很轻松的实现循环单链表了。

        那么我们为什么要学习单链表呢? 

        如果我们只是使用单链表,当我们有某一结点的位置时,我们可以通过单链表的特性去合理的访问到其后面的各个结点;但其前面的结点对于我们来说就是未知的了。

        为了解决这个问题,我们就可以引入循环单链表,这样我们在得到某一结点位置后,我们依次访问最终就可以成功的访问到该链表头结点,那么我们指定结点前的区域就不再是未知的了。

 

         在引入循环链表时,我们就可以设立一个尾指针,甚至说尾指针在这里要比头指针更加的方便!我们不难知道,对于头指针来说访问链表头部需要O(1)的时间复杂度、访问尾部时需要O(n)的时间复杂度。但对于循环单链表的话,我们就可以使用O(1)的时间复杂度访问其头结点和尾结点,对于尾结点没什么好说的,因为其就指向尾结点我们直接访问即可;但对于头结点我们在访问时仅仅需要访问尾结点的next指针,由于其是循环的,所以我们就可以仅用O(1)的时间复杂度就访问到了头结点,这无疑来说节省了很多时间。那么同理,我们在遇到某些操作中包含需要查找尾结点的操作时,这样都可以节省其时间。

循环双链表 

        如上图中上方的链表一样,该链表是刚才我们已经了解过的双链表;那么其循环双链表的建立实际上是类似于循环单链表的;只不过需要注意的是,由于我们的双链表的每个结点是有两个指针的,所以我们在使其尾部指向头部时,也要去更改头部的prior指针,使其指向链表的尾,这样我们就可以实现循环双链表了。

         这里,由于循环链表于前面的单双链表相似,所以这里就不给出其完整代码实现了,实际上我们只需对一些地方的代码进行特定的修改就可以得到该循环链表的代码了。

静态链表

      通过前面的学习,我们知道,单链表各个结点的存储,在我们计算机的内存中是完全随机杂乱的,我们需要通过指针去link(连接)这些结点;那么我们可不可以在内存中申请一块连续的存储空间,去进行存储这个链表呢?

        当然这乍一听与数组十分的相似,只不过我们需要通过这一连续的存储空间去实现链表的功能,也就是需要通过next"指针"去指向后续节点。

        那么这里我们就可以参考数组,我们将结点划分为存数据的data和存下一位置的数组下标next;这样我们在存入一个元素时首先判断该数组位置是否已存入数据,若没有存入数据的话,我们将其存入,并将上一尾结点的next更新为该结点的数组下标。

        这里我们通过next去串联数组的下标,进而实现链表功能。

          例如上图所示,我们可以将结点的data默认初始化为 -2 (也就是说,当我们在某结点进行存入数据时,可以判断下其next是否为-2,若不是-2则说明该结点已经存在元素),那么我们观察图中链表,可以看出这里我们:头->数组[2]->数组[1]->数组[6]->数组[3]->-1;-1则表明到达了尾部。

优点:增、删 操作不需要大量移动元素 缺点:不能随机存取,只能从头结点开始依次往后查 找;容量固定不可变

适用场景:①不支持指针的低级语言;②数据元素数 量固定不变的场景(如操作系统的文件分配表FAT)

代码

双链表代码

// 2.4 双链表

#include <bits/stdc++.h>

using namespace std ;

typedef struct DNode{
	int data ;
	struct DNode *prior, *next ;
}DNode, *DLinkList;

//初始化
bool InitDLinkList(DLinkList &L) {
	L = (DNode *)malloc(sizeof(DNode)) ;
	if(L == NULL)	return false ;	//分配失败 
	
	L->next = NULL;
	L->prior = NULL;
	return true;
}

//尾插法建立
DLinkList DList_TailInsert(DLinkList &L) {
	int x;
	cin >> x;
	
	DNode *s, *r = L;
	
	while(x!=9999) {
		s = (DNode *)malloc(sizeof(DNode)) ;
		s->data = x;
		r->next = s;
		s->prior = r;
		r = s;
		cin >> x;
	}
	
	r->next = NULL;
	return L;
} 

//插入
bool InsertNextDNode(DNode *p, DNode *s) {
	if(p == NULL || s == NULL)	return false;
	
	s->next = p->next;
	if(p->next != NULL) {
		p->next->prior = s;
	}
	p->next = s;
	s->prior = p;
	return true;
}

//删除(p后的节点) 
bool DeleteNextDNode(DNode *p) {
	if(p == NULL)	return false;
	DNode *q = p->next;
	if(q == NULL)	return false;
	
	p->next = q->next;
	if(q->next != NULL)	q->next->prior = p;
	
	free(q);
	return true;
}

//销毁
void DestoryList(DLinkList &L) {
	while(L->next != NULL){
		DeleteNextDNode(L);
	}
	free(L);
	L=NULL;
} 

//遍历
//从前向后遍历
void PriorFindList(DLinkList L) {
	DNode *p = L->next;
	while(p != NULL) {
		cout << p->data << " " ;
		p = p->next;
	}
	cout << endl;
}
//从后向前遍历
void AfterFindList(DLinkList L) {
	DNode *p = L;
	while(p->next != NULL) {
		p = p->next;
	}
	
	while(p!=L) {
		cout << p->data << " " ;
		p = p->prior;
	}
	cout << endl;
}
 

int main() {
	
	DLinkList L;
	
	InitDLinkList(L);
	
	DList_TailInsert(L);
	
	PriorFindList(L);
	AfterFindList(L);
	
	DNode *p, *s;
	
	p = L;
	s = L;
	
	for(int i=0; i<=3; i++)	p = p->next;	//删去第五个值 
	
	DeleteNextDNode(p);
	
	PriorFindList(L);
	AfterFindList(L);
	
	return 0;
} 

 

 

尾:

        由于博主才疏学浅,总结的相关知识仅限于自我学习认知,若出现错误。望各位大神指点。在这里感谢各位。

        (由于学习的仓促,有些代码未能完全实现以及部分磨块的讲解不充分;若后期实现该代码,将会进行下一步的补充)

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

IDEA利用鼠标调整字体大小

就可以按住ctrl和鼠标调节代码字体的大小啦&#xff01; 如果有用&#xff0c;记得给我来个赞~ 谢啦&#xff01;

【python基础学习07课_函数基础课】

一、函数的基础知识 一、函数的作用是用来干什么的&#xff1f; 函数在编程中是一个组织好的、可重复使用的代码块&#xff0c;用于执行一个特定的任务。具体来说&#xff0c;函数的常见作用包括&#xff1a;1、执行计算或数据处理。 2、控制程序的流程&#xff0c;如条件判断…

Java+SpringBoot+Vue+MySQL:员工健康管理技术新组合

✍✍计算机毕业编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java、…

压缩式 交换式 碎片整理 :(使碎片减少或没有)

交换式碎片整理 首先流程 是 p3这个程序在运行&#xff0c;p1p2p4 的话在等待 &#xff0c;然后p3这时要多用3个内存块&#xff0c;这是 p4 通过拷贝&#xff0c;将内存拷贝到磁盘上&#xff0c;对应的数据也是从主存中cp到磁盘此时主存多出3个内存块给p3继续使用 2.压缩式碎片…

QML中动态增加表格数据

1.QML中的表格实现 import QtQuick 2.15 import QtQuick.Window 2.15import QtQuick.Controls 2.0 import Qt.labs.qmlmodels 1.0 import QtQuick.Layouts 1.15Window {width: 640height: 480visible: truetitle: qsTr("Hello World")TableModel{id:table_modelTabl…

Transformer之self-attention

注意力是一个有助于提高神经机器翻译应用程序性能的概念。在这篇文章中&#xff0c;我们将看看Transformer&#xff0c;一个使用注意力来提高这些模型训练速度的模型。Transformer在特定任务中优于谷歌神经机器翻译模型。最大的好处来自于Transformer如何使自己适合并行化。 在…

135.乐理基础-半音是小二度吗?全音是大二度吗?三全音

内存参考于&#xff1a;三分钟音乐社 上一个内容&#xff1a;134.乐理基础-音程名字的简写-CSDN博客 上一个内容里练习的答案&#xff1a; 半音可以与小二度划等号吗&#xff1f;全音可以和大二度划等号吗&#xff1f; 严格来说它们是不能划等号的&#xff0c;半音与全音是侧…

Android Studio level过滤查看各个等级的日志

Android Studio level过滤查看各个等级的日志 旧版as可以在下方的日志输出框选择debug、info&#xff0c;warn、error日志&#xff0c;新版的需要通过在过滤框手动/联想输入 level:xxx&#xff0c;过滤相应等级的日志&#xff0c;如图&#xff1a; android studio/idea返回/前进…

vue使用gitshot生成gif

vue使用gitshot生成gif 问题背景 本文将介绍vue中使用gitshot生成gif。 问题分析 解决思路&#xff1a; 使用input组件上传一个视频&#xff0c;获取视频文件后用一个video组件进行播放&#xff0c;播放过程进行截图生成图片数组。 demo演示上传一个视频&#xff0c;然后生…

Python 从文件中读取JSON 数据并解析转存

文章目录 文章开篇Json简介Json数据类型Json硬性规则Json数据转化网站Json和Dict类型转换json模块的使用Python数据和Json数据的类型映射json.dumps1.字典数据中含有**存在中文**2.json数据通过缩进符**美观输出**3.对Python数据类型中键进行**排序输出**4.json数据**分隔符的控…

K8S常用kubectl命令汇总(持续更新中)

天行健&#xff0c;君子以自强不息&#xff1b;地势坤&#xff0c;君子以厚德载物。 每个人都有惰性&#xff0c;但不断学习是好好生活的根本&#xff0c;共勉&#xff01; 文章均为学习整理笔记&#xff0c;分享记录为主&#xff0c;如有错误请指正&#xff0c;共同学习进步。…

Leetcode—63. 不同路径 II【中等】

2024每日刷题&#xff08;115&#xff09; Leetcode—63. 不同路径 II 动态规划算法思想 实现代码 class Solution { public:int uniquePathsWithObstacles(vector<vector<int>>& obstacleGrid) {int m obstacleGrid.size();int n obstacleGrid[0].size();…

HTTPS 原理和常见面试题及解析

在互联网安全领域&#xff0c;HTTPS 是一个非常重要的协议&#xff0c;也是很多技术岗位面试中经常涉及的话题。下面我们来看一些关于 HTTPS 的常见面试题及答案解析。 ## 1. 什么是 HTTPS&#xff1f; **答案&#xff1a;** HTTPS 是 Hypertext Transfer Protocol Secure&am…

杭州半日游 - 规划

杭州半日游 https://mbd.baidu.com/newspage/data/dtlandingsuper?niddt_4902055370698452252 11:00 到杭州站 》地铁1号线 30min 午餐可选&#xff1a; 新白鹿餐厅(银泰城店) 最近 17min 2km 知味观(湖滨总店) 30min 3km 》去码头&#xff0c;知味观(湖滨总店) 距此 120…

2024年3月5-7日年生物发酵装备展-环科环保科技

参展企业介绍 山东环科环保科技有限公司,是一家集环保设备的设计、制造、安装、服务及环境治理工程总承包于一体的企业。 公司长期专注于大气、水、危固废三大领域&#xff0c;以科技创造碧水蓝天&#xff0c;为客户提供环保解决方案。 以稳定的产品及服务质量、适用的技术、…

GIT 拉取代码报错error:some local refs could not be updated

文章目录 报错信息处理办法在这里插入图片描述小结 报错信息 ![new branch] dev->orgin/dev(unable to update local ref) error:some local refs could not be updated;try running git remote prune orginto remove any old,confilicting branches 处理办法 git gc --pru…

【Vue】路由

&#x1f4dd;个人主页&#xff1a;五敷有你 &#x1f525;系列专栏&#xff1a;Vue ⛺️稳中求进&#xff0c;晒太阳 目录 路由 单页应用程序 总结&#xff1a; VueRouter 核心步骤&#xff1a; 组件存放目录的问题 路由的封装 声明式导航 声明式导航 - 导航链…

一款高温型霍尔效应传感器

一、产品概述 HAL443A单极性霍尔位置传感器是由内部电压稳压器、霍尔电压发生器、差分 放大器、温度补偿单 元、施密特触发器和集 电极开路输出级组成的磁敏传感电路&#xff0c;其输入为磁感应强度&#xff0c;输出是一个数字电压 信号。它是一种单磁极工作的磁敏电路&…

【Java程序设计】【C00327】基于Springboot的高校教师教研信息填报系统(有论文)

基于Springboot的高校教师教研信息填报系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的高校教师教研信息填报系统&#xff0c;本系统有管理员、教研管理以及教研人员三种角色&#xff1b; 管理员&#xff1a…

C++设计模式——抽象工厂模式

文章目录 抽象工厂模式的主要组成部分抽象工厂模式的一个典型例子抽象工厂模式用于其他场景抽象工厂模式与其他设计模式结合使用 C 中的抽象工厂模式是一种创建型设计模式&#xff0c;它主要用于处理对象家族的创建&#xff0c;这些对象之间可能存在一定的关联关系或属于相同的…