java 队列 loop,队列(基于动态数组的两种实现——LoopQueue、ArrayQueue)

(希望我所描述的,给你带来收获!)

队列是先进先出的线性表,在具体应用中通常用链表或者数组来实现!队列结构可以类比日常生活中"排队买东西",在队伍末端的人可以看成新插入的元素,把排队买东西的整个过程看作是入队出队操作,那么总是排在最末尾的那个人最后买东西、最后一个交易完再“出队”!先进先出也可以换一种说法叫——后进后出。都是一个道理。

我们使用数组来实现我们的队列,因为有动态数组的基础,我们实现的队列不再是固定容量的——动态数组篇章的传送门:动态数组的实现

第一步:创建Queue接口,定义ArrayQueue的一般操作

1 public interface Queue{2 intgetSize();3 voidenqueue(E e);4 E dequeue();5 intgetCapacity();6 booleanisEmpty();7 }

主要的两个操作是 enqueue(入队)和dequeue(出队),我们的标准是,以动态数组尾部为队列尾~以动态数组首位Array[0]位置为队列首,为了保证队列结构的特性,我们不提供给用户查看队列中间元素的操作!

第二步:新建ArrayQueue,实现Queue接口的行为

1 public class ArrayQueue implements Queue{2

3 Arrayarray;4

5 public ArrayQueue(intcapacity) {6 array = new Array(capacity);7 }8

9 publicArrayQueue() {10 this(10);11 }12

13 @Override14 public intgetSize() {15 returnarray.getSize();16 }17

18 @Override19 public voidenqueue(E e) {20 array.addLast(e);21 }22

23 @Override24 publicE dequeue() {25 returnarray.removeFirst();26 }27

28 @Override29 public intgetCapacity() {30 returnarray.capacity();31 }32

33 @Override34 public booleanisEmpty() {35 returnarray.isEmpty();36 }37 }

对于ArrayQueue的一些操作,出队操作的时间复杂度总是O(n)的、其他操作的均摊均为O(1)级别(关于均摊复杂度和震荡复杂度会另起一篇介绍)。出于对出队操作的复杂度考虑,我们不希望有如此高昂的时间代价,我们可以基于数组实现循环队列来降低该操作成本!

实现循环队列的第一步:实现Queue接口>

1 private E[] data; //存储数据的数组

2 private int front; //队列头

3 private int tail; //队列尾元素的下一个位置

4 private int size; //记录数据总长度

5 public LoopQueue(intcapacity) {6 data = (E[])new Object[capacity + 1];7 }8

9 publicLoopQueue() {10 this(10);11 }

分别声明了front、tail、size三个变量分别用来记录队列首位置、队列尾位置的下一个空位置和数据总长度

值得思考的是:

第一:tail的实际index值(数组下标值,往后统一使用index替换)是否总是大于front的实际index值?(要考虑循环)

第二:front == tail 应该作为判定队列是否为空的标志,那队列满的标志,front和tail的关系如何?

第二步:

1 public class LoopQueue implements Queue{2 private E[] data; //存储数据的数组

3 private int front; //队列头

4 private int tail; //队列尾

5 private int size; //记录数组总长度

6 public LoopQueue(intcapacity) {7 data = (E[])new Object[capacity + 1];8 }9

10 publicLoopQueue() {11 this(10);12 }13

14

15 @Override16 public intgetSize() {17 returnsize;18 }19

20 @Override21 public voidenqueue(E e) {22 if ((tail + 1)%data.length ==front)23 resize(getCapacity()*2);24 data[tail] =e;25 tail = (tail + 1) %data.length;26 size++;27 }28

29 private void resize(intnewCapacity) {30 E[] newData = (E[])new Object[newCapacity + 1];31 for (int i = 0; i < size; i++) {32 newData[i] = data[(front+i) %data.length];33 }34 front = 0;35 tail =size;36 data =newData;37 }38

39 @Override40 publicE dequeue() {41 if(isEmpty())42 throw new IllegalArgumentException("dequeue is failed,Queue is empty");43

44 E e =data[front];45 data[front] = null;46 front = (front + 1) %data.length;47 size --;48 if ((size - 1) == getCapacity()/4 && getCapacity()/2 != 0)49 resize(getCapacity()/2);50 returne;51 }52

53 @Override54 public intgetCapacity() {55 return data.length - 1;56 }57

58 @Override59 public booleanisEmpty() {60 return tail ==front;61 }62

63 /**

64 * 用于测试的toString方法65 *@return

66 */

67 @Override68 publicString toString() {69 StringBuilder str = newStringBuilder();70 str.append(String.format("Queue:size = %d, capacity = %d\n",size,getCapacity()));71 str.append("front [");72 for (int i = front; i != tail ; i = (i + 1) %data.length) {73 str.append(data[i]);74 if ((i+1) % data.length !=tail)75 str.append(",");76 }77 str.append("] tail");78 returnString.valueOf(str);79 }80 }

tail的实际index值(数组下标值,往后统一使用index替换)是否总是大于front的实际index值?(要考虑循环)

答:tail的index值不总是大于front的index值,因为队列满足循环的效果,当数组尾部已经无法承载容量时,如果(0,front)之间有足够空间,依然可以回到front之前的空间去存储数据。

front == tail 应该作为判定队列是否为空的标志,那队列满的标志,front和tail的关系如何?

答:队列满的标志应该是(tail + 1)% data.length == front(取模运算),因为整个队列是循环的,若data.length == 9,tail == 8,front == 1时,我们的下一次enqueue(入队)操作会在tail位置上插入一个元素,插入元素之后tail的index应该更新为 0;(取模运算的魅力就在于此)。思考一下,插入一个新的元素之后tail的值为0,若此时我再插入一个新的元素,tail的值是否会更新为1(要注意了!front == 1)?答案是不会的,因为我们插入元素之前总该要(tail + 1)% data.length == front判断队列是否满!(建议画图观察!言语描述难免不够深刻)

总结来看:tail位置上总是存储一个用户不可见的无关元素,只有当enqueue时,才会使得tail位置的元素有意义,然而插入新元素之后,tail又会改变为 tail = (tail+1) % data.length; 整体的设计使得实际容量capacity的数组只能容纳(capacity - 1)个元素,换句话说,需要浪费一个空间!

这也是为什么在初始化数组时将用户传进的capacity进行加1的操作

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值