目录
一、普通队列
1. 概念
2. Queue(Java集合框架的接口)
3. Queue中的方法
4. 方法使用演示
5. 队列的模拟实现
6. 顺序普通队列的缺点:
二、循环队列
1. 循环队列也是一种数据结构。基于上述队列的缺点,此时就有了循环队列,如下图:
2. 是一个类似圆形的数组:
3. 所以此时有两个问题:
4. 循环队列的实现: (oj题链接:力扣)
三、双端队列(Deque)
1. 概念
2. 双端队列的使用
3. 面试题
两个队列实现一个栈:
两个队列实现一个栈:
前言
队列是一种数据结构,在Java集合框架中,有对应的实现的接口,Queue是一个队列规范的接口,Deque是一个双端队列实现的接口,由于两个都是接口,所以是不能直接进行是实例化的,需要接口引用具体的类来进行实例化,所以底层可以是顺序表(数组),也可以是链表(单 / 双链表)。
队列:只可以在一段进行插入操作,在另一端进行删除操作的线性表,队列具有先进先出的特性,在插入操作的一端称作队尾,进行删除操作的一端称作队头。
如上图:Queue(队列)是一个接口,底层是一个双向链表来实现的,所以Queue不能直接实例化一个对象,只能是接口引用一个具体类(LinkedList)的方式来实例化。
boolean offer(E e) | 入队列 |
E poll() | 出队列 |
peek() | 获取队头元素 |
int size() | 获取队列中的有效元素个数 |
boolean isEmpty() | 判断队列是否为空 |
public static void main(String[] args) {
//尾插法和头部删除
MyQueue queue = new MyQueue();
queue.offer(1);
queue.offer(2);
queue.offer(3);
System.out.println(queue.peek());//1
System.out.println(queue.poll());//1
System.out.println(queue.peek());//2
System.out.println(queue.isEmpty());//false
System.out.println(queue.usedSize);//3
}
注:双向链表是最合适实现一个队列的,双向链表可以在尾部插入,也可以在尾部进行删除,因为每个节点都有前驱和后继节点,插入和删除的时间复杂度都是O(1),但是如果是单链表此时只能使用尾插法插入,使用头删法(在头部进行删除)时间复杂度才是O(1).
//单链表实现一个队列
public class MyQueue {
static class Node {
public int val;
public Node next;
public Node(int val) {
this.val = val;
}
}
public Node head;
public Node last;
public int usedSize;
//入队
public void offer(int val) {
Node node = new Node(val);
if (head == null) {
head = node;
last = node;
}else {
last.next = node;
last = node;
}
usedSize++;
}
//出队
public int poll() {
if (isEmpty()) {
throw new EmptyException("队列是空的!");
}
int ret = head.val;
head = head.next;
if (head == null) {
last = null;//如果只有一个节点,那么last也要置空
}
usedSize--;
return ret;
}
//判断队列是否为空
public boolean isEmpty() {
return usedSize == 0;
}
//获取队列队顶元素
public int peek() {
if (isEmpty()) {
throw new EmptyException("队列是空的!");
}
return head.val;
}
//获取队列有效元素的个数
public int getUsedSize() {
return usedSize;
}
}
如下图:此时一个数组实现的顺序队列正在一边入队元素,一边出队元素,如果队列中的元素满了,此时元素再进行出队,但是后边的元素是入不进来的,虽然前面的格子空出来了,但是此时元素只能从队尾进入,队头出,可以看出,此时的队列的利用效率不是很高。
1. 有front指针和rear指针同时指向数组的0下标,入队元素就让rear指针往后走一个,之后出队元素时,就让front指针向后移动。 |
2. 当循环队列满了,此时rear指针可以往前走一步,到达0下标,然后继续入队元素。 |
1. 当队列满,如何让rear指针指向0下标? |
2. 当队列满时,rear = front;当队列为空时,rear = front,所以如何判断队列的空和满? |
1. 每次入队元素或者出队元素时,让 rear =(rear + 1)% queue.size(),front =(front + 1)% queue.size(),而不是简单的rear++,front++,所以就可以让rear下标和front下标在走到最后一个位置时,再往后走一步就又到了0下标的位置。
2. 通过浪费一个空间来区分队列的空和满,如果rear指针的下一个就是front(0下标),此时就认为队列为满,当rear = front时,此时队列为空。(或者使用usedSize来记录当前元素个数也可以)。
package Review;
//循环队列底层就是一个数组
//浪费掉最后一个空间来表示队列是否是满的
//就是每次都让rear往后走一步,之后进行判断如果rear的下一个位置就是0下标
//此时队列就是满的,如果front == rear,此时队列是空的
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) {
//1.检查队列是否是满的
if (isFull()) {
return false;
}
//2.入队元素,之后让rear引用往后走一步
elem[rear] = value;
rear = (rear + 1) % elem.length;
return true;
}
public boolean deQueue() {
if (isEmpty()) {
return false;
}
//front++;
front = (front + 1) % elem.length;
return true;
}
public int Front() {
if (isEmpty()) {
return -1;
}
return elem[front];
}
public int Rear() {
if (isEmpty()) return -1;
//return elem[rear - 1];
//此时还有一个问题需要注意,如果rear到了0下标,之后就数组越界了
//数组是没有-1下标的,
//也就是让rear返回数组的最后一个下标
int index = (rear == 0) ? elem.length - 1 : rear - 1;
return elem[index];
}
public boolean isFull() {
return (rear + 1) % elem.length == front;
}
public boolean isEmpty() {
return front == rear;
}
}
双端队列就是可以在两端都可以入队,也可以出队的队列,元素可以从队头入队和出队,也可以从队尾出队和入队。
(在实际使用中,Deque接口使用的是比较多的,栈和队列都可以使用该接口,这个接口中有栈的方法,也有队列的方法)
public static void main9(String[] args) {
//底层是一个双向链表
Deque deque = new LinkedList<>();
//数组实现的双端队列:底层就是一个数组
Deque deque1 = new ArrayDeque<>();
//顺序的双端队列也可以当作栈来使用
Deque stack = new ArrayDeque<>();
stack.push(1);
stack.push(2);
stack.push(3);//顺序的双端队列(底层是用数组来实现的)也提供了栈的相关的方法
}
public static void main8(String[] args) {
//双端队列
Deque deque = new LinkedList<>();
//普通队列
//Queue中既有offer方法,也有add方法,add在无法添加一个元素时,会抛出一个异常
//offer方法优于add方法,如果无法添加元素,offer方法不会抛出异常
Queue queue = new LinkedList<>();
//链式栈 虽然是具体的类但是里面有栈的方法
//LinkedList类中的方法是最多的,因为它实现了很多接口,此时一定会重写接口中的方法
LinkedList stack = new LinkedList<>();
//双向链表
List list = new LinkedList<>();
//其他的都是使用接口来引用的:只有接口中的方法
}
1. 用栈实现一个队列 (oj链接:力扣) | 思路:用两个栈才能实现一个队列,因为栈是先进后出,队列是先进先出;此时需要把栈中的全部元素入栈到第二个栈中,此时的栈顶元素就是出队的元素。 |
2. 用队列实现一个栈 (oj链接:力扣) | 思路:两个队列实现一个栈,”入栈“开始入到第一个队列中,之后入队就入到不为空的队列中,”出栈“就出到另一个队列中,出size-1个元素,最后剩下的一个元素就是要出栈的元素。 |
import java.util.LinkedList;
import java.util.Queue;
class MyStack2 {
private Queue qu1;
private Queue qu2;
public MyStack2() {
qu1 = new LinkedList<>();
qu2 = new LinkedList<>();
}
public void push(int x) {
if (!qu1.isEmpty()) {
qu1.offer(x);
}else if (!qu2.isEmpty()) {
qu2.offer(x);
}else {
qu1.offer(x);
}
}
public int pop() {
if (empty()) {
return -1;
}
if (!qu1.isEmpty()){
//此时一定需要定义一个size来存放qu1的容量,如果直接将size
//写进for循环中,size一直在改变,就会导致循环的次数减少
int size = qu1.size();
for (int i = 0; i < size-1; i++) {
qu2.offer(qu1.poll());
}
return qu1.poll();
}else {
int size = qu2.size();
for (int i = 0; i < size-1; i++) {
qu1.offer(qu2.poll());
}
return qu2.poll();
}
}
public int top() {
if (empty()) return -1;
if (!qu1.isEmpty()){
//此时一定需要定义一个size来存放qu1的容量,如果直接将size
//写进for循环中,size一直在改变,就会导致循环的次数减少
int size = qu1.size();
int val = -1;
for (int i = 0; i < size; i++) {
val = qu1.poll();
qu2.offer(val);
}
return val;
}else {
int size = qu2.size();
int val = -1;
for (int i = 0; i < size; i++) {
val = qu2.poll();
qu1.offer(val);
}
return val;
}
}
public boolean empty() {
return qu1.isEmpty() && qu2.isEmpty();
}
}
import java.util.Stack;
class MyQueue2 {
private Stack stack1;
private Stack stack2;
public MyQueue2() {
stack1 = new Stack<>();
stack2 = new Stack<>();
}
public void push(int x) {
stack1.push(x);
}
public int pop() {
//前提是两个队列都不能是空的
if (empty()) return -1;
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.pop();
}
public int peek() {
if (empty()) return -1;
if (stack2.isEmpty()) {
while (!stack1.isEmpty()) {
stack2.push(stack1.pop());
}
}
return stack2.peek();
}
public boolean empty() {
return stack1.isEmpty() && stack2.isEmpty();
}
}