栈和队列及应用

本文详细介绍了栈和队列的基本概念、数据结构、Java中的实现方式(如递归、链表和队列方法),以及它们在括号匹配、虚拟机栈和栈帧中的应用。同时讨论了栈和队列的对比,包括时间复杂度、实现策略和循环队列的特点。
摘要由CSDN通过智能技术生成

栈:先进后出-》后进先出【栈中元素拿出来就不能放进去了!!!】

队列:先进先出

在这里插入图片描述
12是最先放进去的,但是12在拿出来的时候是最后拿出来的-》先进后出

与栈相关的方法有四个

在这里插入图片描述
在这里插入图片描述

栈的使用

1.用递归的方式打印出链表的逆置

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

2.括号的匹配

第一个遇到的右括号要跟最后一个左括号匹配
Q:我们为什么要用栈来解决这个题呢?
因为栈的插入和删除的时间复杂度为O(1)更为合适

在这里插入图片描述

import java.util.Stack;

class Solution {
        public boolean isValid(String s) {
            Stack<Character> stack=new Stack();
            for(int i=0;i<s.length();i++){
                char ch=s.charAt(i);
                if(ch=='('||ch=='{'||ch=='['){
                    stack.push(ch);//如果有左括号,则push进去
                }
                else{
                    if(stack.isEmpty()){//栈内为空,说明没有左括号放进去,所以不可能匹配
                        return false;
                    }
                    char top= stack.peek();
                    if(ch==')'&&top=='('||ch=='}'&&top=='{'||ch==']'&&top=='['){
                        stack.pop();//如果有匹配的,则一起pop出去
                    }else {
                        return false;
                    }
                }
            }
            if(!stack.isEmpty()){
                return false;
            }
           return true;
        }
}

易错点记忆

在这里插入图片描述

概念区分

栈,虚拟机栈,栈帧
栈:数据结构栈
虚拟机栈:我们内存当中的一块区域
栈帧:我们每次调用方法的时候要在栈上开辟一块区域,我们叫它栈帧

队列

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
总结:我们不管是用单链表还是双链表都能实现栈和队列,但是我们使用双向链表更容易实现栈和队列

栈的题目总结,一定要注意empty()的条件以及if执行条件【是否添加括号】,不要单纯往上添加

队列的方法

在这里插入图片描述
1.queue表示的是普通队列,而deque表示的是双端队列;通过了解我们知道linkedlist的使用范围很广,我们可以用它来表示队列,栈等
2.队列的方法:(1)offer表示添加元素进去,切记队头进,队尾出(add也表示添加元素)
(2)poll表示拿出元素(remove同理)
(3)peek方法与栈同理,表示瞄一眼,看到的是队头元素(element同理)上面peek运行得到的是“2”

1. 用链表实现栈

在这里插入图片描述顺序栈可以用stack,链式栈可以用linkedlist来实现

在这里插入图片描述

2.用链表实现队列

双向链表实现
在这里插入图片描述
也可以把linkedlist当作一个队列,从队尾进,对头出
单链表也可以实现,但是没有双向链表好实现
在这里插入图片描述

3.用双向链表实现队列的插入offer和删除poll方法

import java.util.Queue;

public class MyQueue {
    static class ListNode {//用这个链表表示队列
        private int val;
        private ListNode prev;
        private ListNode next;

        public ListNode(int val) {
            this.val = val;
        }
    }
//队列中的队头和队尾
    private ListNode front;
    private ListNode rear;
    private int usedSize;

        public void offer(int x) {
            //用双向链表来表示队列插入元素
            ListNode node = new ListNode(x);
            if (front == null) {
                front = rear = node;
            } else {
                node.next = front;
                front.prev =node;
                front = node;
            }
            usedSize++;
        }

        public int poll() {
            int ret = rear.val;
            //用双向链表来表示队列的元素删除 删除尾节点
            if (front == null) {
                return -1;
            }
            if (front == rear) {
                front = null;
                rear = null;
                usedSize--;
                return ret;
            }
            /*
            方法1.rear=rear.prev;
            rear.next=null;*/
            rear.prev.next = null;
            rear = rear.prev;
            usedSize--;
            return ret;
        }

    public static void main(String[] args) {
        MyQueue queue=new MyQueue();
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        queue.offer(4);
        System.out.println(queue.poll());
        System.out.println(queue.poll());
    }
}

循环队列

注意:1.为了避免出现越界的问题,直接空出一个位置来,即在rear和front之间空出一个位置
在这里插入图片描述
后面的获取队尾元素的方法中,当rear不为0,则就直接rear-1
当队列长度为k的时候,为了使空余位置的方法可以成功实现,所以我们就让数组可容纳元素为k+1个,即new int[k+1]
2.代码展示:

class MyCircularQueue {
//类底下要有成员变量
private int[] elem;
private int front;//front和rear此时表示的是循环队列的数组下标
private int rear;
    public MyCircularQueue(int k) {//构造函数,对变量进行初始化
    this.elem=new int [k+1];
    }
    
    public boolean enQueue(int value) {
        if(isFull()){
            return false;
        }
        elem[rear]=value;
        rear=(rear+1)%elem.length;
        return true;
        }//循环队列中不能使用rear++,因为会越界,必须使用这个
    
    
    public boolean deQueue() {
        if(isEmpty()){
            return false;
        }else{
            front=(1+front)%elem.length;
            return true;
        }
    }
    
    public int Front() {//获取队首元素,就是根据下标来找元素,此时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() {//front和rear重合的话就表示队列为空
    if(front==rear){
        return true;
    }
    return false;
    }

双端队列(Deque)

在这里插入图片描述
第一个是双端队列的线性实现,添加元素就可以直接使用push等方法
第二个是双端队列的链式实现,添加元素使用offer等方法

队列和栈的相互应用

用队列实现栈

注意:1.要分清pop方法和top方法的区别,top是只返回栈顶元素,所以是瞄一眼,只要把栈顶元素存在temp中,直接返回temp就可以了
2.抛出poll()方法,括号中没有变量
3.队列在初始化的时候使用的是new LinkedList

class MyStack {
    private Queue<Integer> qu1;
    private Queue<Integer> 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;
    }   
    if(!qu1.isEmpty()){
    int size=qu1.size();
    for(int i=0;i<size-1;i++){
    int temp=qu1.poll();
      qu2.offer(temp);
    }
    return qu1.poll();
    }else{
        int size=qu2.size();
        for(int i=0;i<size-1;i++){
           int temp=qu2.poll();
            qu1.offer(temp);
        }
        return qu2.poll();
    }
    }
    
    public int top() {//返回栈顶元素就相当瞄一眼,所以要将所有元素从有的一边挪到空的一边
    //temp仅仅记录值,最后也返回temp即可
    if(empty()){
        return -1;
    }
    int temp=-1;
    if(!qu1.isEmpty()){
    int size=qu1.size();
    for(int i=0;i<size;i++){
       temp=qu1.poll();
       qu2.offer(temp);
    }
    return temp;
    }else{
        int size=qu2.size();
        for(int i=0;i<size;i++){
          temp=qu2.poll();
          qu1.offer(temp);
        }
        return temp;
    }
    }
    
    public boolean empty() {
    return qu1.isEmpty()&&qu2.isEmpty();
    }
}


用栈实现队列

注意:1.栈在初始化的时候使用的是new Stak()

class MyQueue {
private Stack<Integer> s1;
private Stack<Integer> s2;

    public MyQueue() {
    s1=new Stack<>();
    s2=new Stack<>();
    }
    
    public void push(int x) {//入的时候统一放在s1里面;
    //它这是新建的两个栈,所以放元素的时候肯定没有东西
        s1.push(x);
    }
    
    public int pop() {//出的时候统一从s2中拿出
    if(empty()){
        return -1;
    }
    if(s2.isEmpty()){
        while(!s1.isEmpty()){
            s2.push(s1.pop());
        } 
    }
    return s2.pop();
    }
    
    public int peek() {
    if(empty()){
        return -1;
    }
    if(s2.isEmpty()){
        while(!s1.isEmpty()){
            s2.push(s1.pop());
        }
    }
     return s2.peek();
    }
    
    public boolean empty() {
    return s1.isEmpty()&&s2.isEmpty();
    }
}

栈和队列对比总结

1.栈和队列它们插入和删除元素的时间复杂度均为O(1)
2.用无头单链表存储队列,front引用队头,back引用队尾,则在进行出队操作的时:
【1】如果队列中有多个节点的时候,只需要修改front
【2】如果队列中只有一个节点的时候,front和back都需要修改
3.队列遵循先进先出的原则,所以队列只能从队头删除元素
4.栈是尾部插入和删除,一般使用顺序表实现;队列是头部删除,尾部插入,一般使用链表实现
5.循环队列
【1】循环队列也是队列的一种,是一种特殊的线性数据结构
【2】循环队列通过设置计数的方式可以判断队列空或满——设置计数即添加一个字段来记录队列中有效元素的个数,如果队列中有效元素个数=空间总大小的时候队列满,如果队列中有效元素个数为0时队列空
【3】循环队列的队头为front,队尾为rear,循环队列长度为N,最多存储N-1个数据。其队内有效长度为(rear-front+N)%N

  • 44
    点赞
  • 47
    收藏
    觉得还不错? 一键收藏
  • 2
    评论
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值