当前位置 : 主页 > 编程语言 > java >

【Java -- 数据结构】什么是队列(Queue)?

来源:互联网 收集:自由互联 发布时间:2022-06-22
前言 队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的

前言

队列是一种特殊的线性表,特殊之处在于它只允许在表的前端(front)进行删除操作,而在表的后端(rear)进行插入操作,和栈一样,队列是一种操作受限制的线性表。进行插入操作的端称为队尾,进行删除操作的端称为队头。先进先出,简单暴力的理解就是吃进去拉出来

优点:

  • 提供了先进先出的存取方式

缺点:

  • 存取其他项很慢

【Java -- 数据结构】什么是队列(Queue)?_出队
队列有两个最基本的操作,入队enqueue(),放一个数据到队列的尾部;出队dequeue(),从队列头部取出一个元素。

顺序队列和链式队列

队列可以使用数组来实现也可以使用链表来实现,用数组实队列的栈叫做顺序队列,用链表实现的队列叫做链式队列。

使用数组来实现一个队列:

public class ArrayQueue {
private String[] items;
private int n = 0;//总大小
private int head = 0;
private int tail = 0;

//申请一个大小为n的数组
public ArrayQueue(int n) {
this.items = new String[n];
this.n = n;
}

//入队
public boolean enqueue(String item){
//如果队列满了返回false
if(tail == n) return false;
items[tail] = item;
++tail;
return true;
}

//出队
public String dequeue(){
//head == tail 表示队列为空
if(head == tail) return null;
String res = items[head];
++head;
return res;
}

}

队列需要两个指针,一个指向队头,一个指向队尾。

【Java -- 数据结构】什么是队列(Queue)?_数据_02
当a、b、c、d一次进入队列之后,队列中的head指针指向下标为0的位置,tail下标为4的位置
当我们调用两次出队操作的之后,队列中的head指针指向下标为2的位置,tail指针位置不变。

随着不停的入队和出队操作,head和tail指针会不断的往后移动移动到最右边,即使数组中还有空闲的空间也不能往队列中添加数据了,那怎么办呢?

我们可以使用数据搬移,当然出队的时候不会出现空间不够的情况也就不用搬移,我们只需要在入队的时候集中触发一次数据搬移即可。根据这个思想,上面的出队操作不用变,入队操作可以改成下面的:

public boolean enqueue(String item){
//tail==n 说明队列满了
if(tail == n){
//tail==n&&head==0说明整个队列都是满的
if(head == 0) return false;
//搬移数据
for (int i = head; i < tail; i++) {
items[i-head] = items[i];
}
//更新tail 和 head
tail -= head;
head = 0;
}

items[tail] = item;
++tail;
return true;
}

从上面的代码可以看到,当tail指针移动到数据的最右边之后,如果有新数据插入,我们可以将head和tail之间的数据,整体搬移到数组0到tail-head的位置。

使用链表实现队列:

使用链表实现队列同样需要head和tail指针,分别指向链表的第一个结点和最后一个结点。

入队的时候,tail的next指针指向新的结点,然后tail变成新的结点的next。

出队的时候,head的next结点变成它后面的结点的next结点。

public class LinkedQueue {

private Node head = null;
private Node tail = null;

//入队
public void enqueue(String value){
if(tail == null){
Node newNode = new Node(value,null);
head = newNode;
tail = newNode;
}else {
tail.next = new Node(value,null);
tail = tail.next;
}
}
//出队
public String dequeue(){
if(head == null) return null;
String value = head.data;
head = head.next;
if(head == null){
tail = null;
}
return value;
}

private static class Node{
private String data;
private Node next;

public Node(String data, Node next) {
this.data = data;
this.next = next;
}

public String getData() {
return data;
}
}
}

循环队列:

上面使用数组来实现的队列,当tail==n的时候,会有数据的搬移操作,这样入队的时候的性能就会收到影响。有没有办法解决呢?可以使用循环队列。

原本的数组是一条直线,有头有尾,现在我们把首位相连组成一个环

当tail == n 的时候,表示队列满了,当 head == tail的时候表示队列是空的。

public class CircleQueue {
private String[] items;
private int n = 0;//总大小
private int head = 0;
private int tail = 0;

//申请一个大小为n的数组
public CircleQueue(int n) {
this.items = new String[n];
this.n = n;
}

//入队
public boolean enqueue(String item){
//(tail+1)%n == head表示满了
if((tail + 1)%n == head) return false;
items[tail] = item;
tail = (tail+1)%n;
return true;
}

//出队
public String dequeue(){
//head == tail 表示队列为空
if(head == tail) return null;
String res = items[head];
head = (head+1)%n;
return res;
}

}

阻塞队列:

阻塞队列就是在队列的基础上加了阻塞操作,就是在队列空的时候,从头部取数据会被阻塞,直到队列中有了数据才会返回。如果队列已经满了,那么插入数据的操作会被阻塞,直到队列中有新的空闲位置的时候才返回。

并发队列:

在多线程的情况下,会有多个线程同时操作队列,这时候就会存在线程安全的问题。

线程安全的队列称为并发队列,最简单直接的方式就是在enqueue()和dequeue()的方法上加上锁。

不过锁的粒度比较大,同一时刻只能有一个存或者取的操作。

通过基于数组的循环队列,利用CAS原子操作,可以实现非常高的并发队列。

大部分资源有限的场景,当没有空闲资源的时候,基本上都可以用于排队请求,比如线程池,数据库连接池等。


上一篇:【Java -- 数据结构】什么是图(Graph)?
下一篇:没有了
网友评论