1,什么是对队列?
- 队列是一种特殊的线性表,只能在头尾两端进行操作
- 队尾:只能从队尾添加元素,一般叫做入队
- 对头:只能从队头移除元素,一般叫做出队
- 队列遵循先进先出的原则,First In First Out,FIFO
2,队列的接口设计和实现
-
队列的内部实现可以使用 动态数组和链表实现
-
我们优先使用双向链表,因为队列主要是往头尾操作元素
public class Queue {
//创建双向链表
private List list = new LinkedList<>();
//元素的数量
public int size() {
//链表内元素的数量
return list.size();
}
//是否为空
public boolean isEmpty() {
//调用链表方法,判断是否为空
return list.isEmpty();
}
//清除元素
public void clear() {
//和链表一样清除元素
list.clear();
}
//入队
public void enQueue(E element) {
//就是链表在队尾添加元素
list.add(element);
}
//出队
public E deQueue() {
//删除链表的第一个元素,就是出队
return list.remove(0);
}
//获取队头
public E front() {
//获取链表的第一个元素
return list.get(0);
} }
2.1,力扣练习-232. 用栈实现队列
面试题链接:leetcode-cn.com/problems/im…
面试题解析:
- 准备两个栈:instack保存入栈的元素。outStack从依保存次从inStack出栈的元素
- 入栈是push到instack中
- 出栈时:队列是先进先出
- 如果outStack为空,将instack所有元素逐一弹出,push到outstack,outstack弹出栈顶元素
- 如果outstack不为空时,outStack弹出栈顶元素(即队头元素)
面试题代码实现
class MyQueue {
private Stack<Integer> inStack;
private Stack<Integer> outStack;
/** Initialize your data structure here. */
public MyQueue() {
//初始化inStack,outStack
inStack = new Stack<>();
outStack = new Stack<>();
}
/** 入队 */
public void push(int x) {
//inStack直接push
inStack.push(x);
}
/** 出队 */
public int pop() {
//先检查outStack是否为空
if(outStack.isEmpty()){
//inStack不为空,遍历inStack
while(!inStack.isEmpty()){
//inStack一次pop出来,压栈进入outStack
outStack.push(inStack.pop());
}
}
//outStack出栈就是,操作队头元素
return outStack.pop();
}
/** 获取队头元素 */
public int peek() {
//先检查outStack是否为空
if(outStack.isEmpty()){
//inStack不为空,遍历inStack
while(!inStack.isEmpty()){
//inStack一次pop出来,压栈进入outStack
outStack.push(inStack.pop());
}
}
//获取栈顶元素
return outStack.peek();
}
/** 队列是否为空 */
public boolean empty() {
//inStack和outStack同时为空,说明队列为空
return inStack.isEmpty() && outStack.isEmpty();
}
}
3,双端队列
-
双端队列就是在队头和队尾都可以进行添加和删除的队列
-
双端队列的实现:
public class Deque {
//初始化链表
private List 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直接添加元素
list.add(element);
}
//对头出队
public E deQueueFront() {
//删除索引为0的元素
return list.remove(0);
}
//对头入队
public void enQueueFront(E element) {
//在索引为0时添加元素
list.add(0, element);
}
//队尾出队
public E deQueueRear() {
//找到队尾元素的索引,删除对应元素
return list.remove(list.size() - 1);
}
//获取对头
public E front() {
//获取索引为0的元素
return list.get(0);
}
//获取队尾
public E rear() {
//获取队尾索引的元素
return list.get(list.size() - 1);
} }
4,循环队列
-
循环队列是把顺序队列首尾相连,就是对头和队尾相连的队列
-
出队和入队同一班队列一样,先进先出
-
循环队列的设计和实现:使用动态数组实现
public class CircleQueue {
private int front; //头节点的位置
private int size; //元素数量
private E[] elements;//动态数组
private static final int DEFAULT_CAPACITY = 10;
public CircleQueue() {
//开辟队列内存
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
//获取元素数量
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
front = 0;
//size为0
size = 0;
}
//入队
public void enQueue(E element) {
//查询容量
ensureCapacity(size + 1);
//算出下一个添加元素的位置,然后添加
//size+对头front = index
//如果index大于数组长度elements.length,索引为index - elements.length
//如果index小于数组长度elements.length,索引为index - 0
elements[index(size)] = element;
//size加1
size++;
}
//出队
public E deQueue() {
//获取头节点
E frontElement = elements[front];
//头节点指向null
elements[front] = null;
//计算下一个头节点的索引
//front+1就是下一个头节点的位置
//如果index大于数组长度elements.length,索引为index - elements.length
//如果index小于数组长度elements.length,索引为index - 0
front = index(1);
//size减1
size--;
//返回头节点
return frontElement;
}
//获取头节点
public E front() {
//根据头节点索引获取元素
return elements[front];
}//根据传入的索引,计算位置 private int index(int index) { index += front; return index - (index >= elements.length ? elements.length : 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[index(i)]; } elements = newElements; // 重置front front = 0; }}
5,循环双端队列
-
循环双端队列:就是循环队列的对头和队尾,都可以进行删除和添加操作
-
循环双端队列其实就是多了:从对头入队,从队尾出队的实现
-
循环双端队列的设计和实现:
public class CircleDeque {
private int front;
private int size;
private E[] elements;
private static final int DEFAULT_CAPACITY = 10;
public CircleDeque() {
elements = (E[]) new Object[DEFAULT_CAPACITY];
}
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 enQueueRear(E element) {
ensureCapacity(size + 1);
elements[index(size)] = element;
size++;
} /*
* 从头部出队
* @param element
/
public E deQueueFront() {
E frontElement = elements[front];
elements[front] = null;
front = index(1);
size--;
return frontElement;
} /*
* 从头部入队
* @param element
/
public void enQueueFront(E element) {
ensureCapacity(size + 1);
//头部入队,就是front索引-1
//计算索引的位置,负值给front
front = index(-1);
elements[front] = element;
size++;
} /*
* 从尾部出队
* @param element
*/
public E deQueueRear() {
//队尾索引为size-1
int rearIndex = index(size - 1);
//获取队尾元素
E rear = elements[rearIndex];
//队尾置null
elements[rearIndex] = null;
//size减1
size--;
return rear;
} public E front() {
return elements[front];
}
//获取队尾
public E rear() {
//队尾索引为size-1
//根据index方法计算出对应的队尾实现
return elements[index(size - 1)];
}private int index(int index) { index += front; //front索引为0是,从对头入队的实现 if (index < 0) { return index + elements.length; } return index - (index >= elements.length ? elements.length : 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[index(i)]; } elements = newElements; // 重置front front = 0; }}
6,力扣练习-225. 用队列实现栈
面试题链接:leetcode-cn.com/problems/im…
方法一:两个队列
为了满足栈的特性,即最后入栈的元素最先出栈,在使用队列实现栈时,应满足队列前端的元素是最后入栈的元素。可以使用两个队列实现栈的操作,其中 queue 1 用于存储栈内的元素, queue 2 作为入栈操作的辅助队列。
入栈操作时,首先将元素入队到 queue 2 ,然后将 queue 1 的全部元素依次出队并入队到 queue 2 ,此时 queue 2 的前端的元素即为新入栈的元素,再将 queue 1 和 queue 2 互换,则 queue 1 的元素即为栈内的元素, queue 1 的前端和后端分别对应栈顶和栈底。
由于每次入栈操作都确保 queue 1 的前端元素为栈顶元素,因此出栈操作和获得栈顶元素操作都可以简单实现。出栈操作只需要移除 queue 1 的前端元素并返回即可,获得栈顶元素操作只需要获得 queue 1 的前端元素并返回即可(不移除元素)。 由于 queue 1 用于存储栈内的元素,判断栈是否为空时,只需要判断 queue 1 是否为空即可。
class MyStack {
Queue<Integer> queue1;
Queue<Integer> queue2;
/** Initialize your data structure here. */
public MyStack() {
queue1 = new LinkedList<Integer>();
queue2 = new LinkedList<Integer>();
}
/** 入栈 */
public void push(int x) {
queue2.offer(x);
while (!queue1.isEmpty()) {
queue2.offer(queue1.poll());
}
Queue<Integer> temp = queue1;
queue1 = queue2;
queue2 = temp;
}
/** 出栈 */
public int pop() {
return queue1.poll();
}
/** 产看栈顶元素 */
public int top() {
return queue1.peek();
}
/** 判断是否为空 */
public boolean empty() {
return queue1.isEmpty();
}
}
方法二:一个队列
使用一个队列时,为了满足栈的特性,即最后入栈的元素最先出栈,同样需要满足队列前端的元素是最后入栈的元素。
入栈操作时,首先获得入栈前的元素个数 n n,然后将元素入队到队列,再将队列中的前 n n 个元素(即除了新入栈的元素之外的全部元素)依次出队并入队到队列,此时队列的前端的元素即为新入栈的元素,且队列的前端和后端分别对应栈顶和栈底。
由于每次入栈操作都确保队列的前端元素为栈顶元素,因此出栈操作和获得栈顶元素操作都可以简单实现。出栈操作只需要移除队列的前端元素并返回即可,获得栈顶元素操作只需要获得队列的前端元素并返回即可(不移除元素)。
由于队列用于存储栈内的元素,判断栈是否为空时,只需要判断队列是否为空即可
class MyStack {
//初始化队列
Queue<Integer> queue;
/** Initialize your data structure here. */
public MyStack() {
queue = new LinkedList<Integer>();
}
/** 入栈 */
public void push(int x) {
//获取队列元素数量
int n = queue.size();
//入队
queue.offer(x);
//遍历队列,压栈
for (int i = 0; i < n; i++) {
//队列先进先出跟栈相反,所以反转队列,改为先进后出
queue.offer(queue.poll());
}
}
/** chu zhan */
public int pop() {
return queue.poll();
}
/** Get the top element. */
public int top() {
return queue.peek();
}
/** Returns whether the stack is empty. */
public boolean empty() {
return queue.isEmpty();
}
}