文章目录
一、队列
1、队列的定义
-
队列:只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出FIFO(First In First Out) 。
-
入队列:进行插入操作的一端称为队尾(Tail/Rear) 。
-
出队列:进行删除操作的一端称为队头( Head/Front)。
在Java中, Queue是个接口,底层是通过链表实现的。
方法 | 功能 |
---|---|
boolean offer(E e) | 入队列 |
E poll() | 出队列 |
peek() | 获取队头元素 |
int size() | 获取队列中有效元素个数 |
boolean isEmpty() | 检测队列是否为空 |
public class Test01 {
public static void main(String[] args) {
Queue<Integer> myQueue =new LinkedList<>();
myQueue.offer(1);
myQueue.offer(2);
myQueue.offer(3);
System.out.println(myQueue.peek());
System.out.println(myQueue.poll());
System.out.println(myQueue.peek());
}
}
2、双端队列
双端队列(deque):是指允许两端都可以进行入队和出队操作的队列, deque 是 “double ended queue”
的简称。 那就说明元素可以从队头出队和入队,也可以从队尾出队和入队。
Deque是一个接口,使用时必须创建LinkedList的对象。
import java.util.*;
import java.util.Queue;
/**
* @author Susie-Wen
* @version 1.0
* @description:
* @date 2022/7/10 10:16
*/
public class Test01 {
public static void main(String[] args) {
Deque<Integer> myQueue =new LinkedList<>();//创建双端队列
myQueue.offer(1);
myQueue.offer(2);
myQueue.offer(3);
System.out.println(myQueue.peek());
System.out.println(myQueue.poll());
System.out.println(myQueue.peek());
}
}
Linkedlist可以当做栈,可以当做队列,可以当做双端队列。ArrayList可以当做顺序表(动态数组)。
3、源码解读
这里主要来说一下在Queue方法当中的add方法和offer方法有什么区别。
1、add方法
- 如果可以在不违反容量限制的情况下立即将指定元素插入此队列,则成功时返回true,如果当前没有可用空间,则引发IllegalStateException。
- 参数:
e–要添加的元素 - 返回值:
true(由Collection.add指定) - 抛出异常:
IllegalStateException–如果由于容量限制,此时无法添加元素,如果指定元素的类阻止它被添加到这个队列中
NullPointerException–如果指定的元素为空则抛出该异常,并且此队列不允许空元素
IllegalArgumentException–如果此元素的某个属性阻止它添加到此队列中
2、offer方法
- 如果可以在不违反容量限制的情况下立即将指定的元素插入到此队列中。当使用容量受限的队列时,这种方法通常更适合添加,这种方法只能通过引发异常来插入元素。
- 参数:
e–要添加的元素 - 返回:
如果元素被添加到该队列,则为真,否则为假 - 抛出异常:
ClassCastException–如果指定元素的类阻止它被添加到这个队列中
NullPointerException–如果指定的元素为空,并且此队列不允许空元素
IllegalArgumentException–如果此元素的某个属性阻止它添加到此队列中
注:一般情况下使用offer方法的情况多一点。
二、实现队列
1、实现单链表队列(队列的链式存储)
分析时间复杂度:
【1】、以头插法入队:O(1)
【2】、以头插法出队:O(N)
【3】、以尾插法入队:O(N)
【4】、以尾插法出队:O(1)
还能缩小时间复杂度吗?
可以,在尾巴节点加一个last引用标志。
【1】定义节点
static class Node{
public int val;
public Node next;
public Node(int val){
this.val=val;
}
}
【2】入队
/**
* 入队方法
*/
public Node head;//代表队列的头部
public Node tail;//代表队列的尾部
public void offer(int val){
Node node=new Node(val);
if(head==null){
//此时队列里面没有元素,所以头尾指针都指向同一个节点
head=node;
tail=node;
}else{
//队列里面有元素,因此让尾部指向该元素
tail.next=node;
tail=tail.next;
}
}
【3】出队
/**
* 出队操作
*/
public int poll(){
if(head==null){
return -1;//或者抛出异常
}
int oldVal=head.val;
if(head.next==null){
head=tail=null;
}else{
head=head.next;
}
return oldVal;
}
【4】查看队头元素
//查看当前队头元素
public int peek(){
if(head==null){
return -1;//或者抛出异常
}
return head.val;
}
测试:
public class Test01 {
public static void main(String[] args) {
MyQueue myQueue=new MyQueue();
myQueue.offer(1);
myQueue.offer(2);
myQueue.offer(3);
System.out.println(myQueue.peek());
System.out.println(myQueue.poll());
System.out.println(myQueue.peek());
}
}
源码:
public class MyQueue {
static class Node{
public int val;
public Node next;
public Node(int val){
this.val=val;
}
}
/**
* 入队方法
*/
public Node head;//代表队列的头部
public Node tail;//代表队列的尾部
public void offer(int val){
Node node=new Node(val);
if(head==null){
//此时队列里面没有元素,所以头尾指针都指向同一个节点
head=node;
tail=node;
}else{
//队列里面有元素,因此让尾部指向该元素
tail.next=node;
tail=tail.next;
}
}
/**
* 出队操作
*/
public int poll(){
if(head==null){
return -1;//或者抛出异常
}
int oldVal=head.val;
if(head.next==null){
head=tail=null;
}else{
head=head.next;
}
return oldVal;
}
//查看当前队头元素
public int peek(){
if(head==null){
return -1;//或者抛出异常
}
return head.val;
}
}
2、实现循环队列(队列的顺序存储)
使用数组存储存在的问题:
【1】浪费一个格子方法
【1】入队
//入队
public boolean enQueue(int value) {
if(isFull()){
return false;
}
this.elem[rear]=value;
rear=(rear+1)%elem.length;
return true;
}
【2】出队
public boolean deQueue() {
if(isEmpty()){
return false;
}
front=(front+1)%elem.length;
return true;
}
【3】判断队满
public boolean isFull() {
//如果rear的下一个元素是front就说明满了,因为空出来一个格子
return (rear+1)%elem.length==front;
}
【4】判断队空
public boolean isEmpty() {
return rear==front;
}
【5】获取队头元素
public int Front() {
if(isEmpty()){
return -1;
}
return elem[front];
}
【6】获取队尾元素
public int Rear() {
if(isEmpty()){
return -1;
}
int index=(rear==0) ? elem.length-1:rear-1;
return elem[index];
}
源码:
class MyCircularQueue {
public int[] elem;//数组
public int usedSize;
public int front;//队头下标
public int rear;//队尾下标
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;
return true;
}
//出队
public boolean deQueue() {
if(isEmpty()){
return false;
}
front=(front+1)%elem.length;
return true;
}
public int Front() {
if(isEmpty()){
return -1;
}
return elem[front];
}
public int Rear() {
if(isEmpty()){
return -1;
}
int index=(rear==0) ? elem.length-1:rear-1;
return elem[index];
}
public boolean isEmpty() {
return rear==front;
}
public boolean isFull() {
//如果rear的下一个元素是front就说明满了,因为空出来一个格子
return (rear+1)%elem.length==front;
}
}
【2】usedSize方法
class MyCircularQueue {
public int[] elem;//数组
public int usedSize;
public int front;//队头下标
public int rear;//队尾下标
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;
this.usedSize++;
return true;
}
//出队
public boolean deQueue() {
if(isEmpty()){
return false;
}
front=(front+1)%elem.length;
this.usedSize--;
return true;
}
public int Front() {
if(isEmpty()){
return -1;
}
return elem[front];
}
public int Rear() {
if(isEmpty()){
return -1;
}
int index=(rear==0) ? elem.length-1:rear-1;
return elem[index];
}
public boolean isEmpty() {
return usedSize==0;
}
public boolean isFull() {
//如果rear的下一个元素是front就说明满了,因为空出来一个格子
return usedSize==elem.length;
}
}
三、题目
1. 用队列实现栈
题目》》
分析:
- 1、需要两个队列才能实现栈
- 2、入栈:入到不为空的队列当中
- 3、出栈:出不为空的队列,出size-1个,剩下的那个元素就是要出栈的元素。
【1】入栈
//入栈:把元素入到不为空的队列当中
public void push(int x) {
if(!qu1.isEmpty()){
qu1.offer(x);
}else if(!qu2.isEmpty()){
qu2.offer(x);
}else{
qu1.offer(x);
}
usedSize++;
}
【2】出栈
这里必须定义一个临时变量curSize,不能直接使用size方法,因为size方法会变化
//出栈
public int pop() {
if(empty()){
return -1;
}
//这里必须定义一个临时变量curSize,不能直接使用size方法
//因为size方法会变化
if(!qu1.isEmpty()){
int curSize=qu1.size();
for(int i=0;i<curSize-1;i++){
qu2.offer(qu1.poll());
}
usedSize--;
return qu1.poll();
}else{
int curSize=qu2.size();
for(int i=0;i<curSize-1;i++){
qu1.offer(qu2.poll());
}
usedSize--;
return qu2.poll();
}
}
【3】获取栈顶元素
//获取栈顶元素
public int top() {
if(empty()){
return -1;
}
//这里必须定义一个临时变量curSize,不能直接使用size方法
//因为size方法会变化
if(!qu1.isEmpty()){
int curSize=qu1.size();
int ret=-1;
for(int i=0;i<curSize;i++){
ret=qu1.poll();
qu2.offer(ret);
}
return ret;
}else{
int ret=-1;
int curSize=qu2.size();
for(int i=0;i<curSize;i++){
ret=qu2.poll();
qu1.offer(ret);
}
return ret;
}
}
源码:
class MyStack {
Queue<Integer> qu1;
Queue<Integer> qu2;
public int usedSize;
public MyStack() {
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);
}
usedSize++;
}
//出栈
public int pop() {
if(empty()){
return -1;
}
//这里必须定义一个临时变量curSize,不能直接使用size方法
//因为size方法会变化
if(!qu1.isEmpty()){
int curSize=qu1.size();
for(int i=0;i<curSize-1;i++){
qu2.offer(qu1.poll());
}
usedSize--;
return qu1.poll();
}else{
int curSize=qu2.size();
for(int i=0;i<curSize-1;i++){
qu1.offer(qu2.poll());
}
usedSize--;
return qu2.poll();
}
}
//获取栈顶元素
public int top() {
if(empty()){
return -1;
}
//这里必须定义一个临时变量curSize,不能直接使用size方法
//因为size方法会变化
if(!qu1.isEmpty()){
int curSize=qu1.size();
int ret=-1;
for(int i=0;i<curSize;i++){
ret=qu1.poll();
qu2.offer(ret);
}
return ret;
}else{
int ret=-1;
int curSize=qu2.size();
for(int i=0;i<curSize;i++){
ret=qu2.poll();
qu1.offer(ret);
}
return ret;
}
}
//判断是否栈空
public boolean empty() {
return usedSize==0;
}
}
2. 用栈实现队列
题目》》
分析:
- 1、使用两个栈来实现队列
- 2、第一个栈:只入队
- 3、第二个栈:先判断此栈是否为空,如果不为空,就出队。如果为空,就把第一个栈的元素全部入到第二个栈,然后出栈
【1】入队
//入队:全部入到第一个栈
public void push(int x) {
s1.push(x);
}
【2】出队
public int pop() {
if(empty())
return -1;//代表当前队列为空
if(s2.empty()){
//需要把s1里面的元素全部倒过来
while(!s1.empty()){
s2.push(s1.pop());
}
}
//此时s2当中一定有元素
return s2.pop();
}
【3】去队头
public int peek() {
if(empty())
return -1;//代表当前队列为空
if(s2.empty()){
//需要把s1里面的元素全部倒过来
while(!s1.empty()){
s2.push(s1.pop());
}
}
//此时s2当中一定有元素
return s2.peek();
}
【4】判空
public boolean empty() {
return s1.empty() && s2.empty();
}
源码:
class MyQueue {
Stack<Integer> s1;
Stack<Integer> s2;
public MyQueue() {
s1=new Stack<>();
s2=new Stack<>();
}
//入队:全部入到第一个栈
public void push(int x) {
s1.push(x);
}
public int pop() {
if(empty())
return -1;//代表当前队列为空
if(s2.empty()){
//需要把s1里面的元素全部倒过来
while(!s1.empty()){
s2.push(s1.pop());
}
}
//此时s2当中一定有元素
return s2.pop();
}
public int peek() {
if(empty())
return -1;//代表当前队列为空
if(s2.empty()){
//需要把s1里面的元素全部倒过来
while(!s1.empty()){
s2.push(s1.pop());
}
}
//此时s2当中一定有元素
return s2.peek();
}
public boolean empty() {
return s1.empty() && s2.empty();
}
}
3. 实现一个最小栈
分析:
- 1、有两个栈,最小栈和普通栈。其中最小栈来查找最小值。
- 2、每次入栈的时候,判断最小栈的栈顶元素是否小于入栈元素,如果入栈元素更小,则将入栈元素同时入到两个栈当中。否则只把元素入到普通栈。
- 3、每次出栈的时候,都要判断最小栈的栈顶元素是否与出栈元素相同,如果相同,则两个栈一起出栈。否则,只出普通栈的元素。
【1】入栈
//入栈:入到普通栈,并且判断是否需要入到最小栈当中
public void push(int val) {
s.push(val);
if(minStack.empty()){
//最小栈如果没有元素,则必须放到最小栈当中
minStack.push(val);
}else{
int peekV=minStack.peek();
if(val<=peekV){
minStack.push(val);
}
}
}
【2】出栈
//出栈,出普通栈的元素,并且判断是否要出最小栈的元素
public void pop() {
if(!s.empty()){
int popV=s.pop();
int peekVMinS=minStack.peek();
if(popV==peekVMinS){
minStack.pop();
}
}
}
【3】获取栈顶元素
//获取栈顶元素
public int top() {
if(!s.empty()){
return s.peek();
}
return -1;
}
【4】获取最小值
public int getMin() {
if(!minStack.empty()){
return minStack.peek();
}
return -1;
}
源码:
class MinStack {
private Stack<Integer> s;//普通栈
private Stack<Integer> minStack;//维护当前栈的最小值
public MinStack() {
s=new Stack<>();
minStack=new Stack<>();
}
//入栈:入到普通栈,并且判断是否需要入到最小栈当中
public void push(int val) {
s.push(val);
if(minStack.empty()){
//最小栈如果没有元素,则必须放到最小栈当中
minStack.push(val);
}else{
int peekV=minStack.peek();
if(val<=peekV){
minStack.push(val);
}
}
}
//出栈,出普通栈的元素,并且判断是否要出最小栈的元素
public void pop() {
if(!s.empty()){
int popV=s.pop();
int peekVMinS=minStack.peek();
if(popV==peekVMinS){
minStack.pop();
}
}
}
//获取栈顶元素
public int top() {
if(!s.empty()){
return s.peek();
}
return -1;
}
public int getMin() {
if(!minStack.empty()){
return minStack.peek();
}
return -1;
}
}
4、设计循环队列
其实就是上面二 2 【2】的代码。
class MyCircularQueue {
public int[] elem;//数组
public int usedSize;
public int front;//队头下标
public int rear;//队尾下标
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;
this.usedSize++;
return true;
}
//出队
public boolean deQueue() {
if(isEmpty()){
return false;
}
front=(front+1)%elem.length;
this.usedSize--;
return true;
}
public int Front() {
if(isEmpty()){
return -1;
}
return elem[front];
}
public int Rear() {
if(isEmpty()){
return -1;
}
int index=(rear==0) ? elem.length-1:rear-1;
return elem[index];
}
public boolean isEmpty() {
return usedSize==0;
}
public boolean isFull() {
//如果rear的下一个元素是front就说明满了,因为空出来一个格子
return usedSize==elem.length;
}
}