1. 使用数组实现一个简单的队列
/**
* ===========================
* 队列首部 00000000000000000000000000 队列尾部
* ===========================
*/
public class ArrayQueue<Element> implements Queue<Element>{
// 通过内部的array来实现
private Array<Element> array;
// 构造函数
public ArrayQueue(int capacity){
this.array = new Array<>(capacity);
}
// 默认的构造函数
public ArrayQueue(){
this.array = new Array<>();
}
@Override
public int getSize() {
return this.array.getSize();
}
@Override
public boolean isEmpty() {
return this.array.isEmpty();
}
public int getCapacity(){
return this.array.getCapacity();
}
@Override
public void enqueue(Element element) {
// 进入队列(数组的末尾来添加元素)
this.array.addLast(element);
}
@Override
public Element dequeue() {
// 出队列(删除最后一个元素),数组的第一个元素
return this.array.removeFirst();
}
@Override
public Element getFront() {
// 获取第一个元素(对首部的第一个元素)
return this.array.getFirst();
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
// 使用自定义的方式实现数组的输出
stringBuilder.append("Queue:");
// 开始实现数组元素的查询
stringBuilder.append("front [");
for (int i = 0; i < this.array.getSize(); i++) {
stringBuilder.append(this.array.get(i));
// 开始实现数组元素的回显(只要下表不是最后一个元素的话,就直接输出这个元素)
if (i != this.array.getSize()-1)
stringBuilder.append(", ");
}
stringBuilder.append("] tail");
// 实现数组元素的输出
return stringBuilder.toString();
}
}
2. 使用数组实现一个循环队列(维护一个size变量)
/**
* 循环队列的几个要点:
* 1. tail = head 说明队列就是满的
* 2. 循环队列需要空出来一个位置
*/
public class LoopQueue<E> implements Queue {
private E[] data;
private int head; // 首部指针
private int tail; // 尾部指针
private int size; // 可以通过head以及tail的位置情况去计算size的大小【注意是不能直接使用getCapacity的】
// 实现循环队列
public LoopQueue() {
// 设置一个队列的默认的大小
this(10);
}
// 循环队列的实现
public LoopQueue(int capacity) {
this.data = (E[]) new Object[capacity + 1];
// 需要多出来一个
this.head = 0;
this.tail = 0;
this.size = 0;
}
@Override
public int getSize() {
// 计算容量大小
return this.size;
}
// capacity表示的这个队列中最大可以容纳的元素个数【这是一个固定值】
@Override
public int getCapacity() {
// 由于队列默认占了一个空的位置,因此sizt = this.length-1
return this.data.length - 1;
}
// 当head和tail的值相同的时候队列满了
public boolean isEmpty() {
return head == tail;
}
@Override
public void enqueue(Object value) {
// 1. 开始判断队列有没有充满, 因为数组的下表是不会改变的,所以这里需要以数组的下标为一个参考点, 而不是this.data.length-14
if ((tail + 1) % this.data.length == head) {
// 2. 开始进行扩容, 原来的二倍, 由于默认的时候已经白白浪费了一个空间,因此这里就直接扩容为原来的二倍即可
resize(2 * getCapacity());
}
// 2. 如果没有满的话,就开始添加元素
this.data[tail] = (E) value;
// 3. 添加完毕之后(tail是一个循环的位置,不可以直接相加)
// this.tail = 2;
this.tail = (this.tail+1) % data.length; // data.length对于数组的下标
// int i = (this.tail + 1) % data.length;
// 4. 队里的长度
this.size++;
}
/**
* 开始resize数组元素
*
* @param capacity
*/
private void resize(int capacity) {
// 开始进行数组的扩容
// 1. 创建一个新的容器(默认多一个位置)
E[] newData = (E[]) new Object[capacity + 1];
// 2. 开始转移元素到新的容器
for (int i = 0; i < size; i++) {
int index = (head + i) % data.length;
newData[i] = data[index];
}
// 添加完毕之后,开始回收之前的data,data里面的数据一直是最新的数据
this.data = newData; // jvm的垃圾回收机制会自动回收内存data
// 3. 添加完毕
this.head = 0;
// 4. 指向尾部
this.tail = size;
}
@Override
public Object dequeue() {
// 1. 先来看下队列中有没有元素数据
if (this.size == 0)
throw new IllegalArgumentException("Empty queue cannot dequeue!");
// 2. 开始看一下内存的大小
Object ret = this.data[head];
// 3. 开始删除数据
this.data[head] = null;
// TODO 注意点:直接使用+1而不是使用++
// 4. 开始向后移动, 为了防止出错,尽量使用 this.head+1来进行操作,而不是使用 this.haad++, 否则可能会出现死循环的情况【特别注意!!!!!!!!!!!!】
this.head = (this.head+1) % data.length;
// 5. 计算新的容量大小
this.size--;
// 6. 开始进行缩容操作, d当容量为1, size为0的时候,就不需要缩小容量、、
if (this.size == this.getCapacity() / 4)
// 缩小为原来的一半大小的容量
resize(this.getCapacity() / 2);
// 获取出队列的元素
return ret;
}
@Override
public Object getFront() {
// 由于front的位置一直是队列的第一个元素,直接返回即可
if (this.size == 0)
throw new IllegalArgumentException("Empty queue cannot get element!");
return this.data[head];
}
@Override
public String toString() {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("Array : size=%d, capacity=%d; ", size, getCapacity()));
stringBuilder.append("LoopQueue head [");
// 第一个元素在head的位置,最后一个元素在tail-1的位置
// 注意循环队列的遍历方式,是一个循环
for (int i = this.head; i != tail; i = (i+1) % data.length) {
stringBuilder.append(data[i]);
// 只要不是最后一个元素的话, 由于循环条件中已经规定了i!=tail, 因此这里是不能直接这样来判断的
if ((i+1)%data.length == this.tail) {
// 此时就是最后一个元素
} else {
stringBuilder.append(", ");
}
}
stringBuilder.append("] tail");
return stringBuilder.toString();
}
}
3.使用数组实现一个循环队列(不维护size变量)
/**
* 使用数组实现一个循环队列的数据结构
*/
public class WithoutSizeLoopQueue<E> implements Queue<E> {
private E[] data;
private int head;
private int tail;
public WithoutSizeLoopQueue(int capacity) {
// 泛型不能直接被实例化, 由于循环队列默认会空出来一个位置,这里需要注意
this.data = (E[]) new Object[capacity + 1];
this.head = 0;
this.tail = 0;
}
public WithoutSizeLoopQueue() {
this(10);
}
@Override
public int getSize() {
// 计算数组的元素个数
if (tail >= head) {
return tail - head;
} else {
int offSet = head - tail;
return this.data.length - offSet;
}
}
@Override
public int getCapacity() {
return data.length - 1;
}
@Override
public void enqueue(E value) {
// 开始进入队列
// 1. 先来看下队列有没有满
if ((tail + 1) % data.length == head)
// 开始扩大容量
resize(2 * getCapacity());
// 2. 开始进入队列
data[tail] = value;
// 3. 开始移动下标
tail++;
// 4. 开始更新容量【没必要】
}
public void resize(int newCapacity) {
// 1. 更新为新的容量
E[] newData = (E[]) new Object[newCapacity + 1];
// 2. 开始转移数据到新的数组中去
for (int i = 0; i < getSize(); i++) {
newData[i] = data[(i + head) % data.length];
}
// 3. 销毁原来的数据信息
data = newData;
// 【错误警告】bug:移动到新的这个数组里面之后,需要重新改变head和tail的位置【bug】
tail = getSize(); // 尾部节点始终指向最后一个元素的后面一个位置, 此时如果直接使用getSize实际上获取到的是上一次的元素的个数,而不是最新的元素的个数信息(需要放在前面,否在获取到的size就不是同一个了,特别重要)
head = 0; // 头节点指向的是第一个元素
// 4. 直接销毁
newData = null;
}
@Override
public E dequeue() {
// 1.先来看下队列是否为空
if (getSize() == 0)
throw new IllegalArgumentException("Empty queue cannot dequeue!");
// 2.开始出head位置的元素
E value = data[head];
// 3. 删除元素
data[head] = null;
// 4. 移动head
head = (head+1) % data.length;
// 此时需要进行一次判断
if (getSize() == getCapacity() / 4 && getCapacity() / 2 != 0)
resize(getCapacity() / 2);
// 如果数组缩小容量之后,这里的
return value;
}
@Override
public E getFront() {
return dequeue();
}
@Override
public String toString(){
// 开始遍历输出队列的元素
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append(String.format("LoopQueue: size = %d, capacity = %d\n", getSize(), getCapacity()));
stringBuilder.append("front ");
// 从head位置开始遍历
for (int i = head; i != tail ; i = (i+1) % data.length) {
stringBuilder.append(data[i]);
if ((i + 1) % data.length != tail)
stringBuilder.append(", ");
}
return stringBuilder.append(" tail").toString();
}
}
4. 使用链表实现一个队列
/**
* 使用链表实现的队列
*/
public class LinkedListQueue<E> implements Queue<E>{
// 这是一个内部类,只能在这个类的内部可以访问(用户不需要了解底层是如何实现的)
private class Node {
// 用于存储元素
public E e;
// 用于存储下一个节点
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
}
// 定义队列需要的参数
private Node head, tail;
private int size;
public LinkedListQueue(){
this.head = null;
this.tail = null;
this.size = 0;
}
@Override
public int getSize() {
return this.size;
}
public boolean isEmpty(){
return this.size == 0;
}
@Override
public int getCapacity() {
return 0;
}
/**
* 入队
* @param value
*/
@Override
public void enqueue(E value) {
// 入队从尾部开始
if (tail == null){
// 1. 如果此时没有元素的话
tail = new Node(value);
head = tail;
}
else {
// 2. 如果已经存在了元素,那么队列尾部肯定是不为空的,而是指向了一个空节点
tail.next = new Node(value);
// 移动尾节点
tail = tail.next;
}
size++;
}
/**
* 出队
* @return
*/
@Override
public E dequeue() {
if (isEmpty())
throw new IllegalArgumentException("Empty queue cannot dequeue!");
// 1. 存储出队的元素
Node retNode = head;
// 2.head节点下移动
head = head.next;
// 3.删除原来的头节点
retNode.next = null; // 实际上删除的是这个节点的数据域
// 如果删除了最后一个元素之后
if (head == null)
tail = null;
size--;
return retNode.e;
}
@Override
public E getFront() {
if (isEmpty())
throw new IllegalArgumentException("Empty queue cannot dequeue!");
return head.e;
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
Node cur = head;
stringBuilder.append("head:");
// 1. 第一种循环遍历的方式, 注意这里判断的是每一次的cur是否为空, 而不是判断cur.next, 否则第一个元素是不能打印输出的
while (cur != null) {
stringBuilder.append(cur + "->");
// 继续向后移动节点
cur = cur.next;
}
stringBuilder.append("NULL tail");
return stringBuilder.toString();
}
}
5. 使用一个带有虚拟头结点的链表实现一个队列
/**
* 带有虚拟头结点的链表实现的队列
*/
public class DummyHeadLinkedListQueue<E> implements Queue<E> {
// 这是一个内部类,只能在这个类的内部可以访问(用户不需要了解底层是如何实现的)
private class Node {
// 用于存储元素
public E e;
// 用于存储下一个节点
public Node next;
public Node(E e, Node next) {
this.e = e;
this.next = next;
}
public Node(E e) {
this(e, null);
}
public Node() {
this(null, null);
}
@Override
public String toString() {
return e.toString();
}
}
// 定义一个虚拟头结点
private Node dummyHead, tail;
private int size;
public DummyHeadLinkedListQueue(){
this.dummyHead = new Node(null, null);
this.tail = null;
this.size = 0;
}
@Override
public int getSize() {
return this.size;
}
public Boolean isEmpty(){
return this.size == 0;
}
@Override
public int getCapacity() {
throw new IllegalArgumentException("LinkedList cannot getCapacity!");
}
@Override
public void enqueue(E value) {
// 开始入队列(从队列的尾部进行入队列)
// 1. 构造插入的节点
Node node = new Node(value);
// 2. 尾部插入节点 //bug : 由于默认的时候 tail为null,此时如果直接访问tail.next 是错误的
if (tail == null) {
dummyHead.next = node;
// 说明此时队列为空的
tail = node;
} else {
tail.next = node;
// 3. 尾部节点向后移动
tail = tail.next;
}
// 4. 队列长度增加
size++;
}
@Override
public E dequeue() {
// 元素出队列从链表的头部出去
// 1. 获取头结点的位置
Node head = dummyHead.next;
// 缓存出队的元素
Node retNode = head;
// 2. 移动节点
dummyHead.next = head.next;
// 4. 更新容量
size--;
head = null;
if (isEmpty())
tail = null; // bug修复
return (E) retNode;
}
@Override
public E getFront() {
return null;
}
@Override
public String toString(){
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Queue head:");
Node cur = dummyHead.next;
while (cur != null){
stringBuilder.append(cur + "->");
cur = cur.next;
}
return stringBuilder.append("null tail").toString();
}
}
以上就是创建队列实现的5中方式,每种方式都有各自的特点,就做个简单总结吧!
队列的时间复杂度分析:
+ 队列Queue[数组队列]
void enqueue(E) O(1) 均摊复杂度
E dequeue() O(n) 移出队列首部的元素,需要其他元素向前补齐
E getFront() O(1)
void dequeue() O(1)
Int getSize() O(1)
bool isEmpty() O(1)
+ 循环队列
void enqueue(E) O(1) 均摊复杂度
E dequeue() O(1)
E getFront() O(1)
void dequeue() O(1)
Int getSize() O(1)
bool isEmpty() O(1)
以上就是对于队列实现的几种方式的总结了。