数据结构与算法-队列

  1. 队列

特性

 

操作特性:

队列也是一种操作受限的线性表,只允许在一端插入和删除数据。

数据结构与算法-队列_第1张图片

跟栈一样,队列也可以使用数据来实现,还可以使用链表来实现。

使用数组实现的队列叫做顺序队列,使用链表实现的队列叫做链式队列。

 

 

顺序队列

 

/**
 * use array implement queue
 *
 * @author liujun
 * @version 0.0.1
 * @date 2019/08/23
 */

public class MyArrayQueue {

 
private int[] array;

 
/** max queue size */
 
private final int MaxArraySize;

  private int
size;

  private int
head;

  private int
tail;

  public
MyArrayQueueOne(int maxArraySize) {
   
this.MaxArraySize = maxArraySize;
    this
.array = new int[maxArraySize];
    this
.head = 0;
    this
.tail = 0;
 
}

 
public void enqueue(int value) {
   
if (tail < MaxArraySize) {
     
array[tail] = value;
     
tail++;
     
size++;
   
} else {
     
throw new IndexOutOfBoundsException("queue full");
   
}
  }

 
public int size() {
   
return size;
 
}

 
public boolean isfull() {
   
return size == MaxArraySize;
 
}

 
public int dequeue() {

   
int getValue = -1;

    if
(head < tail) {
      getValue =
array[head];

      for
(int i = 1; i < tail; i++) {
       
array[i - 1] = array[i];
     
}
     
tail = tail - 1;
     
size--;
   
}

   
return getValue;
 
}
}

 

 

 

 

这是一个基本的队列实现,但存在着问题,那就是数组空间连续性的问题,在每次操作的,会进行数据的所有数据的一次搬移操作。

数据结构与算法-队列_第2张图片

 

 

每次有一个数据从队列中取出,就会触发一次数据的搬移,浪费严重。针对此问题,可采用集中式的触发。即当队列中的数据被填满了,才触发一次数据搬移操作。在队列还没有被填满之前,不进行数据的搬移操作。

 

public void enqueue(int value) {
 
if (tail <= MaxArraySize) {

   
//如果空间已经被占用,则触发一次搬移操作
   
if (tail == MaxArraySize) {
     
for (int i = head; i < tail; i++) {
       
array[i - head] = array[i];
     
}
     
tail = head;
     
head = 0;
   
}
   
//
   
if (tail < MaxArraySize) {
     
array[tail] = value;
     
tail++;
     
size++;
   
}
  }
else {
   
throw new IndexOutOfBoundsException("queue full");
 
}
}

 

 

 

循环队列

在顺序队列的实现中,当tail == MaxArraySize 会有数据的搬移操作。必然的会影响到性能,那有没有不搬移数据的实现呢?那这个来看看循环队列的实现思路。

 

循环队列,类比循环链表, 那就是一个首尾相连的数组。

数据结构与算法-队列_第3张图片

就像一个环一样,队列的添加与删除总不断的这环中往复。

 

但要想写出没有bug的环境形队列,关键在于确定队空与队满的判定条件。假如队列大小为n,那么,队空的判定条件就是head==tail,那队满的判断条件呢。先来看看图中所画队满时的环形队列

 

数据结构与算法-队列_第4张图片

tail =1、head=2 队列大小为8,可(1+1)%8=2

总结规律就是(tail+1)%n == head,这就是环形队列满的判定条件。

 

再来看看代码实现吧:

 


/**
 * use array implement cycle queue
 *
 * @author liujun
 * @version 0.0.1
 * @date 2019/08/25
 */

public class MyCycleQueue {

 
private final int[] array;

  private final int
capacity;

  private int
head;

  private int
tail;

  public
MyCycleQueue(int maxSize) {
   
this.capacity = maxSize;
    this
.array = new int[capacity];
 
}

 
public void enqueue(int value) {
   
// check cycle is full
   
if ((tail + 1) % capacity == head) {
     
throw new IndexOutOfBoundsException("queue is full");
   
} else {
     
array[tail] = value;
     
tail = (tail + 1) % capacity;
   
}
  }

 
public int dequeue() {
   
// check cycle is null
   
int getvalue = -1;
    if
(head == tail) {
     
throw new NegativeArraySizeException("queue is empty");
   
} else {
      getvalue =
array[head];
     
head = (head + 1) % capacity;
   
}

   
return getvalue;
 
}
}

 

 

 

 

链式队列

链式队列的实现.

数据结构与算法-队列_第5张图片

因为链表只涉及节点的删除与添加操作。没有数据搬移的操作,实现起来也比较简单。但因为链表是内存不连续,对于CPU的缓存不友好。

 

 

/**
 * use linked implement queue
 *
 * @author liujun
 * @version 0.0.1
 * @date 2019/08/23
 */

public class MyLinkedQueue {

 
class LinkNode {
   
private int val;

    private
LinkNode next;

    public
LinkNode(int valu) {
     
this.val = valu;
   
}
  }

 
/** max queue size */
 
private final int MaxQueueSize;

  private
LinkNode root = new LinkNode(-1);

  private
LinkNode tail = root;

  private int
size;

  public
MyLinkedQueue(int maxQueueSize) {
   
this.MaxQueueSize = maxQueueSize;
 
}

 
public int size() {
   
return size;
 
}

 
public boolean isfull() {
   
return size == MaxQueueSize;
 
}

 
public void enqueue(int value) {

   
if (size < MaxQueueSize) {
      LinkNode tmpValue =
new LinkNode(value);

     
tail.next = tmpValue;
     
tail = tmpValue;

     
size++;
   
}
  }

 
public int dequeue() {

   
int getvalue = -1;
    if
(size > 0) {
      LinkNode linkNode =
root.next;
     
getvalue = linkNode.val;

     
root.next = linkNode.next;

      if
(null == root.next) {
       
tail = root;
     
}

     
size--;
   
}

   
return getvalue;
 
}
}

 

 

并发队列

这是一种特殊性质的队列,即在一个队列的实现中,加入了阻塞操作。当队列为空,队头取数据就会被阻塞,当队列满了,队尾放数据就会被阻塞,这很好的协调了两端速度不一至的问题,这个在实际的软件开发过程中非常的常用,就是我们平时开发中所说的,“生产者-消费者模型”,

 

数据结构与算法-队列_第6张图片

 

当生产者线程的速度过快,消费者线程来不及消费时,队列很快被填满,生产者线程就会被阻塞。待消费者线程将队列数据取走,再唤醒生产者线程继续生产。

 

还可以协调生产者与消费者的个数,来提高数据的处理速度。

数据结构与算法-队列_第7张图片

 

 

并发队列即可以使用链表来实现,还可以使用数组来实现。

使用链表来实现的阻塞队列,可以支持一个无界的队列,可以用来处理很长的任务队列,但由于队列可能会非常的长,处理时间也可能会非常的长,所以针对时间不敏感的任务,就比较适合使用链表来实现的阻塞队列,比如定时调度的任务,后台消息发送队列等。

使用数组来实现的阻塞队列,只支持一个有限的数组的大小,当处理的任务超过队列时,可直接拒绝任务处理,这样就更加适合时间敏感的系统,比如秒杀,处理用户的请求等。但队列的大小,设置时也需要格外的注意, 队列太大,导致处理请求过多的等待,过小,又导致资源浪费。

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