💕"
开朗些,勇敢些
"💕
作者:Mylvzi
文章主要内容:数据结构之队列及其面试题
一.队列
1.概念
队列:只允许在一端进行数据的插入(队尾),在另一端进行数据的删除(队头)的特殊线性表,和栈不同的是,队列具有先进先出的特性(FIFO)
2.java中的队列
Java中队列是一个接口,底层是通过链表实现的;既然是接口,就不能直接实例化对象,要利用实现queue接口的类去实例化对象
源码
二.队列的模拟实现
队列的底层是是LinkdList,所以可以使用双向链表实现队列
java
/**
* 实现队尾进,队头出
*/
public void offer(int x) {
ListNode node = new ListNode(x);
if (rear == null) {
front = rear = node;
this.usedSize++;
return;
}
rear.next = node;
node.prev = rear;
rear = node;
this.usedSize++;
}
public int poll() {
if (front == null) {
throw new QueueEmptyException("队列为空,无法返回数据");
}
int ret = front.val;
// 如果只有一个节点 要把front和rear都置空 否则在执行front.prev = null这句代码时会报错
if (front.next == null) {
front = null;
rear = null;
return ret;
}
front = front.next;
front.prev = null;
this.usedSize--;
return ret;
}
public int peek() {
if (front == null) {
throw new QueueEmptyException("队列为空,无法返回数据");
}
int ret = front.val;
return ret;
}
public int size() {
return this.usedSize;
}
public boolean empty() {
return this.usedSize==0;
}
}
3.Deque
Deque也是Java中和队列有关的数据结构,它是一种双端队列,即队头,队尾都可以实现数据插入与删除,deque 是 "double ended queue" 的简称。同时Deque也是Java中最常用的queue结构
源码:
4.循环队列的实现
所谓循环队列就是front和rear一直在绕着圆走,和普通队列不一样的是,它可以重复利用空间
循环队列是一种线性数据结构,其操作表现基于 FIFO(先进先出)原则并且队尾被连接在队首之后以形成一个循环。它也被称为"环形缓冲器"。
循环队列的一个好处是我们可以利用这个队列之前用过的空间。在一个普通队列里,一旦一个队列满了,我们就不能插入下一个元素,即使在队列前面仍有空间。但是使用循环队列,我们能使用这些空间去存储新的值。
一般的队列
循环队列
循环队列的难点在于如何判满,这里我们提供两种方法
循环队列的实现1:使用计数器
java
class MyCircularQueue {
private int[] elem;
private int front;
private int rear;
private int cnt = 0;
public MyCircularQueue(int k) {
this.elem = new int[k];
}
public boolean enQueue(int value) {
// 插入数据
if(isFull()) {
return false;
}
this.elem[rear] = value;
rear = (rear+1) % elem.length;
cnt++;
return true;
}
public boolean deQueue() {
if(isEmpty()) {
return false;
}
front = (front+1) % elem.length;
cnt--;
return true;
}
public int Front() {
if(isEmpty()) {
return -1;
}
return this.elem[front];
}
public int Rear() {
if (isEmpty()) {
return -1;
}
int index = (rear == 0) ? (elem.length-1) : (rear-1);
return this.elem[index];
}
public boolean isEmpty() {
return cnt == 0;
}
public boolean isFull() {
return cnt == this.elem.length;
}
}
循环队列的实现2:预留一个空间
java
class MyCircularQueue {
private int[] elem;
private int front;
private int rear;
public MyCircularQueue(int k) {
this.elem = new int[k+1];
}
public boolean enQueue(int value) {
// 插入数据
if(isFull()) {
return false;
}
this.elem[rear] = value;
rear = (rear+1) % elem.length;
return true;
}
public boolean deQueue() {
if(isEmpty()) {
return false;
}
front = (front+1) % elem.length;
return true;
}
public int Front() {
if(isEmpty()) {
return -1;
}
return this.elem[front];
}
public int Rear() {
if(isEmpty()) {
return -1;
}
int index = (rear == 0) ? (elem.length-1) : (rear-1);
return this.elem[index];
}
public boolean isEmpty() {
return rear == front;
}
public boolean isFull() {
return (rear+1) % elem.length == front;
}
}
注意:为了实现循环,rear和front的变化不能直接写成rear++或者front++,否则会发生越界
rear = (rear+1) % elem.length;
front = (front+1) % elem.length;
用队列实现栈
1.使用两个队列
利用两个队列实现栈
如果是入栈,入到不为空的队列之中
如果是出栈,先找到不为空的队列,将队尾之前的所有元素出到另一个队列之中,最后再出队尾元素,实现后进先出
如果是返回栈顶元素,和出栈的思路一样,栈顶就是不为空队列的队尾
栈为空:当两个队列都为空时,栈为空
java
/**
* 用队列实现栈 使用两个队列实现栈
* 队列:先进先出 栈:后进先出
* 要明白,一个队列无法实现栈,他们有本质上的区别
* 入栈:入到不为空的队列之中 如果两个都为空,入到第一个队列之中
* 出栈:出栈顶元素,后进先出 对应的是队列中的队尾元素 将队尾元素之前的所有元素转移到另一个队列之中,再返回队尾元素
*/
class MyStack2 {
private Queue<Integer> que1;
private Queue<Integer> que2;
public MyStack2() {
this.que1 = new LinkedList<>();
this.que2 = new LinkedList<>();
}
public void push(int x) {
// 入栈到不为空的队列之中
if(!que1.isEmpty()) {
que1.offer(x);
} else if (!que2.isEmpty()) {
que2.offer(x);
}else {
que1.offer(x);
}
}
public int pop() {
// 出栈
// 为空 直接返回
if (empty()) {
return -1;
}
if (!que1.isEmpty()) {
int size = que1.size();
for (int i = 0; i < size - 1; i++) {
// 将que1队尾元素前面的所有元素转移到que2中
que2.offer(que1.poll());
}
return que1.poll();
} else {
int size = que2.size();
for (int i = 0; i < size - 1; i++) {
// 将que2队尾元素前面的所有元素转移到que1中
que1.offer(que2.poll());
}
return que2.poll();
}
}
public int top() {
// 为空 直接返回
if (empty()) {
return -1;
}
int tmp = -1;
if (!que1.isEmpty()) {
int size = que1.size();
for (int i = 0; i < size; i++) {
// 将que1队尾元素前面的所有元素转移到que2中
tmp = que1.poll();
que2.offer(tmp);
}
return tmp;
} else {
int size = que2.size();
for (int i = 0; i < size - 1; i++) {
// 将que2队尾元素前面的所有元素转移到que1中
tmp = que2.poll();
que1.offer(tmp);
}
return tmp;
}
}
public boolean empty() {
// 当两个队列都为空时,栈就为空
return que1.isEmpty() && que2.isEmpty();
}
}
2.使用一个队列
java
/**
* 思路2:使用一个队列实现栈
* 栈:后进先出 队列:先进后出
* 保证队头的元素时最后一个入栈的
* 新入一个元素的时候将之前所有元素都poll出去,再重新入队,那么新元素就是队头了
* 这样就实现了后进先出
*/
class MyStack2 {
Queue<Integer> queue;
public MyStack2() {
this.queue = new LinkedList<>();
}
public void push(int x) {
int n = queue.size();
queue.offer(x);
for (int i = 0; i < n; i++) {
// 重新入队
queue.offer(queue.poll());
// int ret = queue.poll();
// queue.offer(ret);
}
}
public int pop() {
return queue.poll();
}
public int top() {
return queue.peek();
}
public boolean empty() {
return queue.isEmpty();
}
}
可以带入数据尝试一下 之所以需要%N,是因为rear有可能处于下标为0的位置