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

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

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

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

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

1 public interface Queue<E> {
2     int getSize();
3     void enqueue(E e);
4     E dequeue();
5     int getCapacity();
6     boolean isEmpty();
7 }

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

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

 1 public class ArrayQueue<E> implements Queue<E> {
 2 
 3     Array<E> array;
 4 
 5     public ArrayQueue(int capacity) {
 6         array = new Array<E>(capacity);
 7     }
 8 
 9     public ArrayQueue() {
10         this(10);
11     }
12 
13     @Override
14     public int getSize() {
15         return array.getSize();
16     }
17 
18     @Override
19     public void enqueue(E e) {
20         array.addLast(e);
21     }
22 
23     @Override
24     public E dequeue() {
25         return array.removeFirst();
26     }
27 
28     @Override
29     public int getCapacity() {
30         return array.capacity();
31     }
32 
33     @Override
34     public boolean isEmpty() {
35         return array.isEmpty();
36     }
37 }

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

实现循环队列的第一步:<新建一个LoopQueue<E>实现Queue<E>接口>

 1     private E[] data;   //存储数据的数组
 2     private int front;  //队列头
 3     private int tail;   //队列尾
 4     private int size;   //记录数据总长度
 5     public LoopQueue(int capacity) {
 6         data = (E[])new Object[capacity + 1];
 7     }
 8 
 9     public LoopQueue() {
10         this(10);
11     }

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

值得思考的是:

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

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

第二步:

 1 public class LoopQueue<E> implements Queue<E> {
 2     private E[] data;   //存储数据的数组
 3     private int front;  //队列头
 4     private int tail;   //队列尾
 5     private int size;   //记录数组总长度
 6     public LoopQueue(int capacity) {
 7         data = (E[])new Object[capacity + 1];
 8     }
 9 
10     public LoopQueue() {
11         this(10);
12     }
13 
14 
15     @Override
16     public int getSize() {
17         return size;
18     }
19 
20     @Override
21     public void enqueue(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(int newCapacity) {
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     @Override
40     public E 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         return e;
51     }
52 
53     @Override
54     public int getCapacity() {
55         return data.length - 1;
56     }
57 
58     @Override
59     public boolean isEmpty() {
60         return tail == front;
61     }
62 
63     /**
64      * 用于测试的toString方法
65      * @return
66      */
67     @Override
68     public String toString() {
69         StringBuilder str = new StringBuilder();
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         return String.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的操作

猜你喜欢

转载自www.cnblogs.com/zhixiangshu/p/10144789.html
今日推荐