循环队列LoopQueue

为充分利用向量空间,克服"假溢出"现象的方法是:将向量空间想象为一个首尾相接的圆环,并称这种向量为循环向量。存储在其中的队列称为循环队列(Circular Queue)。循环队列是把顺序队列首尾相连,把存储队列元素的表从逻辑上看成一个环,成为循环队列。
在循环队列中,当队列为空时,有front=rear,而当所有队列空间全占满时,也有front=rear。为了区别这两种情况,规定循环队列最多只能有MaxSize-1个队列元素,当循环队列中只剩下一个空存储单元时,队列就已经满了。因此,队列判空的条件是front=rear,而队列判满的条件是front=(rear+1)%MaxSize。

  • 循环队列中只能存储MaxSize-1个元素,会有1个位置是空着的
  • 循环队列空的条件是rear=front
  • 循环队列满的条件是front=(rear+1)%MaxSize(为了防止溢出,所以需要进行取余计算)

队列接口

public interface Queue<E> {
	
	void enqueue(E e);//入队
	E dequeue();//出队
	E getFront();//获取队首元素
	int getSize();//获取队列中元素个数
	boolean isEmpty();//判断队列是否为空
}

循环队列LoopQueue

public class LoopQueue<E> implements Queue<E> {

	private E[] data;//队列数组
	private int front, tail;//头节点,尾节点
	private int size;//队列元素个数

	public LoopQueue(int capacity) {
		// TODO Auto-generated constructor stub
		data = (E[]) new Object[capacity + 1];// 循环队列中浪费1个元素,所以存储capacity个元素创建数组时需要+1
		front = 0;//头节点
		tail = 0;//尾节点
		size = 0;//队列元素个数
	}

	public LoopQueue() {
		// TODO Auto-generated constructor stub
		this(10);
	}

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

	@Override
	public void enqueue(E e) {
		// TODO Auto-generated method stub
		if ((tail + 1) % data.length == front) { // 循环队列满
			resize(getCapacity() * 2);
		}
		data[tail] = e;
		tail = (tail + 1) % data.length;
		size++;
	}

	private void resize(int newcapacity) {
		// TODO Auto-generated method stub
		E[] newData = (E[]) new Object[newcapacity + 1];
		for (int i = 0; i < size; i++) {
			newData[i] = data[(i + front) % data.length];// 需要把data中的队列按照顺序复制给newData,并且防止i+front超过data.length
		}
		data = newData;
		front = 0;
		tail = size;
	}

	@Override
	public E dequeue() {
		// TODO Auto-generated method stub
		if (isEmpty()) {
			try {
				throw new Exception("队列为空");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		E ret = data[front];
		data[front] = null;// 防止data[front]闲逛
		front = (front + 1) % data.length;
		size--;
		if (size == getCapacity() / 4 && getCapacity() / 2 != 0) { // 缩容 
		//size == getCapacity() / 4 防止复杂度震荡  
		// getCapacity() / 2 != 0 防止new数组长度为0
			resize(getCapacity() / 2);
		}
		return ret;
	}

	@Override
	public E getFront() {
		// TODO Auto-generated method stub
		if (isEmpty()) {
			try {
				throw new Exception("队列为空");
			} catch (Exception e) {
				// TODO Auto-generated catch block
				e.printStackTrace();
			}
		}
		return data[front];
	}

	@Override
	public int getSize() {
		// TODO Auto-generated method stub
		return size;
	}

	@Override
	public boolean isEmpty() {
		// TODO Auto-generated method stub
		return front == tail;// 循环队列为空
	}

	public String toString() {
		StringBuilder res = new StringBuilder();
		res.append(String.format("LoopQueue: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 class Test01 {
	public static void main(String[] args) {
		LoopQueue<Integer> loopqueue = new LoopQueue<>();
		for (int i = 0; i < 10; i++) {
			loopqueue.enqueue(i);
			System.out.println(loopqueue);
			if (i % 3 == 2) { //i=2,5,8的时候出队,测试缩容与扩容
				loopqueue.dequeue();
				System.out.println(loopqueue);
			}
		}
	}
}

测试结果

java队列任务重复执行问题 java循环队列queue_java队列任务重复执行问题


循环队列与顺序队列对比

  • 当入队和出队元素逐渐增多时,循环队列的效率明显高于顺序队列
  • 循环队列与顺序队列效率所产生的差距,主要是由出队操作引起的(顺序队列出队需要队列每个元素向前移动一次,时间复杂度为O(n);循环队列出队操作只需要改变头节点的指向即可,时间复杂度为O(1)。)