队列,即FIFO(First In First Out),它的特点是先进先出,在一些应用方面,队列的内存特点显得的尤为重要,如在项目中用到的消息队列。接下来看一下实现队列需要的方法。
ArrayQueue()
无参构造,使用默认参数初始化队列的容量
ArrayQueue(int capacity)
有参构造,初始化capacity容量大小的队列
getSize()
得到队列中元素个数
getCapacity()
获取队列的容量大小
isEmpty()
判断队列是否为空
enqueue()
入队
dequeue()
出队
getFront()
获取队头的元素
toString()
简单队列是基于动态数组实现,所以先贴出动态数组的代码。
/**
* @Author: Cui
* @Date: 2020/7/4
* @Description: 动态数组
*/
public class Array<E> {
private E[] data;
private int size;
//构造函数,传入数组的容量capacity构造Array
public Array(int capacity){
data = (E[])new Object [capacity];
size = 0;
}
//无参构造,默认数组容量为10
public Array(){
this(10);
}
//获取数组中的元素个数
public int getSize(){
return size;
}
//获取数组的容量
public int getCapacity(){
return this.data.length;
}
//返回数组是否为空
public boolean isEmpty(){
return size == 0;
}
//向所有的元素后添加一个新元素
public void addLast(E e){
add(size,e);
}
//向数组首地址添加一个新元素
public void addFirst(E e){
add(0,e);
}
//添加元素
public void add(int index,E e){
if(index < 0 || index > size){
throw new IllegalArgumentException("index<0 or 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 ++;
}
private void resize(int capacity) {
E[] newData = (E[]) new Object[capacity];
for(int i=0;i<size;i++){
newData[i] = data[i];
}
data = newData;
}
//从数组中删除index位置的元素,返回删除的元素
public E remove(int index){
if(index < 0 || index > size){
throw new IllegalArgumentException("index<0 or index>size");
}
E ret = data[index];
for(int i = index+1 ; i < size ; i++){
data[i-1] = data[i];
}
size--;
//动态缩容
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);
}
//查找指定位置的元素
public E get(int index){
return data[index];
}
//获取数组的最后一个元素
public E getLast(){
return get(size-1);
}
//获取数组的第一个元素
public E getFirst(){
return get(0);
}
//查询是否包含
public boolean contains(E e){
for(int i=0;i<size;i++){
if(data[i].equals(e)){
return true;
}
}
return false;
}
//修改指定下标的元素,并返回修改前的元素
public E set(int index,E e){
if(index < 0 || index > size){
throw new IllegalArgumentException("index<0 or index>size");
}
E e1 = data[index];
data[index] = e;
return e1;
}
//查找元素并返回元素的下标,如果不存在则返回-1
public int find(E e){
for(int i = 0; i < size; i++){
if(data[i].equals(e)){
return i;
}
}
return -1;
}
@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();
}
}
队列接口
public interface Queue<E> {
int getSize();
boolean isEmpty();
void enqueue(E e);
E dequeue();
E getFront();
}
队列实现类
/**
* @Author: Cui
* @Date: 2020/7/9
* @Description:
*/
public class ArrayQueue<E> implements Queue<E> {
private Array<E> array;
public ArrayQueue(){
array = new Array<>();
}
public ArrayQueue(int capacity){
array = new Array<>(capacity);
}
@Override
public int getSize() {
return array.getSize();
}
public int getCapacity(){
return array.getCapacity();
}
@Override
public boolean isEmpty() {
return array.isEmpty();
}
@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 stringBuilder = new StringBuilder();
stringBuilder.append("Queue:");
stringBuilder.append("front:[");
for(int i = 0; i < array.getSize(); i++){
stringBuilder.append(array.get(i));
if(i != array.getSize()-1){
stringBuilder.append(",");
}
}
stringBuilder.append("] tail");
return stringBuilder.toString();
}
}
测试
public class Main {
public static void main(String[] args) {
ArrayQueue<Integer> queue = new ArrayQueue<>();
for(int i = 0;i < 10;i++){
queue.enqueue(i);
System.out.println(queue);
if(i % 3 == 2){
queue.dequeue();
System.out.println(queue);
}
}
}
}
结果
简单队列的复杂度分析:
出队每次都要移动后边其他元素,所以复杂度为O(n)。在此,我们引入循环队列。循环队列简化出队的复杂度主要在于当有元素出队时不必改变其他元素的位置,而是改变队头的位置来实现的。
这里的循环队列继承了队列接口,但不依赖于动态数组。
/**
* @Author: Cui
* @Date: 2020/7/9
* @Description:
*/
public class LoopQueue<E> implements Queue<E> {
private E[] data;
private int front,tail;
private int 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 int getSize() {
return size;
}
@Override
public boolean isEmpty() {
return front == tail;
}
@Override
public void enqueue(E e) {
if((tail+1) % data.length == front){
resize(getCapacity()*2);
}
data[tail] = e;
tail = (tail + 1) % data.length;
size ++;
}
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 E dequeue() {
if(isEmpty()){
throw new IllegalArgumentException("队列为空");
}
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("队列为空");
}
return data[front];
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("Queue: size = %d , capacity = %d\n",size,getCapacity()));
stringBuilder.append("front:[");
for(int i = front; i != tail; i = (i + 1) % data.length){
stringBuilder.append(data[i]);
if((i + 1) % data.length != tail){
stringBuilder.append(",");
}
}
stringBuilder.append("] tail");
return stringBuilder.toString();
}
}
测试代码
/**
* @Author: Cui
* @Date: 2020/7/4
* @Description:
*/
public class Main {
public static void main(String[] args) {
LoopQueue<Integer> queue = new LoopQueue<>(20);
for(Integer i = 0;i < 10;i++){
queue.enqueue(i);
System.out.println(queue);
if(i % 3 == 2){
queue.dequeue();
System.out.println(queue);
}
}
}
}
结果
在循环队列中,出队的复杂度也为O(1)。这里来粗略的测试一下当出队入队数量过大时简单队列和循环队列的效率差距。
import java.util.Random;
/**
* @Author: Cui
* @Date: 2020/7/4
* @Description:
*/
public class Main {
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;
}
public static void main(String[] args) {
//简单队列
int opCount = 100000;
ArrayQueue<Integer> arrayQueue = new ArrayQueue<>();
double time = testQueue(arrayQueue,opCount);
System.out.println("简单队列所需时间:"+time);
//循环队列
LoopQueue<Integer> loopQueue = new LoopQueue<>();
double time1 = testQueue(loopQueue,opCount);
System.out.println("循环队列所需时间:"+time1);
}
}
结果
简单队列比循环队列所用的时间高出两个数量级! -_-!