《考研408数据结构》第三章(队列)复习笔记
前提提要:
因为数据结构我个人大一学过,而且我也一直认为是408里最简单的,所以我一直拖到很晚才开,视频也没看,直接加速用【思维导图】+【例题】极限回顾复习,因此我也默认大部分人都是很早就学完了数据结构,所以该数据结构系列笔记只适用于非初学者的框架回顾复习,不再解释简单知识点概念。
;
【另外关于:队列】
网上许多大佬、博主都总结了,【栈】和【队列】出的代码题本来就少可以说就没有,那么代码理解这块我将不再深究,反正知道逻辑会做【选择题】应该就够了
一、队列的基本概念
思维导图(放大保存可打印)
稍微解释
大家都会我就不说什么了,如何定义说白了就是————【只允许在一端插入、另一端删除的线性表】(比栈多一个口)
因为这个特性,所以最早进入的元素也会最早出队列,这叫【FIFO先进先出(First In First Out)】
这玩意也不用背,看单词四级翻译就完事,好像考试也不考,只是我们写代码习惯而已
例题
二、队列的顺序存储
思维导图
【队列形式方案一】
队列的形式基本都是【循环队列】
- 也就是两个指针:【头指针front】、【尾指针rear】,分别指代对头和队尾
- 【头指针front】负责【删除数据】
- 从队头(第1个存入数的空间)开始,往后遍历删除被front指向的元素
- 【尾指针rear】负责【插入数据】
- 从队头(第1个存入数的空间)开始,往后遍历填入被rear指向的元素
- 小误区:我刚开始学的时候老是以为【头指针front】清空一轮、【尾指针rear】填满一轮,都各自会回到一开始指向的初始位置(比如一开始都指着0,最后【填满、清空】一轮后还是会回到0)
- 但是大错特错!!!又没有说插入的时候必须要填满,也没说删除时必须清空啊,所以这两个指针想在什么位置就在什么位置(队列范围内)
- 而循环队列要做的就是,保持【尾指针rear】永远不会【跑出队列外面 】
- 当【尾指针rear】在队尾快要出去时(rear + 1的位置就是出队列了),让【rear+1 % 队列的长度】就能回到开头了
- 对【队列长度】取余的原理就是:任何数对N取余,结果都是【小于等于N】,比如N=5,9999 mod 5 = 5、2 mod 5 = 1......而5 mod 5刚好就是开头0
- 需要知道:注意:队尾永远会牺牲一个单元空间,当队列有n个空间,最多只能插入数据到n-1位,而第n位是
- (若从0开始)在循环队列里【最后一个位置N-1】不存数据,就是为了让rear存数存满时到达【这个N-1的位置】,然后rear就能和头指针front位置错开,避免误判成队列为空
- 那么基本几个操作
- 【判断为空】的操作就是【尾指针rear = 头指针front】
- 【判断为满】就是【(尾指针rear +1)% 队列长度 = 头指针front】
- 因为最后一个位置(头指针front的前一位)是不存数据的,尾指针rear删完最后一个数据会最后移动到【N】的位置,然后【rear+1】就是【N+1】,它对【队列长度】取余的结果刚好就是【回到队头】
- 【入队】就是【尾指针rear =(尾指针rear +1)% 队列长度】
- 【出队】就是【头指针front =(头指针front +1)% 队列长度】
- 【查找】就是【 Q.data[ Q.front ] 】
【注意!!!】
像这种【牺牲一个空间】来错开【头、尾指针】表示栈满的队列方案,需要注意有这么几种指针方案:
- 1、第一种是
- 【头指针front】在【队首元素】
- 【尾指针rear】在【队尾后一元素】
- 2、第二种是
- 【头指针front】在【队首元素的前一位】
- 【尾指针rear】在【队尾元素】
【队列形式方案二】
但是有的队列非要用【队尾那个不让存数据的空间】存数据,那队列满的时候【头指针front】和【尾指针rear】就有可能重合,空队列也是重合,就没办法判断到底是队满?还是对空了?
【解决方法】也很简单:
- 1、数据结构多加一个值:【size】记录整个队列【当前存入几个数据】
- 【size】初始值设为0
- 插入成功后要【size++】,删除成功后【size--】
- 2、【判断为满】:【size = MaxSize】
- MaxSize代表这个队列的最大长度n
- 而队列的本质是数组,数组是 [从0开始 ~ 到n-1]
- 3、【判断为空】:【size = 0】
【队列形式方案三】
还有一种【解决方法】:
- 1、数据结构多加一个值:【tag】记录整个队列【当前存入几个数据】
- 【tag】初始值设为0
- 插入成功后要【tag=1】,删除成功后【tag=0】
- 2、【判断为满】:【front==rear 并且 tag=1】
- tag=1,说明头尾指针重合是由插入导致的,肯定队满
- 3、【判断为空】:【front==rear 并且 tag=0】
- tag=0,说明头尾指针重合是由删除导致的,肯定队空
【灾难级误区】
可能我智商有点低有点傻逼,我老是困惑:
- 为什么队列可以在数组里中间断开?不应该从0到n连续起来的吗?
- 为什么【队列的长度】可以不固定?
- 因为:
- 特么的【队列长度】不是这个结构体数组的最大长度!!!是特么的【当前存了几个数】!!!(比如:创建a[5],令a[0]=x1、a[1]=x2现在这个队列数组最大长度是5,长度是2
- 然后前面说了头指针、尾指针可以到随意位置,随着不断插入、删除,自然而然就会从中间断开,但是依靠【(指针位 + 1) % MaxSize】并不影响整个队列在逻辑上依旧连续!!!
【画队列做题】
解释完上面的误区疑点,从现在开始,以后画队列的图,必须适应:
- 【在中间断开】
- 【数据插入:从中间到顶、再从低到中间】
【双端队列(冷门)】
快速做法
【例题】
三、队列的链式存储
思维导图
【链表队列】最适合的结构
首先牢记:【链表队列】是只做【头删、尾插】的链表!!!!!
- 注意:选择题通常默认考察【不带头结点】的队列链表,所以【首元节点】是会被删除的
然后给出结论:
- 【队列】比较特殊,和前面学的链表、栈都不一样
- 【普通非循环】链表最屌,【最适合】它!!!
- 前提是要有【头、尾双指针】!!!!
- 因为没有尾指针,对尾结点插入不方便
- 【循环双链表】也方便,但是没【普通非循环】简洁
- 【循环单链表】弄个 “循环结构” 纯粹多此一举!!!
- 用实例证明以上结论
- 【普通不循环】链表队列
- 【循环单】链表队列
- 【循环双】链表队列
- 不需要我再画流程了,它在哪都是无敌的存在
- 至于为什么在队列没有【非循环】更简洁,就因为他还是要【维护循环结构】,所以代码肯定会比【非循环】多一两句,但是【整体时间复杂度都是O(1)】
- 【总结】
;
;
【回忆】:那为什么前面链表的章节说【单链表】垃圾呢?
- 那是【单链表】针对【尾删除!!】不管带几个指针,都是垃圾!!!