数据结构之队列

数据结构队列的相关学习:

简介

队列是是只允许在一端进行插入操作,而在另一端进行删除操作的线性表。

队列是一种先进先出的线性表,简称FIFO允许插入的以端称为队尾,允许删除的一端被称为队头。

入队

出队

数组实现队列

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
public class Array<E> {

private E[] data;
private int size;

// 构造函数,传入数组的容量capacity构造Array
public Array(int capacity){
data = (E[])new Object[capacity];
size = 0;
}

// 无参数的构造函数,默认数组的容量capacity=10
public Array(){
this(10);
}

// 获取数组的容量
public int getCapacity(){
return data.length;
}

// 获取数组中的元素个数
public int getSize(){
return size;
}

// 返回数组是否为空
public boolean isEmpty(){
return size == 0;
}

// 在index索引的位置插入一个新元素e
public void add(int index, E e){

if(index < 0 || index > size)
throw new IllegalArgumentException("Add failed. Require index >= 0 and index <= size.");

if(size == data.length)
resize(2 * data.length);

for(int i = size - 1; i >= index ; i --)
data[i + 1] = data[i];

data[index] = e;

size ++;
}

// 向所有元素后添加一个新元素
public void addLast(E e){
add(size, e);
}

// 在所有元素前添加一个新元素
public void addFirst(E e){
add(0, e);
}

// 获取index索引位置的元素
public E get(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("Get failed. Index is illegal.");
return data[index];
}

public E getLast(){
return get(size - 1);
}

public E getFirst(){
return get(0);
}

// 修改index索引位置的元素为e
public void set(int index, E e){
if(index < 0 || index >= size)
throw new IllegalArgumentException("Set failed. Index is illegal.");
data[index] = e;
}

// 查找数组中是否有元素e
public boolean contains(E e){
for(int i = 0 ; i < size ; i ++){
if(data[i].equals(e))
return true;
}
return false;
}

// 查找数组中元素e所在的索引,如果不存在元素e,则返回-1
public int find(E e){
for(int i = 0 ; i < size ; i ++){
if(data[i].equals(e))
return i;
}
return -1;
}

// 从数组中删除index位置的元素, 返回删除的元素
public E remove(int index){
if(index < 0 || index >= size)
throw new IllegalArgumentException("Remove failed. Index is illegal.");

E ret = data[index];
for(int i = index + 1 ; i < size ; i ++)
data[i - 1] = data[i];
size --;
data[size] = null; // loitering objects != memory leak

if(size == data.length / 4 && data.length / 2 != 0)
resize(data.length / 2);
return ret;
}

// 从数组中删除第一个元素, 返回删除的元素
public E removeFirst(){
return remove(0);
}

// 从数组中删除最后一个元素, 返回删除的元素
public E removeLast(){
return remove(size - 1);
}

// 从数组中删除元素e
public void removeElement(E e){
int index = find(e);
if(index != -1)
remove(index);
}

@Override
public String toString(){

StringBuilder res = new StringBuilder();
res.append(String.format("Array: size = %d , capacity = %d\n", size, data.length));
res.append('[');
for(int i = 0 ; i < size ; i ++){
res.append(data[i]);
if(i != size - 1)
res.append(", ");
}
res.append(']');
return res.toString();
}

// 将数组空间的容量变成newCapacity大小
private void resize(int newCapacity){

E[] newData = (E[])new Object[newCapacity];
for(int i = 0 ; i < size ; i ++)
newData[i] = data[i];
data = newData;
}
}
1
2
3
4
5
6
7
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
public class ArrayQueue<E> implements Queue<E> {
private Array<E> array;

public ArrayQueue(int capacity) {
array = new Array<>(capacity);
}

public ArrayQueue() {
array = new Array<>();
}

@Override
public int getSize() {
return array.getSize();
}

@Override
public boolean isEmpty() {
return array.isEmpty();
}

public int getCapacity() {
return array.getCapacity();
}

@Override
public void enqueue(E e) {
array.addLast(e);
}

@Override
public E dequeue() {
return array.removeFirst();
}

@Override
public E getFront() {
return array.getFirst();
}

@Override
public String toString() {

StringBuilder res = new StringBuilder();
res.append("Queue: ");
res.append("front [ ");
for (int i = 0; i < array.getSize(); i++) {
res.append(array.get(i));
if (i != array.getSize() - 1)
res.append(", ");
}
res.append("] tail");

return res.toString();
}

public static void main(String[] args) {
ArrayQueue<Integer> queue = new ArrayQueue <> ();
System.out.println();
for (int i = 0; i < 100000; i++) {
queue.enqueue(i);
System.out.println(queue);
if (i % 3 == 2) {
queue.dequeue();
System.out.println(queue);
}
}
}
}

测试

复杂度分析

ArrayQueue<E>
void enqueue(E e)O(1)均摊
E dequeue()O(n)
E getFronto(1)
int getSizeO(1)
boolean isEmpty()O(1)

如果使用顺序队列的话,试想一下如果我们队列十分的大,那么我们需要耗费的时间是多少呢。

循环队列

队列首位相接的顺序存储结构。

通过这样的方法,我们成功避免了数据搬移操作。看起来不难理解在用数组实现的非循环队列中,队满的判断条件是 tail == n,队空的判断条件是 head == tail。那针对循环队列,如何判断队空和队满呢?
队列为空的判断条件仍然是 head == tail。但队列满的判断条件就稍微有点复杂了。

tail=3,head=4,n=8,所以总结一下规律就是:(3+1)%8=4。当队满时,(tail+1)%n=head,当队列满时,图中的 tail 指向的位置实际上是没有存储数据的。所以,循环队列会 浪费一个数组的存储空间。

基于数组实现

循环队列需要我们单独去实现。

1
2
3
4
5
6
7
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
public class LoopQueue<E> implements Queue<E> {

private E[] data;
private int front, tail;
private int size; // 有兴趣的同学,在完成这一章后,可以思考一下:
// LoopQueue中不声明size,如何完成所有的逻辑?
// 这个问题可能会比大家想象的要难一点点:)

public LoopQueue(int capacity){
data = (E[])new Object[capacity + 1];
front = 0;
tail = 0;
size = 0;
}

public LoopQueue(){
this(10);
}

public int getCapacity(){
return data.length - 1;
}

@Override
public boolean isEmpty(){
return front == tail;
}

@Override
public int getSize(){
return size;
}

@Override
public void enqueue(E e){

if((tail + 1) % data.length == front)
resize(getCapacity() * 2);

data[tail] = e;
tail = (tail + 1) % data.length;
size ++;
}

@Override
public E dequeue(){

if(isEmpty())
throw new IllegalArgumentException("Cannot dequeue from an empty queue.");

E ret = data[front];
data[front] = null;
front = (front + 1) % data.length;
size --;
if(size == getCapacity() / 4 && getCapacity() / 2 != 0)
resize(getCapacity() / 2);
return ret;
}

@Override
public E getFront(){
if(isEmpty())
throw new IllegalArgumentException("Queue is empty.");
return data[front];
}

private void resize(int newCapacity){

E[] newData = (E[])new Object[newCapacity + 1];
for(int i = 0 ; i < size ; i ++)
newData[i] = data[(i + front) % data.length];

data = newData;
front = 0;
tail = size;
}

@Override
public String toString(){

StringBuilder res = new StringBuilder();
res.append(String.format("Queue: size = %d , capacity = %d\n", size, getCapacity()));
res.append("front [");
for(int i = front ; i != tail ; i = (i + 1) % data.length){
res.append(data[i]);
if((i + 1) % data.length != tail)
res.append(", ");
}
res.append("] tail");
return res.toString();
}

public static void main(String[] args){ //测试

LoopQueue<Integer> queue = new LoopQueue<>();
for(int i = 0 ; i < 10 ; i ++){
queue.enqueue(i);
System.out.println(queue);

if(i % 3 == 2){
queue.dequeue();
System.out.println(queue);
}
}
}
}

对比

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
import java.util.Random;

public class Main {

// 测试使用q运行opCount个enqueueu和dequeue操作所需要的时间,单位:秒
private static double testQueue(Queue<Integer> q, int opCount) {

long startTime = System.nanoTime();

Random random = new Random();
for (int i = 0; i < opCount; i++) {
q.enqueue(random.nextInt(Integer.MAX_VALUE));
}
for (int i = 0; i < opCount; i++) {
q.dequeue();
}

long endTime = System.nanoTime();

return (endTime - startTime) / 1000000000.0; //纳秒10^9
}

public static void main(String[] args) {

int opCount = 100000; //随机10万个数插入队列

ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
double time1 = testQueue(arrayQueue, opCount);
System.out.println("ArrayQueue, time: " + time1 + " s");

LoopQueue<Integer> loopQueue = new LoopQueue<>();
double time2 = testQueue(loopQueue, opCount);
System.out.println("LoopQueue, time: " + time2 + " s");
}
}

可见时间差距是将近200多倍。

应用

阻塞队列

阻塞队列在队列基础上增加了阻塞操作。在队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。

基于阻塞队列实现的“生产者 - 消费者模型”,可以有效地协调生产和消费的速度。当“生产者”生 产数据的速度过快,“消费者”来不及消费时,存储数据的队列很快就会满了。这个时候,生产者就阻塞等待,直到“消费者”消费了数据,“生产者”才会被唤醒继续“生产”。
而且不仅如此,基于阻塞队列,我可以通过协调“生产者”和“消费者”的个数,来提高数据的处理效率。比如前面的例子,我们可以多配置几个“消费者”,来应对一个“生产者”。

并发队列

线程安全的队列我们叫作并发队列。最简单直接的实现方式是直接在 enqueue()、dequeue() 方法 上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或者取操作。实际上,基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的并发队列。这也是循环队列比链式队列应用 更加广泛的原因。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值