一、队列
我们会发现队列和栈很相似,栈是先进后出,队列是先进先出。既然栈可以用双向链表实现,那么队列也应该可以。队列的操作主要有入队和出队,入队就是向队尾添加元素,出队就是向队头删除元素。
队列的实现:
package 队列;
import 链表.LinkedList;
public class Queue<E> {
private LinkedList<E> list = new LinkedList();
/**
* 元素的数量
* @return
*/
public int size(){
return list.size();
}
/**
* 是否为空
* @return
*/
public boolean isEmpty(){
return list.isEmpty();
}
/**
* 清空元素
*/
public void clear(){
list.clear();
}
/**
* 入队
*/
public void enQueue(E element){
list.add(element);
}
/**
* 出队
* @return
*/
public E deQueue(){
return list.remove(0);
}
/**
* 获取队列的头元素
* @return
*/
public E front(){
return list.get(0);
}
}
二、使用两个栈实现队列。
程序就是对数据的操作,而数据结构是数据存储的一种格式。既然栈和队列存储的格式是不一样的,我们要做的就是同样一份数据经过怎样的操作方式 最终在这两种不同的数据结构下,得到相同的结果。 我们写的代码就是数据的操作方式。我写的不是代码,而是对数据的操作方式,嘿嘿。
package 队列;
import 栈.Stack;
public class CQueue {
Stack<Integer> stack1 = new Stack<>();
Stack<Integer> stack2 = new Stack<>();
//队列: push(1) push(2) push(3) pop() push(4) pop()
//结果: 还在队列的元素 4 3 出队的元素: 2 1
// 思路:push的元素,我们都放到栈1中。
// 然后我们需要把1取出,我们只能把栈1的元素取出来放到栈2中
// 现在又放入4,那我们只能把栈2的元素取出放回栈1,因为4要在3的后面
// 现在需要取出2,同样将栈1的元素取出放到栈2
// 栈1:1 2 3 栈2:空
// 栈1:空 栈2: 3 2 出栈:1
// 栈1: 2 3 4 栈2:空
// 栈1: 空 栈2 : 4 3 出栈:2
// 总结:当栈2不为空时,需要先将栈2的元素取出放入栈1 ,然后push
// 当栈1不为空时,需要将栈1的元素放入栈2,然后pop
public void push(int value) {
while(!stack2.isEmpty()){
stack1.push(stack2.pop());
}
stack1.push(value);
}
public int pop() {
while(!stack1.isEmpty()){
stack2.push(stack1.pop());
}
return stack2.pop();
}
}
测试结果:
三、双端队列Deque
这个跟上面是一样的,就是多了2个方法而已,直接上代码吧!
package 队列;
import 链表.LinkedList;
public class Deque<E> {
private LinkedList<E> list = new LinkedList<>();
public int size() {
return list.size();
}
public boolean isEmpty() {
return list.isEmpty();
}
public void clear() {
list.clear();
}
public void enQueueRear(E element) {
list.add(element);
}
public E deQueueFront() {
return list.remove(0);
}
public void enQueueFront(E element) {
list.add(0, element);
}
public E deQueueRear() {
return list.remove(list.size() - 1);
}
public E front() {
return list.get(0);
}
public E rear() {
return list.get(list.size() - 1);
}
}
四、循环队列CircleQueue
循环队列底层可以使用数组实现,首先需要回答的是什么是循环队列。
现在的队列是:11,22,33,44,55。假设现在66,77入队,11,22出队,那么队列就变成了33,44,55,66,77,队头就变成了33。假设现在又有两个数88,99入队,那么队列就变成了33,44,55,66,77,88,99。而88放在了索引为0的位置,99放在了索引为1的位置。这就是循环队列,假设现在又有元素入队,那么此时队列已经满了,和动态数组一样,循环队列也需要动态扩容。
这里有几个方法需要详解,我们一个一个的看。
enQueue(E element):假设我们先不考虑扩容,入队就是向队尾添加元素,那么size就要+1,第一种情况就是上图,front=0,size=5所以我们往队尾添加元素就是elements[front+size] = element;第二种情况就是下图我们往队尾添加一个元素88,front=2,size = 5 ,那elements[7]不就数组越界了吗。所以综合2种情况就是elements[(front+size) % elements.length] = element;
deQueue():出队就是删除队头的元素,那么size就要-1。同样有两种情况,假设front=2,删除队头的元素那么front就变成了3;当front=6时,删除队头的元素后front就变成了0。所以front = (front+1) % elements.length;
ensureCapacity(int capacity):首先来看看我们之前arrayList的扩容方法是怎么写的。扩容的思想就是当数组容量不够时,创建一个新数组,新数组的容量是原来的n倍,然后将旧数组的值取出放到新数组中就行了。那循环数组的扩容和arrayList的扩容有什么区别呢?举个例子就明白了,假设现在的循环队列的容量是7,里面的元素是88,99,33,44,55,66,77 此时循环队列已经装满了,那么循环队列需要扩容。假设队头的元素是33,扩容后数组里存放元素就变成了:33,44,55,66,77,88,99。不就是循环队列是先从队头的元素开始遍历嘛,所以newElements[i] = elements[(i+front)%elements.length];
i=0,front=2,length=7;所以newElements[0] = elements[2];
i=1,front=2,length=7;所以newElements[1] = elements[3];
.....依次类推。
最后front = 0;
/**
* 保证要有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++){
newElements[i] = elements[i];
}
elements = newElements;
}
package 队列;
public class CircleQueue<E> {
private int front;
private int size;
private E[] elements;
public CircleQueue() {
elements = (E[]) new Object[10];
}
public int size() {
return size;
}
public boolean isEmpty() {
return size == 0;
}
public void clear() {
for (int i = 0; i < size; i++) {
elements[index(i)] = null;
}
front = 0;
size = 0;
}
/**
* 入队:向队尾添加元素
* @param element
*/
public void enQueue(E element) {
ensureCapacity(size + 1);
elements[(front+size) % elements.length] = element;
size++;
}
/**
* 出队,删除队头的元素
* @return
*/
public E deQueue() {
E element = elements[front];
elements[front] = null;
front = (front+1) % elements.length; //下面的那个例子,假设front=6,现在front+1,因为是循环队列所以front变为了0
size--;
return element;
}
/**
* 取出队头的元素
* @return
*/
public E front() {
return elements[front];
}
/**
* 保证要有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++) {
newElements[i] = elements[(i+front)%elements.length];
}
elements = newElements;
// 重置front
front = 0;
}
}