数组模拟环形队列(思路分析) [数据结构与算法][Java]

数组模拟环形队列(思路分析)

使用数组模拟环形队列,就可以解决使用数组模拟队列中的遗留问题了,那么我们要如何使用数组模拟环形队列
  • 相当于前面讲过的数组模拟非环形队列(也就是一般队列),我们这里有如下的变化:

    1. front变量的含义发生了改变: front指向了队列中的第一个元素
      • 也就是说arr[front]就是队列中的第一个元素
    2. rear变量的含义发生了改变: rear指向了队列中的最后元素的后一个位置
      • 这里为什么要使用rear指向队列中最后一个元素的后一个位置?
        • 因为这里我们希望要留出一个空间作为约束
          • 那么为什么要预留出一个空间作为约束?
            • 因为我们这里的这个算法就是需要我们预留一个空间,这样我们才能在有这个约束空间的基础上去指定队列满和队列为空的条件
            • 还有就是我们预留出一个空间作为约束之后我们的数组模拟环形队列的实现会变的简单
    • 为什么rear指向队列中的最后一个元素的后一个位置就说是预留了一个空间作为了约束?
      • 我们这样想,我们的rear既然是指向队列中的最后一个元素的后一个位置,那么这个位置肯定要是空的,不可能是rear和front重复,因为我们的front是队首元素,如果我们的rear和front重复,这个时候无法解释 —> 我们难道要说最后一个元素的下一个位置是队首? —> 这样就乱了,最后一个元素的下一个位置如果是队列没有满那么就是待添加元素的空位置,如果队列已经满了,这个时候队尾元素的下一个位置就是我们的空间约束 —> 我们不可能冲破空间约束
        • 空间约束永远是在队首的前一个位置(当队列满的时候也是队尾的下一个位置)
    1. front和rear的初始值为0
    2. 判断队列满的条件: (rear + 1)maxSize == front
    • 这是其实是一个小算法:
      • 这个算法的本质是: (最后一个位置 + 1)/总位置数 = 第一个位置
        • 使用算法的本质来看待问题就不用管所谓的索引的问题了(但是要注意,这里的第一个位置一定要>=0)
    1. 判断队列为空的条件: rear == front
      • 这里其实和数组模拟非环形队列的判断队列为空的条件是一样的,具体要解释也是几乎一样的
        • 这里就是因为我们的front是指向队列中的第一个元素,rear指向了队列中的最后一个元素的后一个位置,所以rear指向的位置中是没有元素的,这个时候我们如果front == rear,那么这个时候就说明我们的第一个元素不存在,那么连第一个元素都没有,说白了其实就是队列中根本就没有元素
      • 这个时候注意: 这里的rear == front的原因肯定是表中元素被删除完了,而不是队满了之后又添加了一个元素打破了空间约束 —> 纯粹扯淡
    2. 队列中的有效的数据个数: (rear + maxSize - front)%maxSize
      • 这里其实也是涉及到了一个小算法
        • 这里我们就详细的描述一下这个小算法:
          • 我们可以使用一个for循环作为类比,我们要计算环形队列中有多少个元素,那么相应的就相当于求for循环执行了多少次,而对于这里是环形队列中保留了队首元素的前一个位置空间作为约束,那么这个时候就相当于for循环中判断条件没有取=(等号)—> eg:(for(int i = 0;i<100;i++) ),那么这个时候我们很明显可以发现for循环执行了100此,那么说明我们的环形队列中有100个元素,但前提是环形队列时满的(只有唯一的作为空间约束的一个位置为空),加上如果环形队列还是从数组下标为0开始的,那么我们就可以推到出公式: 队列中的有效数据个数 = rear - 0,那么这里由于我们的环形队列中元素有可能有增有删,队首元素会从front开始,那么就可以推到出公式: 队列中的有效数据个数 = rear -front, 但是又因为对于环形队列来讲,如果随着数据的增删,这个时候可能front的值会大于rear的值,所以我们最终得出公式: 队列中的有效数据个数 = (rear + maxSize -front)%maxSize
其实我们学习数据结构就可以从这些数据结构中学习到一些算法: 比如: 我们这里的计算队列中的有效的数据个数,我们其实也可以换个角度其实就是求底层的实现环形队列的数组中的元素数目,而数组中的元素的个数我们可以如何求得?
  • 数组中索引是从0开始的,然后我们就可以轻松的类比得出一些算法

数组模拟环形队列图解分析:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s3QhAoVh-1659252098562)(E:\非凡英才\数据结构(java)]\数据结构图解\数组模拟环形队列的实现示意图.png)

补充:

对于之前我们讲过的非环形队列来讲: 我们的maxSize即使队列底层数组的最大容量,还是队列的长度,但是这里我们对于环形队列来讲: 数组的最大容量 = 队列容量 + 1

  • 那么为什么对于环形队列来讲我们的数组最大容量就是队列容量+1 ?
    • 因为这个时候在我们的数组模拟环形队列的实现中我们预留了一个空间作为约束,所以少了一个空间承装数据,所以底层数组的容量 = 队列容量 +1 ,那么其实加的这个1就是约束空间

不是队尾元素的下一个位置被留作了约束空间,而是队首元素的上一个位置被留作了约束空间

  • 如果是队列满的情况之下我们可以成为队尾元素的下一个位置是约束空间,因为队列满的时候队尾元素的下一个位置和队首元素的上一个位置是指的同一个位置

我们对于数组模拟环形队列过程中的队首指针和队尾指针都是不需要进行显示初始化的,因为在我们使用数组模拟环形队列的实现中我们的队首指针和队尾指针初始值都是0,而我们默认初始化的值就是0,所以就没有必要再次在构造器中或者初始化块(代码块)中进行一次显示的初始化

我们在往数组实现的队列中添加元素的时候要先判断队列是否已满,因为如果队列已经满了,这个时候就不能再添加元素了

我们在删除数组实现的队列中的元素的时候要先判断队列是否为空,如果队列都已经为空了,那么也就没有元素可删了

对于数组实现的环形队列来将我们队首指针和队尾指针后移的时候都要考虑越界的发生,所以就要进行取模

我们从数组模拟环形队列实现中如果要进行出队列的操作,这个时候一定注意,因为我们出队列是从队列的头部位置开始出队列,那么我们在出队列的时候一定要用到一个"临时变量",使用这个临时变量先将我们当前的front指向的元素保存到这个临时变量中,然后再进行指针后移,然后我们再将临时变量中存储的队列中被删除的元素返回

我们如果是数组模拟的环形队列,这个时候如果我们遍历显示队列中的数据,这个时候我们是从索引为front的位置开始遍历,因为我们的front是队首指针,指向的是循环队列中的第一个位置,并且我们要遍历队列中有效数据的个数次,那么我们如何求得队列中的有效数据个数?

  • 我们上面正篇中有提到,可以往上面进行查找

在数组模拟环形队列中 : 索引越界位置 %(取模) 数组长度 = 索引实际位置

  • 有的人会问我们的数组中不是有一个位置没有存数据吗,为什么还要%数组长度
    • 这里和数组中存储几个数据没有关系,这个时候我们是判断我们的索引的位置,这个时候索引的位置是可以占满整个数组的,所以我们计算索引真实位置的时候直接套用上面的公式就可以了

补充二:

这里补充一个小算法:
移动次数 = 间隔数 + 1;

  • 什么意思呐?
    • 就是如果我和你之间间隔了3个人,这个时候从我的位置移动到你的位置需要移动 3 + 1次(也就是移动四次)
      • 其实这个我们通过数学归纳法就能很快的总结出这个规律:
        • 如果A和B之间座位都没有,这个时候也就是A座位和B座位相邻,但是这个时候要从A座位到B座位也是要移动一次的 —> 所以也就是说: 移动的次数永远都比间隔的数目大一

补充三:

我们使用循环队列进行删除元素的操作的时候时间复杂度是一个O(n) —> 因为我们如果是使用数组实现了一个非循环队列 , 这个时候我们每次删除的时候都是从队首开始删除元素, 也就是从数组的头部开始输出元素 ,这个时候每次数组头部的元素被删除之后后面的所有位置的元素都要移动到前面来 ,这个时候时间复杂度就是O(n) , 但是我们如果是使用循环队列的时候就不会有这样的问题, 我们使用数组实现的循环队列在删除元素的时候永远都只需要执行一个front 后移的操作 ,不需要我们的元素前移, 因为我们的循环队列中的任意位置都可以作为头指针和尾指针的位置 , 那么也就是对于循环队列来讲我们每次删除元素的时候只需要进行一个front后移操作, 具体添加元素的时候直接队尾指针直接向后移动就可以了 , 经过我们循环处理之后队尾指针会跑到数组中队首指针的前面,而不会出现数组下标越界的问题

  • 我们的循环队列中浪费了一个空间作为约束, 如果没有这个约束空间那么我们的数组实现的环形队列将无法区分队列满或者是队列空 —> 因为如果没有这个约束条件的话我们的队列判断队列满或者是队列空的条件都是: front == rear
    • 所以我们的空间约束其实就是为了让我们可以对队列满和队列空进行一个区分

补充四:

如果某种数据结构的底层实现是一个自定义数组类 , 这个时候如果我们的这个自定义数组类中实现了数组扩容的功能, 这个时候我们往这个数据结构中添加元素的时候就不用判断这个数据结构是否为满了 ,这个时候由于我们的底层数组类是可以进行一个自动扩容的, 所以这个时候当我们的此数据结构满的时候底层数组也就肯定是会满足我们的扩容条件 ,会进行一个扩容 , 但是如果我们的某种数据结构的底层实现是一个自定义数组,但是这个数组中没有扩容功能的时候我们的这个数据结构在添加元素的时候就要判断这个数据结构是否是满了 ,如果满了的时候就不能向这个数据结构中放置元素了

  • eg: 比如如果我们底层使用java官方给的数组实现了一个栈的时候这个时候我们如果想这个栈中压入元素的时候就要判断这个栈是否已经是满了 ,如果这个栈是已经满了 ,那么我们就不能向这个栈中添加元素了 —> 因为我们的java官方提供的数组类中就是没有扩容的功能的

你可能感兴趣的:(算法与数据结构,数据结构)