队列(Queue)是一种常见的数据结构,用于存储和管理元素,其中元素按照先进先出(First-In-First-Out,FIFO)的原则进行操作。简单来说,队列就像是排队等候服务的人群,最先到达的人将首先接受服务,而后到达的人则需要等待前面的人完成服务后才能接受服务。
队列的主要特性包括:
- 入队(Enqueue):将一个元素添加到队列的末尾。
- 出队(Dequeue):从队列的头部移除并返回第一个元素。
- 头部(Front):队列的第一个元素。
- 尾部(Rear/Back):队列的最后一个元素。
- 队列大小(Size):队列中元素的数量。
队列可以用于许多场景,例如任务调度、广度优先搜索算法(BFS)、多线程编程、打印任务管理等。在编程中,队列常常通过数组或链表来实现。数组实现的队列称为顺序队列,链表实现的队列称为链式队列。
由于队列可以有多种实现方法,所以面向接口,便于规范。
先创建一个 Queue 接口:
public interface Queue<E>
{
/**
* 向尾部插入值
* @param value 待插入的值
* @return 插入成功返回true,否则返回false
*/
boolean offer(E value);
/**
* 从头部删除值
* @return 如果队列为空,返回null,否则返回队头的值
*/
E poll();
/**
* 获取队头的值
* @return 如果队列为空,返回null,否则返回队头的值
*/
E peek();
/**
* 检查队列是否为空
* @return 如果队列为空,返回true,否则返回false
*/
boolean isEmpty();
/**
* 判断队列是否已满
* @return 满返回 true ;否则 false
*/
boolean isFall();
}
我们先用数组来实现,其实光数组实现就有很多种实现方式,我这里挑了一个比较好方式:
public class ArrayQueue3<E> implements Queue<E>,Iterable<E>
{
private final E[] array;
private int head;
private int tail;
/**
* 有参构造创建队列
* @param capacity 队列容量
*/
public ArrayQueue3(int capacity)
{
array = (E[])new Object[capacity];
head = 0;
tail = 0;
}
/**
* 重写迭代器
*/
@Override
public Iterator<E> iterator()
{
return new Iterator<E>()
{
private int p = head;//从队头开始
/**
* 判断是否有下一个元素
* @return 如果有返回true,否则返回false,next()方法终止
*/
@Override
public boolean hasNext()
{
return p != tail; //如果p == tail,说明已经到队尾了,没有下一个元素了
}
/**
* 获取下一个元素
* @return 下一个元素
* p % array.length 为了防止p超出数组长度,因为p会一直增加。
*/
@Override
public E next()
{
E value = array[p % array.length];
p++;//p自增,为下次调用next()做准备
return value;
}
};
}
/**
* 实现isEmpty方法
* @return 如果队头指针等于队尾指针,说明队列为空,返回true,否则返回false
*/
@Override
public boolean isEmpty()
{
return head == tail;
}
/**
* 实现isFall方法
* @return 如果队尾指针减去队头指针等于数组长度,说明队列已满,返回true,否则返回false
*/
@Override
public boolean isFall()
{
return tail - head == array.length;
}
/**
* 实现offer方法
* @return 如果队列已满,返回false,添加成功返回true
*/
@Override
public boolean offer(E value)
{
if(isFall())
{
return false;
}
array[tail % array.length] = value;//向队尾添加元素
tail++;//队尾指针后移
return true;
}
/**
* 实现poll方法
* @return 如果队列为空,返回null,否则返回队头元素,并将队头指针后移,相当于删除队头元素
*/
@Override
public E poll()
{
if (isEmpty())
{
return null;
}
E value = array[head % array.length];
head++;
return value;
}
/**
* 实现peek方法
* @return 如果队列为空,返回null,否则返回队头元素
*/
@Override
public E peek()
{
if (isEmpty())
{
return null;
}
return array[head % array.length];
}
}
完了吗?有没有发现上面有什么问题?
int 是不是有个长度限制,最大支持到2147483647,超出会变成负数,而head,tail 会一直自增,但是数组索引只支持int 类型,且必须为正数,所以我们要处理一下,不然超出范围就不行了。
第一种:
在C家族的语言中都支持声明无符号整数,就连最像java的C#都支持 uint 类型,但Java就没有。嘿,气人不。
所以只能曲线救国:用 Integer.toUnsignedLong() 方法来转成无符号长整型,再强转成int
每个数组变量都要变成这样:
array[(int)Integer.toUnsignedLong(n)]
但是这种方法虽然可以解决问题但效率不高,所以还有第二种;
第二种:
采用二进制运算 ‘按位与’:‘&’
这种运算简而言之是将两个二进制数进行比较,如果相同位置上的两个数都为 1 就保留 1 否则全换成 0
比如:
int a = 5; // 二进制:0101
int b = 3; // 二进制:0011
int result = a & b; // 结果:0001 (1的二进制表示)
而一个2的n次方 - 1 的数作为除数,那它的余数在二进制上,就等于 被除数 & 除数
所以在保证数组的长度为一个2的n次方的前提下,就可以改成:
array[head & (array.length - 1)]
这种形式
所以现在的问题是要保证数组的长度为2的n次方
可以采用·:
方式一:
构造函数里面抛异常,只接受容量为2的n次方
public ArrayQueue3(int capacity)
{
if ((capacity & capacity - 1) != 0)
{
throw new IllegalArgumentException("capacity 必须是2的幂");
}
array = (E[])new Object[capacity];
head = 0;
tail = 0;
}
方式二:扩容成2的n次方
先用 (int)(Math.log10(capacity ) / Math.log10(2))得到以2为底capacity的对数的整数部分,
然后加一,就是扩容后的容量的那个2的次方,保证了其始终为2的n次方
int n = (int)(Math.log10(capacity) / Math.log10(2)) + 1;
capacity = 1 << n;//2的n次方的二进制运算方法,1左移一位就是2的一次方,左移两位就是2的二次方......
但是还有一个问题,如果这个数刚好是2的n次方,那么就没有必要去加那一个一,所以先让capacity去减一,以满足所有情况:
int n = (int)(Math.log10(capacity - 1) / Math.log10(2)) + 1;
capacity = 1 << n;
当然,这一步还有一种更花哨的写法,全部基于二进制运算:
capacity -= 1;
capacity |= capacity >> 1;
capacity |= capacity >> 2;
capacity |= capacity >> 4;
capacity |= capacity >> 8;
capacity |= capacity >> 16;
capacity += 1;
这个就是纯数学了,有兴趣的可以研究一下。嘿嘿。
下面是改好后的全部代码:
public class ArrayQueue3<E> implements Queue<E>,Iterable<E>
{
private final E[] array;
private int head;
private int tail;
/**
* 有参构造创建队列
* @param capacity 队列容量
*/
public ArrayQueue3(int capacity)
{
// if ((capacity & capacity - 1) != 0)
// {
// throw new IllegalArgumentException("capacity 必须是2的幂");
// }
//
// int n = (int)(Math.log10(capacity - 1) / Math.log10(2)) + 1;
// capacity = 2 << n;
capacity -= 1;
capacity |= capacity >> 1;
capacity |= capacity >> 2;
capacity |= capacity >> 4;
capacity |= capacity >> 8;
capacity |= capacity >> 16;
capacity += 1;
array = (E[])new Object[capacity];
head = 0;
tail = 0;
}
/**
* 重写迭代器
*/
@Override
public Iterator<E> iterator()
{
return new Iterator<E>()
{
private int p = head;//从队头开始
/**
* 判断是否有下一个元素
* @return 如果有返回true,否则返回false,next()方法终止
*/
@Override
public boolean hasNext()
{
return p != tail; //如果p == tail,说明已经到队尾了,没有下一个元素了
}
/**
* 获取下一个元素
* @return 下一个元素
* p % array.length 为了防止p超出数组长度,因为p会一直增加。
*/
@Override
public E next()
{
E value = array[p & (array.length - 1)];
//E value = array[(int)Integer.toUnsignedLong(p % array.length)];
p++;//p自增,为下次调用next()做准备
return value;
}
};
}
/**
* 实现offer方法
* @return 如果队列已满,返回false,添加成功返回true
*/
@Override
public boolean offer(E value)
{
if(isFall())
{
return false;
}
array[tail & (array.length - 1)] = value;//向队尾添加元素
tail++;//队尾指针后移
return true;
}
/**
* 实现poll方法
* @return 如果队列为空,返回null,否则返回队头元素,并将队头指针后移,相当于删除队头元素
*/
@Override
public E poll()
{
if (isEmpty())
{
return null;
}
E value = array[head & (array.length - 1)];
head++;
return value;
}
/**
* 实现peek方法
* @return 如果队列为空,返回null,否则返回队头元素
*/
@Override
public E peek()
{
if (isEmpty())
{
return null;
}
return array[head & (array.length - 1)];
}
/**
* 实现isEmpty方法
* @return 如果队头指针等于队尾指针,说明队列为空,返回true,否则返回false
*/
@Override
public boolean isEmpty()
{
return head == tail;
}
/**
* 实现isFall方法
* @return 如果队尾指针减去队头指针等于数组长度,说明队列已满,返回true,否则返回false
*/
@Override
public boolean isFall()
{
return tail - head == array.length;
}
}
然后是链表实现:
public class LinkedListQueue<E> implements Queue<E>,Iterable<E>
{
/**
* 链表节点类
* @param <E> 节点值的类型
* value 节点值
* next 下一个节点
*/
private static class Node<E>
{
E value;
Node next;
public Node(E value,Node<E> next)
{
this.value=value;
this.next=next;
}
}
private Node<E> head = new Node<>(null,null);//头节点
private Node<E> tail = head;//尾节点
private int size;//队列长度
private int capacity = Integer.MAX_VALUE;//队列容量,默认为Integer.MAX_VALUE
/**
* 初始化队列,使得队尾指向队头
*/
{
tail.next = head;
}
public LinkedListQueue()
{
}
/**
* 有参构造创建队列
* @param capacity 队列容量
*/
public LinkedListQueue(int capacity)
{
this.capacity = capacity;
}
/**
* 实现offer方法
* @param value 待插入的值
* @return 如果队列已满,返回false
* 否则,将值插入队尾,返回true
*/
@Override
public boolean offer(E value)
{
if (size >= capacity)
{
return false;
}
Node<E> added = new Node<>(value,head);
tail.next = added;
tail = added;
size ++;
return true;
}
/**
* 实现poll方法
* @return 如果队列为空,返回null
* 否则,返回队头的值,并将队头指针后移,相当于删除队头元素
*/
@Override
public E poll()
{
if (isEmpty())
{
return null;
}
else
{
Node<E> first = head.next;
head.next = first.next;
if (first == tail)//如果队列只有一个元素,删除后队列为空,需要重新让tail=head,且将tail重新指向head
{
tail = head;
tail.next = head;
}
size --;
return first.value;
}
}
/**
* 实现peek方法
* @return 如果队列为空,返回null
* 否则,返回队头的值
*/
@Override
public E peek()
{
if (isEmpty())
{
return null;
}
else
{
return (E) head.next.value;
}
}
/**
* 实现isEmpty方法
* @return 如果队头指针等于队尾指针,说明队列为空,返回true,否则返回false
*/
@Override
public boolean isEmpty()
{
return head == tail;
}
/**
* 实现isFall方法
* @return 如果队列已满,返回true,否则返回false
*/
@Override
public boolean isFall() {
return false;
}
/**
* 实现iterator方法
* @return 返回一个迭代器
*/
@Override
public Iterator<E> iterator() {
//匿名内部类实现迭代器
return new Iterator<E>() {
Node<E> p = head.next;
/**
* 实现hasNext方法
* @return 如果p不等于head,说明还有元素,返回true,否则返回false
*/
@Override
public boolean hasNext() {
return p != head;
}
/**
* 实现next方法
* @return 返回p的值,并将p后移
*/
@Override
public E next() {
E value = p.value;
p = p.next;
return value;
}
};
}
}
链表实现起来比较简单