C++ 队列
C++是C语言的继承,C语言是过程化程序设计语言,C++是面向对象的程序设计语言,过程化意思是编程的作用倾向于提供输入通过一系列的代码得到一个输出,面向对象则使适用于模块化设计,C语言的局限性在大型程序开发设计的过程中则会逐步显现出来。
C++ 相对C语言有什么优点呢
- 封装性:对数据的管理上的优点
- 继承性:体现了代码重用上的优点
- 多态性:则体现了同一方法对不同类对象有不同的效果
牢牢记住这三个词:封装、继承、多态,这些是谭浩强C++教学丛书中描述的最基本的概念。
知道了这些大的概念,知道了C++是面向对象的语言,下一步就可以开始研究我们的对象了,怎么去实现面向对象呢?比如我们要设计一个管理系统,需要先研究这个系统里有哪些类,这些类相应具有什么方法,然后逐步通过代码一步一步讲类和方法码出来。
这里说一个题外话,为什么很多程序员喜欢戏称自己的工作为“搬砖”,因为在大公司里,很多程序员很少参与到新的模块、代码的设计,所做的工作就是将前人的代码从这个程序搬到另一个程序中,所做的工作就像搬砖,枯燥乏味。
上面说到设计一个系统要考虑到为了这个系统,需要设计哪些类。而队列,可以考虑为一种类,也可以作为组成某个类的一种数据结构。
在了解队列之前,需要知道几个最基本的概念:指针、FIFO(先进先出)
这里就不详细讲解指针的概念,只需要知道指针的值指向电脑寄存器上另外一个地方的值,可以理解它记录的是另外一个变量的位置或者说地址。
FIFO,First Input First Output的缩写,先进先出队列,这是一种传统的按序执行方法,先进入的指令先完成并引退,跟着才执行第二条指令。生活中时常出现的排队就是一种先进先出的行为——在超市购物之后会提着我们满满的购物车来到收银台排在结账队伍的最后,眼睁睁地看着前面的客户一个个离开。这就是一种先进先出机制,先排队的客户先行结账离开。
千呼万唤始出来,终于可以开始了解队列了。
①队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。
②队列中没有元素时,称为空队列。
③建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置。
④队列采用的FIFO(first in first out),新元素(等待进入队列的元素)总是被插入到链表的尾部,而读取的时候总是从链表的头部开始读取。每次读取一个元素,释放一个元素。所谓的动态创建,动态释放。因而也不存在溢出等问题。由于链表由结构体间接而成,遍历也方便。(先进先出)
实际上,上面所说的概念是顺序队列的概念,你有没有看出顺序队列的弊端?
对!实际上当A、B、C依次加入队列,然后又按照A、B、C的顺序依次读取释放之后,front和rear指针从队列的0号位置,指向了最末的3号位置,假如这个队列只静态申请了0到3这4个单元位置,到此这个队列就没法再添加进新的元素了,因为假如添加D之后,rear再往后移将没有空间给它指向了!(这也被称为假上溢现象),这就是顺序队列的弊端。
知道了顺序队列的弊端如何去改进呢,我们就引申出循环队列的概念。
好,我们一起把顺序队列、循环队列再过一遍。
顺序队列
建立顺序队列结构必须为其静态分配或动态申请一片连续的存储空间,并设置两个指针进行管理。一个是队头指针front,它指向队头元素;另一个是队尾指针rear,它指向下一个入队元素的存储位置,如图所示
每次在队尾插入一个元素是,rear增1;每次在队头删除一个元素时,front增1。随着插入和删除操作的进行,队列元素的个数不断变化,队列所占的存储空间也在为队列结构所分配的连续空间中移动。当front=rear时,队列中没有任何元素,称为空队列。当rear增加到指向分配的连续空间之外时,队列无法再插入新元素,但这时往往还有大量可用空间未被占用,这些空间是已经出队的队列元素曾经占用过得存储单元。
顺序队列中的溢出现象:
(1) “下溢”现象:当队列为空时,做出队运算产生的溢出现象。“下溢”是正常现象,常用作程序控制转移的条件。
(2)”真上溢”现象:当队列满时,做进栈运算产生空间溢出的现象。“真上溢”是一种出错状态,应设法避免。
(3)”假上溢”现象:由于入队和出队操作中,头尾指针只增加不减小,致使被删元素的空间永远无法重新利用。当队列中实际的元素个数远远小于向量空间的规模时,也可能由于尾指针已超越向量空间的上界而不能做入队操作。该现象称为”假上溢”现象。
总结一下:
1、队列时先进先出的一种数据结构(类比排队有助理解);
2、顺序队列有两个指针,队列非空时,front指向下一个要删除的元素的位置,rear指向下一个要插入的元素的位置;
3、顺序队列当rear指向申请的存储单元最末时,无法再进行添加操作(上溢);
4、front=rear , 代表队列为空。
循环队列
在实际使用队列时,为了使队列空间能重复使用,往往对队列的使用方法稍加改进:无论插入或删除,一旦rear指针增1或front指针增1 时超出了所分配的队列空间,就让它指向这片连续空间的起始位置。自己真从MaxSize-1增1变到0,可用取余运算rear%MaxSize和front%MaxSize来实现。这实际上是把队列空间想象成一个环形空间,环形空间中的存储单元循环使用,用这种方法管理的队列也就称为循环队列。除了一些简单应用之外,真正实用的队列是循环队列。
在循环队列中,当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种情况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件是front=rear,而队列判满的条件是front=(rear+1)%MaxSize。
判空的条件很好理解,判满怎么理解呢?
一个循环队列,假如有六个存储单位,0-5,往里面依次加入A、B、C、D、E这5个元素,则依次占满了0-4这个五个存储位置,因为只有插入操作,所以front的值不变,仍然为0,rear的值则变为5,此时已经满足了队列满的条件(只剩下5这一个存储单位了),我们代入公式看一下:
front=0
(rear+1)%6=(5+1)%6=0
满足判满条件,可以判断队列是满的。
如果继续删除一个元素然后添加一个元素呢。
此时front=1,rear=0
(rear+1)%6=(0+1)%6=1
仍然满足条件。
总结一下:
1、判空:front=rear 判满:front=(rear+1)%MaxSize;
2、取余运算使得队列可以进行循环使用;
3、和顺序队列的区别是存储空间可以循环使用;
4、需要预留一个存储空间大小,即还剩一个存储空间时,队列已经满了。
对队列概念的讲解就到这里,下一篇内容我们就要着手开始用C++语言来实现C++循环队列。
了解了队列,之后也一定会遇到“堆”,”栈”,”堆栈”的概念,这些数据结构和队列有一定相似之处,这些到底是什么意思?最关键的是即使你去面试,这些都还会问到,所以如果你不懂对你是损失很大的。
[1] https://jingyan.baidu.com/article/6c67b1d6a09f9a2786bb1e4a.html
[2] https://www.cnblogs.com/liumce/p/7760670.html
[3] https://blog.youkuaiyun.com/xiaolei09bupt/article/details/44565255