队列(Queue)
- 队列是一种特殊的线性表,只能在头尾两端进行操作;
- 队尾(rear):只能在队尾添加元素,一般叫做enQueue,入队;
- 队头(front):只能从队头移除元素,一般叫做deQueue,出队。
- 遵循先进先出的原则:First In First Out,FIFO
结构如下图所示:
队列的接口设计
- 存储元素的数量;
- 元素是否为空;
- 入队 enQueue -- 添加元素;
- 出队 deQueue -- 移除元素;
- 获取队头元素;
- 清空元素:clear
队列的内部实现,优先使用双向链表,因为队列主要是进行首尾节点的操作,代码实现如下所示:
/**
* 队列 -- Queue
* @param <E>
*/
public class YYQueue<E> {
//双向链表 -- 数据的存储集合
private YYLinkedList<E> linkedList = new YYLinkedList();
public int size(){
return linkedList.size;
}
public boolean isEmpty(){
return linkedList.isEmpty();
}
/**
* 入列
* @param element
*/
public void enQueue(E element){
linkedList.addObject(element);
}
/**
* 出列
* @return
*/
public E deQueue(){
return linkedList.removeObjectAtIndex(0);
}
/**
* 获取队头
* @return
*/
public E front(){
return linkedList.objectAtIndex(0);
}
/**
* 清空元素
*/
public void clear(){
linkedList.clear();
}
}
用栈Stack实现队列
- 原理:准备两个栈inStack与outStack;
- 数据元素入队时,push到inStack中;
- 数据元素出队时:
- 如果outStack为空,将inStack所有元素逐一弹出,push到outStack中,最后outStack弹出栈顶元素;
- 如果outStack不为空,outStack弹出栈顶元素。
代码实现如下:
import java.util.Stack;
public class MyQueue {
private Stack<Integer> inStack;
private Stack<Integer> outStack;
/** Initialize your data structure here. */
public MyQueue() {
inStack = new Stack<>();
outStack = new Stack<>();
}
/** Push element x to the back of queue. */
public void enQueue(int x) {
inStack.push(x);
}
/** Removes the element from in front of queue and returns that element. */
public int deQueue() {
if (outStack.isEmpty()){
while (!inStack.isEmpty()){
outStack.push(inStack.pop());
}
}
return outStack.pop();
}
/** Get the front element. */
public int front() {
if (outStack.isEmpty()){
while (!inStack.isEmpty()){
outStack.push(inStack.pop());
}
}
return outStack.peek();
}
/** Returns whether the queue is empty. */
public boolean empty() {
return inStack.isEmpty() && outStack.isEmpty();
}
}
- 这里使用的是java系统的stack来实现的;
- outStack.peek()是获取栈顶元素;
- 只有当inStack与outStack都为空,队列才为空。
双端队列(Deque)
- 双端队列是能在头尾两端添加,删除的队列;
- Deque是Double ended queue的简称。
双端队列的接口设计:
- 元素的数量size;
- 是否为空;
- 从队尾入队:enQueueRear;
- 从队头入队:enQueueFront;
- 从队尾出队:deQueueRear;
- 从队头出队:deQueueFront;
- 获取队头元素:front;
- 获取队尾元素:rear;
- 清空元素:clear
双端队列的内部使用双向链表作为数据存储的集合,代码实现如下:
/**
* 双端队列
* @param <E>
*/
public class YYDeque<E> {
//双向链表 -- 数据的存储集合
private YYLinkedList<E> linkedList = new YYLinkedList();
public int size(){
return linkedList.size;
}
public boolean isEmpty(){
return linkedList.isEmpty();
}
/**
* 从队尾入列
* @param element
*/
public void enQueueRear(E element){
linkedList.addObject(element);
}
/**
* 从队头入列
* @param element
*/
public void enQueueFront(E element){
linkedList.insertObjectAtIndex(0,element);
}
/**
* 从队尾出队
* @return
*/
public E deQueueRear(){
return linkedList.removeObjectAtIndex(linkedList.size-1);
}
/**
* 从队头出队
* @return
*/
public E deQueueFront(){
return linkedList.removeObjectAtIndex(0);
}
/**
* 获取队尾
* @return
*/
public E rear(){
return linkedList.objectAtIndex(linkedList.size-1);
}
/**
* 获取队头
* @return
*/
public E front(){
return linkedList.objectAtIndex(0);
}
/**
* 清空元素
*/
public void clear(){
linkedList.clear();
}
}
循环队列(Circle Queue)
- 循环队列的底层用数组实现,代码实现如下所示:
/**
* 循环队列
* @param <E>
*/
public class YYCircleQueue<E> {
//记录队头索引index
private int front = 0;
private int size;
private E[] elements;
private static final int DEFAULT_CAPACITY = 10;
public YYCircleQueue() {
elements = (E[])new Object[DEFAULT_CAPACITY];
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
/**
* 在队尾入队
* @param element
*/
public void enQueue(E element){
//动态扩容
ensureCapacity(size + 1);
int index = (front + size) % elements.length;
elements[index] = element;
size++;
}
/**
* 在队头出队
* @return
*/
public E deQueue(){
E frontElement = elements[front];
elements[front] = null;
front = (front + 1) % elements.length;
size--;
return frontElement;
}
/**
* 获取队头
* @return
*/
public E front(){
return elements[front];
}
/**
* 清空元素
*/
public void clear(){
for (int i = 0;i < size;i++){
int index = index(i);
elements[i] = null;
}
size = 0;
front = 0;
}
/**
* 获取真实index
* @param index
* @return
*/
private int index(int index){
return (front + index) % elements.length;
}
/**
* 保证要有capacity的容量
* @param capacity
*/
private void ensureCapacity(int capacity){
int oldCapacity = elements.length;
//不需要扩容
if (oldCapacity >= capacity) return;
//需要进行数组的扩容,新的容量大小是原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >>1);
//重新申请内存空间
E[] newElements = (E[])new Object[newCapacity];
//将原来内存空间上的元素赋值到新开辟的内存空间中
for (int i = 0;i < size;i++){
int index = (i + front) % elements.length;
newElements[i] = elements[index];
}
elements = newElements;
//front重置为0
front = 0;
System.out.println(oldCapacity + "扩容为" + newCapacity);
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("capacity=").append(elements.length).append(" size=").append(size).append("front=").append(front).append(", [");
for (int i = 0;i < elements.length;i++){
if (i != 0){
sb.append(",");
}
sb.append(elements[I]);
}
sb.append("]");
return sb.toString();
}
}
队列的初始结构图如下:
队列容量为7,front记录队头索引初始值为0;这里的front是实现循环队列的精髓所在,通过更改front的值,始终指向队头元素,从而避免移动数组元素。
public void enQueue(E element)
:数据元素在队尾入队,代码实现如下:
/**
* 在队尾入队
* @param element
*/
public void enQueue(E element){
//动态扩容
ensureCapacity(size + 1);
int index = (front + size) % elements.length;
elements[index] = element;
size++;
}
- 第一种情况:front = 2,size = 3,如下图所示:
往队尾添加元素66,如果直接使用size作为索引就会覆盖元素33,这样明显有问题,所以添加元素66在数组中索引index =
(size + front) % elements.length = 5
,最终队头为元素33,队尾为元素55;第二种情况:front = 2,size = 5,如下图所示:
往队尾添加元素88,由于数组容量为7,在元素77后面没有空间了,所以元素88需要添加到最前面,其在数组中的索引index =
(size + front) % elements.length = 0
;最终队头为元素33,队尾为元素88;第三种情况:front = 2,size = 6,如下图所示:
往队尾添加元素99,其在数组中的索引index =
(size + front) % elements.length = 1
;最终队头为元素33,队尾为元素99;public E deQueue()
:数据元素在队头出队,代码实现如下:
/**
* 在队头出队
* @return
*/
public E deQueue(){
E frontElement = elements[front];
elements[front] = null;
front = (front + 1) % elements.length;
size--;
return frontElement;
}
- 第一种情况:front = 5,size = 3,如下图所示:
- 现在将队头元素66移除,那么front向后挪动一位即front++ = 6;如下图所示:
- 然后再将队头元素77移除,此时front如果再+1,就会出现问题,队头应该指向88才对,即front = (front + 1) % elements.length = 0;最终队头为元素88,队尾为元素88;
-
private void ensureCapacity(int capacity)
循环队列的内部是使用数组实现的,所以会存在动态扩容的问题,代码如下:
/**
* 保证要有capacity的容量
* @param capacity
*/
private void ensureCapacity(int capacity){
int oldCapacity = elements.length;
//不需要扩容
if (oldCapacity >= capacity) return;
//需要进行数组的扩容,新的容量大小是原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >>1);
//重新申请内存空间
E[] newElements = (E[])new Object[newCapacity];
//将原来内存空间上的元素赋值到新开辟的内存空间中
for (int i = 0;i < size;i++){
int index = (i + front) % elements.length;
newElements[i] = elements[index];
}
elements = newElements;
//front重置为0
front = 0;
System.out.println(oldCapacity + "扩容为" + newCapacity);
}
原理如下图所示:
- 队列内部数组的初始容量为7,此时数据已经存满,size = 7,且队头为66,队尾为55;此时再向队头添加元素,由于容量不够,需进行动态扩容,创建一个新的容量为原来容量1.5倍的新数组,需要将原来数组中的元素,一一映射到新数组中;
循环队列的测试代码如下:
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
YYCircleQueue queue = new YYCircleQueue();
for (int i = 1;i <= 10;i++){
queue.enQueue(i);
}
System.out.println(queue);
queue.deQueue();
System.out.println(queue);
queue.deQueue();
System.out.println(queue);
queue.enQueue(88);
System.out.println(queue);
queue.enQueue(188);
System.out.println(queue);
queue.enQueue(288);
System.out.println(queue);
}
测试结果:
循环双端队列
代码实现如下所示:
/**
* 循环双端队列
* @param <E>
*/
public class YYCircleDeque<E> {
//记录队头索引index
private int front = 0;
private int size;
private E[] elements;
private static final int DEFAULT_CAPACITY = 10;
public YYCircleDeque() {
elements = (E[])new Object[DEFAULT_CAPACITY];
}
public int size(){
return size;
}
public boolean isEmpty(){
return size == 0;
}
/**
* 从队尾入列
* @param element
*/
public void enQueueRear(E element){
//动态扩容
ensureCapacity(size + 1);
int index = (front + size) % elements.length;
elements[index] = element;
size++;
}
/**
* 从队头入列
* @param element
*/
public void enQueueFront(E element){
//动态扩容
ensureCapacity(size + 1);
front = front - 1;
if (front < 0){
front = front + elements.length;
}else {
front = front % elements.length;
}
elements[front] = element;
size++;
}
/**
* 从队尾出队
* @return
*/
public E deQueueRear(){
int rearindex = index(size - 1);
E rear = elements[rearindex];
elements[rearindex] = null;
size--;
return rear;
}
/**
* 从队头出队
* @return
*/
public E deQueueFront(){
E frontElement = elements[front];
elements[front] = null;
front = (front + 1) % elements.length;
size--;
return frontElement;
}
/**
* 获取队尾
* @return
*/
public E rear(){
return elements[index(size-1)];
}
/**
* 获取队头
* @return
*/
public E front(){
return elements[front];
}
/**
* 清空元素
*/
public void clear(){
for (int i = 0;i < size;i++){
int index = index(i);
elements[i] = null;
}
size = 0;
front = 0;
}
/**
* 获取真实index
* @param index
* @return
*/
private int index(int index){
return (front + index) % elements.length;
}
/**
* 保证要有capacity的容量
* @param capacity
*/
private void ensureCapacity(int capacity){
int oldCapacity = elements.length;
//不需要扩容
if (oldCapacity >= capacity) return;
//需要进行数组的扩容,新的容量大小是原来的1.5倍
int newCapacity = oldCapacity + (oldCapacity >>1);
//重新申请内存空间
E[] newElements = (E[])new Object[newCapacity];
//将原来内存空间上的元素赋值到新开辟的内存空间中
for (int i = 0;i < size;i++){
int index = (i + front) % elements.length;
newElements[i] = elements[index];
}
elements = newElements;
front = 0;
System.out.println(oldCapacity + "扩容为" + newCapacity);
}
@Override
public String toString() {
StringBuffer sb = new StringBuffer();
sb.append("capacity = ").append(elements.length).append(",size = ").append(size).append(",front = ").append(front).append(" [");
for (int i = 0;i < elements.length;i++){
if (i != 0){
sb.append(",");
}
sb.append(elements[i]);
}
sb.append("]");
return sb.toString();
}
}
-
public void enQueueRear(E element)
:从队尾入列,其与循环队列的逻辑相同代码如下:
/**
* 从队尾入列
* @param element
*/
public void enQueueRear(E element){
//动态扩容
ensureCapacity(size + 1);
int index = (front + size) % elements.length;
elements[index] = element;
size++;
}
-
public E deQueueFront()
:从队头出队,其与循环队列的逻辑相同代码如下:
/**
* 从队头出队
* @return
*/
public E deQueueFront(){
E frontElement = elements[front];
elements[front] = null;
front = (front + 1) % elements.length;
size--;
return frontElement;
}
-
public void enQueueFront(E element)
:从队头入列,代码如下:
/**
* 从队头入列
* @param element
*/
public void enQueueFront(E element){
//动态扩容
ensureCapacity(size + 1);
front = front - 1;
if (front < 0){
front = front + elements.length;
}else {
front = front % elements.length;
}
elements[front] = element;
size++;
}
- 第一种情况:size = 4,front = 2,队头元素为33,结构如下图所示:
现在在队头添加元素22,那么队头索引front需-1,指向添加元素22,计算公式为:front = (front - 1) % elements.length
,如下图所示:
- 第二种情况:size = 5,front = 0,队头元素为11,结构如下图所示:
- 现在往队头添加元素88,如果再将front-1;就会变成负数,存在问题,这里应该将front - 1 + 数组的容量,得到索引index = 6 将元素88存储在最后,计算公式为:
front = (front - 1) + elements.length
,如下图所示:
-
public E deQueueRear()
:从队尾出队
/**
* 从队尾出队
* @return
*/
public E deQueueRear(){
int rearindex = index(size - 1);
E rear = elements[rearindex];
elements[rearindex] = null;
size--;
return rear;
}
- 首先计算出待移除元素在队列中的真实索引;