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 ", 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)
以上就是对于队列实现的几种方式的总结了。