数据结构之队列知识与习题练习

目录

1.队列概念

2.基本方法和简单使用

2.1.基本方法

2.2.简单使用

3.模拟实现与应用场景

3.1.各种实现方式分析

3.2.简单实现(含有尾指针的单向链表)

3.3.应用场景

3.3.1.循环队列

 3.3.2.用栈实现队列

 3.3.3.用队列实现栈

3.3.4.最小元素栈

 3.3.5.特定时间范围内最近的请求


1.队列概念

队列 :只允许在一端进行插入数据操作,在另一端进行删除数据操作的特殊线性表,队列具有先进先出的特点。
入队列:进行插入操作的一端称为 队尾( Tail/Rear
出队列:进行删除操作的一端称为 队头 Head/Front

2.基本方法和简单使用

2.1.基本方法

方法 功能
boolean offer(E e)
入队列
E poll()
出队列
peek()
获取队头元素
int size()
获取队列中有效元素个数
boolean isEmpty()
检测队列是否为空

2.2.简单使用

注意:在 Java 中, Queue 是个接口,底层是通过链表实现的,LinkedList 实现了 Queue接口,在实例化时必须实例化 LinkedList的对象。
public static void main(String[] args) {
    Queue q = new LinkedList<>(); 
    q.offer(1); 
    q.offer(2); 
    q.offer(3); 
    q.offer(4); 
    q.offer(5); // 从队尾入队列 
    System.out.println(q.size()); 
    System.out.println(q.peek()); // 获取队头元素
    q.poll(); 
    System.out.println(q.poll()); // 从队头出队列,并将删除的元素返回 
    if(q.isEmpty()){ 
        System.out.println("队列空"); 
    }else{ 
        System.out.println(q.size()); 
    } 
}

3.模拟实现与应用场景

3.1.各种实现方式分析

实现方式 头删法时间复杂度 尾插法时间复杂度
顺序表 O(n) O(1)
单向链表 O(1) O(n)
含有尾指针的单向链表 O(1) O(1)

3.2.简单实现(含有尾指针的单向链表)

class Node{
    public int val;
    public Node next;

    //构造方法
    public Node(int val) {
        this.val = val;
    }
}
//单向链表实现
public class MyQueue {
    public Node front;//队头
    public Node rear;//队尾

    //入队(尾插法)
    public void offer(int val){
        Node node = new Node(val);
        if(this.front==null){
            this.front=node;
            this.rear=node;
        }
        this.rear.next = node;
        this.rear = node;
    }
    public boolean isEmpty(){
        if(this.front==null){
            return true;
        }
        return false;
    }
    //返回队头元素
    public int peek(){
        if(isEmpty()){
            throw new RuntimeException("队列为空!");
        }
        return this.front.val;
    }
    //删除队头元素
    public int poll(){
        if(isEmpty()){
            throw new RuntimeException("队列为空!");
        }
        int old = this.front.val;
        this.front = this.front.next;
        return old;
    }

}

3.3.应用场景

3.3.1.循环队列

循环队列底层通常是由数组实现的,常用于生产者消费者模型。

//循环队列
public class MyCircularQueue {

    public int front;//队头下标
    public int rear;//队尾下标
    public int[] elem;

    //构造方法,k为队列长度
    public MyCircularQueue(int k) {
        this.elem = new int[k];//实际有效空间为k-1
        //this.elem = new int[k+1];//oj题写法或者使用标记/数组大小变量
    }
    //入队
    public boolean enQueue(int value) {
        if(isFull()){
            return false;
        }
        this.elem[this.rear] = value;
        this.rear = (this.rear+1)%this.elem.length;
        return true;
    }
    //出队
    public boolean deQueue() {
        if(isEmpty()){
            return false;
        }
        this.front = (this.front+1)%this.elem.length;
        return true;
    }
    //获取队头元素
    public int Front() {
        if(isEmpty()){
            return -1;
        }
        return this.elem[this.front];
    }
    //获取队尾元素
    public int Rear() {
        if(isEmpty()){
            return -1;
        }
        int index = -1;
        if(this.rear == 0){
            index = this.elem.length-1;
        }else{
            index = this.rear-1;
        }
        return this.elem[index];
    }
    //是否为空
    public boolean isEmpty() {
        return this.front == this.rear;
    }
    //是否为满
    public boolean isFull() {
        if(this.front == (this.rear+1)%this.elem.length){
            return true;
        }
        return false;
    }
}

图解

数据结构之队列知识与习题练习_第1张图片

 3.3.2.用栈实现队列

使用两个栈实现先入先出队列。队列应当支持一般队列支持的所有操作(pushpoppeekempty):

class MyQueue {

    private Stack s1;
    private Stack s2;
    public MyQueue() {
        this.s1 = new Stack<>();
        this.s2 = new Stack<>();
    }

    //入队
    public void push(int x) {
        s1.push(x);
    }
    //出队
    public int pop() {
        if(empty()){
            return -1;
        }
        if(s2.isEmpty()){
            int size = s1.size();
            for(int i = 0;i

图解

数据结构之队列知识与习题练习_第2张图片

 3.3.3.用队列实现栈

使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(pushtoppop 和 empty):

import java.util.LinkedList;
import java.util.Queue;
class MyStack {

    private Queue qu1;
    private Queue qu2;

    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);
        }
    }
    //出栈
    public int pop() {
        if(empty()){
            return -1;
        }
        //一个队列的size-1个元素转移到另一个队列中,剩下的元素就是要弹出栈的
        if(!qu1.isEmpty()){
            int size = qu1.size();
            for(int i = 0;i

图解

数据结构之队列知识与习题练习_第3张图片

3.3.4.最小元素栈

设计一个支持 push ,pop ,top 操作,并能在常数时间内检索到最小元素的栈:

class MinStack {
    private Stack stack;
    private Stack minStack;
    public MinStack() {
        this.stack = new Stack<>();
        this.minStack = new Stack<>();
    }

    public void push(int val) {
        if(stack.isEmpty()){
            stack.push(val);
            minStack.push(val);
        }else{
            stack.push(val);
            if(val<=minStack.peek()){
                minStack.push(val);
            }
        }
    }

    public void pop() {
        if(stack.isEmpty()){
            return;
        }
        int x = stack.pop();
        if(x==minStack.peek()){
            minStack.pop();
        }
    }

    public int top() {
        if(stack.isEmpty()){
            return -1;
        }
        return stack.peek();
    }

    public int getMin() {
        if(minStack.isEmpty()){
            return -1;
        }
        return minStack.peek();
    }
}

图解

数据结构之队列知识与习题练习_第4张图片

 3.3.5.特定时间范围内最近的请求

写一个 RecentCounter 类来计算特定时间范围内最近的请求:

请你实现 RecentCounter 类:

1.RecentCounter() 初始化计数器,请求数为 0 。
2.int ping(int t) 在时间 t 添加一个新请求,其中 t 表示以毫秒为单位的某个时间,并返回过去 3000 毫秒内发生的所有请求数(包括新请求)。确切地说,返回在 [t-3000, t] 内发生的请求数。

class RecentCounter {
    private Queue requests; 
    public RecentCounter() {
        requests = new LinkedList<>();
    }
    
    public int ping(int t) {
        requests.offer(t);
        while((!requests.isEmpty())&&requests.peek()<(t-3000)){
            requests.poll();
        }
        return requests.size();

    }
}

题解:这道题说实话只看题目很难看懂,我借用力扣大佬的通俗解释就是t代表这个员工的工号,每次新员工t加入q公司前先把工号小于t -3000的老家伙都辞退,然后再让t入职,统计q公司现有几个员工。举个例子:第一次ping(1),先将1入队,判断队列中位于[1-3000,1]之间的数据,位于区间外的出队,操作结束此时队列只有1;第二次ping(100),先将100入队,判断队列中位于[100-3000,100]之间的数据,位于区间外的出队,操作结束此时队列有1和100;第二次ping(3002),先将3002入队,判断队列中位于[3002-3000,3002]之间的数据,位于区间外的出队,数据1出队,操作结束此时队列有100和3002......

你可能感兴趣的:(java,数据结构)