用数组实现队列(顺序队列&循环队列)
顺序队列
↘️
队列(先进先出)
几个问题:
- 队列方法:入队、出队
- 队列的存储:即队首队尾两个指针,
- 扩容:如果队列容量不够了,应该扩容,如果队尾没有位置了,队首有位置,应该把元素往前移
主要是上面三个问题,在代码中都有体现,上面的扩容方法借鉴了ArrayList
的扩容方法。
package com.helius.structure.queue;
import java.util.Arrays;
/**
* 用数组实现一个队列,即顺序队列
*/
public class ArrayQueue {
// 存储数据的数组
private Object[] elements;
//队列大小
private int size;
// 默认队列容量
private int DEFAULT_CAPACITY = 10;
// 队列头指针
private int head;
// 队列尾指针
private int tail;
private int MAX_ARRAY_SIZE = Integer.MAX_VALUE-8;
/**
* 默认构造函数 初始化大小为10的队列
*/
public ArrayQueue(){
elements = new Object[DEFAULT_CAPACITY];
initPointer(0,0);
}
/**
* 通过传入的容量大小创建队列
* @param capacity
*/
public ArrayQueue(int capacity){
elements = new Object[capacity];
initPointer(0,0);
}
/**
* 初始化队列头尾指针
* @param head
* @param tail
*/
private void initPointer(int head,int tail){
this.head = head;
this.tail = tail;
}
/**
* 元素入队列
* @param element
* @return
*/
public boolean enqueue(Object element){
ensureCapacityHelper();
elements[tail++] = element;//在尾指针处存入元素且尾指针后移
size++;//队列元素个数加1
return true;
}
private void ensureCapacityHelper() {
if(tail==elements.length){//尾指针已越过数组尾端
//判断队列是否已满 即判断数组中是否还有可用存储空间
//if(size<elements.length){
if(head==0){
//扩容
grow(elements.length);
}else{
//进行数据搬移操作 将数组中的数据依次向前挪动直至顶部
for(int i= head;i<tail;i++){
elements[i-head]=elements[i];
}
//数据搬移完后重新初始化头尾指针
initPointer(0,tail-head);
}
}
}
/**
* 扩容
* @param oldCapacity 原始容量
*/
private void grow(int oldCapacity) {
int newCapacity = oldCapacity+(oldCapacity>>1);
if(newCapacity-oldCapacity<0){
newCapacity = DEFAULT_CAPACITY;
}
if(newCapacity-MAX_ARRAY_SIZE>0){
newCapacity = hugeCapacity(newCapacity);
}
elements = Arrays.copyOf(elements,newCapacity);
}
private int hugeCapacity(int newCapacity) {
return (newCapacity>MAX_ARRAY_SIZE)? Integer.MAX_VALUE:newCapacity;
}
/**
* 出队列
* @return
*/
public Object dequeue(){
if(head==tail){
return null;//队列中没有数据
}
Object obj=elements[head++];//取出队列头的元素且头指针后移
size--;//队列中元素个数减1
return obj;
}
/**
* 获取队列元素个数
* @return
*/
public int getSize() {
return size;
}
}
测试用例
public class TestArrayQueue {
public static void main(String[] args) {
ArrayQueue queue = new ArrayQueue(4);
//入队列
queue.enqueue("helius1");
queue.enqueue("helius2");
queue.enqueue("helius3");
queue.enqueue("helius4");
//此时入队列应该走扩容的逻辑
queue.enqueue("helius5");
queue.enqueue("helius6");
//出队列
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
//此时入队列应该走数据搬移逻辑
queue.enqueue("helius7");
//出队列
System.out.println(queue.dequeue());
//入队列
queue.enqueue("helius8");
//出队列
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
//入队列
queue.enqueue("helius9");
queue.enqueue("helius10");
queue.enqueue("helius11");
queue.enqueue("helius12");
//出队列
System.out.println(queue.dequeue());
System.out.println(queue.dequeue());
}
}
结果:
helius1
helius2
helius3
helius4
helius5
helius6
helius7
helius8
null
helius9
helius10
循环队列
用java实现循环队列的方法:
-
增加一个属性size用来记录目前的元素个数。目的是当head=rear的时候,通过size=0还是size=数组长度,来区分队列为空,或者队列已满。
-
数组中只存储数组大小-1个元素,保证rear转一圈之后不会和head相等,也就是队列满的时候,rear+1=head,中间刚好空一个元素。
当rear=head的时候,一定是队列空了。
队列(Queue)两端允许操作的类型不一样:
可以进行删除的一端称为队头,这种操作也叫出队dequeue;
可以进行插入的一端称为队尾,这种操作也叫入队enqueue。
队列的示意图
实现队列时,要注意的是假溢出现象,如上图的最后一幅图。
如图所示的假溢出现象,顺序队列可以如此,循环队列我们可以让这个尾指针指向front前面的元素,这也正符合我们想要的循环队列的定义。
解决办法:使用链式存储,这显然可以。在顺序存储时,我们常见的解决办法是把它首尾相接,构成循环队列,这可以充分利用队列的存储空间。
循环队列示意图:
在上图中,front指向队列中第一个元素,rear指向队列队尾的下一个位置。
但依然存在一个问题:当front和rear指向同一个位置时,这代表的是队空还是队满呢?大家可以想象下这种情景。
解决这种问题的常见做法是这样的:
使用一标记,用以区分这种易混淆的情形。
牺牲一个元素空间。当front和rear相等时,为空;当rear的下一个位置是front时,为满。
如下图:
下面我们给出循环队列,并采用第二种方式,即牺牲一个元素空间来区分队空和队满的代码.
几个重点:
1、front指向队头,rear指向队尾的下一个位置。
2、队为空的判断:frontrear;队为满的判断:(rear+1)%MAXSIZEfront。
上面说的rear即为代码中的的tail
/**
* 使用数组实现循环队列
* @author Helius
*/
public class CirculiQueue {
//存储队列数据的数组
private Object[] elements;
//默认数组容量
private int DEFAULT_CAPACITY=10;
//队列中元素个数
private int size;
// 队列头指针
private int head;
//队列尾指针
private int tail;
/**
* 默认构造函数
*/
public CirculiQueue(){
elements = new Object[DEFAULT_CAPACITY];
}
/**
* 通过传入的容量参数构造队列
* @param capacity
*/
public CirculiQueue(int capacity){
elements = new Object[capacity];
}
/**
* 元素入队列
* @param element
* @return
*/
public boolean enqueue(Object element){
//判断队列是否已满
if(head == (tail+1)%elements.length){
//队列已满
return false;
}
//将元素存入tail位置上
elements[tail]=element;
//尾指针后移
/*tail++;
if(tail==elements.length){
tail = 0;
}*/
tail = (tail+1)%elements.length;
size++;
return true;
}
/**
* 元素出队列
* @return
*/
public Object dequeue(){
//判断队列是否为空
if(head==tail){
return null;
}
//获取head位置上的元素
Object element = elements[head];
//头指针后移
/*head++;
if(head==elements.length){
head = 0;
}*/
head = (head+1)%elements.length;
size--;
return element;
}
/**
* 获取队列大小
* @return
*/
public int getSize() {
return size;
}
}
这里也添加了size属性,可以稍做修改,来实现第一种方式。