数据结构 - 栈 与 队列 - (java)

2023-11-13


前言

本篇介绍栈和队列,了解栈有顺序栈和链式栈,队列底层是双链表实现的,单链表也可以实现队列,栈和队列的相互实现和循环队列;如有错误,请在评论区指正,让我们一起交流,共同进步!



本文开始

1. 栈的认识

栈:一种特殊的线性表,只能从一头进入,一头出;
进出栈规则:先进后出

栈的模拟实现:顺序栈和链式栈实现栈时间复杂度都是O(1)
顺序栈:栈可以使用顺序表实现
链式栈:可以用单链表实现:头插和头删(入栈) 或 尾插和尾删(出栈);
可以使用双链表实现;既可以头进头出,也可以尾进尾出;(双链表知道前后节点位置)

在这里插入图片描述

链式栈代码实现:

public static void main(String[] args) {
        //链表实现栈:LinkedList底层是双链表
        LinkedList<Integer> stack = new LinkedList<>();
        stack.push(1);
        stack.push(2);
        stack.push(3);
        System.out.println(stack.pop());
    }

代码实现栈的基本操作:

public static void main(String[] args) {
        Stack<Integer> stack = new Stack<>();
        //压栈:栈中添加元素
        stack.push(2);
        stack.push(3);
        //看栈顶元素
        System.out.println(stack.peek());
        //栈的大小
        System.out.println(stack.size());
        //出栈
        System.out.println(stack.pop());
        //栈是否为空
        System.out.println(stack.isEmpty());
    }

1.1 栈的使用:栈实现队列

双栈实现队列代码:
思路:
一个栈1表示入队,一个栈2表示出队;
队列出队需要判断栈2是否为空,栈2空将栈1中元素全部放到栈2中,此时再出栈2栈顶元素即可;
入队:看栈2是否有元素,栈2有元素直接返回栈顶元素;栈2为空,再将栈1中元素放到栈2中,才能看到出队的值;

public class MyQueue {
    Stack<Integer> stack1;
    Stack<Integer> stack2;
    //创建两个栈
    public MyQueue() {
        stack1 = new Stack<>();
        stack2 = new Stack<>();
    }
    //在栈1中放元素
    public void push(int x) {
        stack1.push(x);
    }
    public int pop() {
        if(empty()) {
            return -1;
        }
        //判断栈2中是否有元素
        if(stack2.isEmpty()) {
            //栈1元素全部放到栈2中
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        //不空,栈2中有元素直接弹出
        return stack2.pop();
    }

    public int peek() {
        if(empty()) {
            return -1;
        }
        //看栈2是否有元素
        if(stack2.isEmpty()) {
            while (!stack1.isEmpty()) {
                stack2.push(stack1.pop());
            }
        }
        //栈2有元素
        return stack2.peek();
    }
    //判断两个栈是否为空
    public boolean empty() {
        return stack1.isEmpty() && stack2.isEmpty();
    }
}

2. 队列的认识

1.队列:也是一种特殊的线性表,一头进,另一头出;
进出队列规则:先进先出;

2.链表实现队列:
单链表实现队列两种方式: - ==使用一种下标
头插法:进队-时间复杂的O(1) - 头插 ,出队-时间复杂的O(n) - 尾删
尾插法:进队-时间复杂的O(n) - 尾插,出队-时间复杂的O(1) - 头删

(两个下标控制头尾)单链表实现队列代码实现:
思想:使用头删尾插,进队出队时间复杂都是O(1);使用两个下标,记录头节点位置head,再记录尾节点位置last; 方便头插(入队),尾删(出队);
【注】单链表实现队列只能尾插头删,保证时间复杂度都为O(1)
不使用头插尾删原有?
使用头插尾删进行单链表,进队-头插时间复杂度O(1), 出队-尾删时间复杂度O(n);
尾删:每次删除都需要找尾节点前一个节点位置,需要遍历一般链表所以时间复杂度高;
代码:

public class MyQueue {
    static class Node {
        int val;
        Node next;
        public Node(int val) {
            this.val = val;
        }
    }
    //用双下标实现队列,需要定义两个
    public Node head;//头下标
    public Node last;//尾下标
    public int size;
    //入队操作: 插入
    public boolean offer(int val) {
        //插入需要新节点,创建新节点
        Node node = new Node(val);
        //没有节点的时候
        if(head == null) {
            //头尾下标指向同一位置
            head = node;
            last = node;
        }else {
            //head != null
            //链接新节点
            last.next = node;
            //尾节点向后移动一步
            last = node;
        }
        size++;//计数
        return true;
    }
    //出队:删除头删
    public int poll(int val) {
        //判断链表是否为空
        if(isEmpty()) {
            return -1;
        }
        //记录删除的值
        int v = head.val;
        head = head.next;
        //如果只有一个节点,lasta也需要置空
        if(head == null) {
            last = null;
        }
        size--;//-1
        return v;
    }
    private boolean isEmpty() {
        return size == 0;
    }
    public int peek() {
        //链表为空不用看队头元素
        if(isEmpty()) {
            return -1;
        }
        return head.val;//返回队头元素
    }
    public int getSize() {
        //队列大小
        return size;
    }
}

双链表实现队列代码实现:
特点:双链表实现队列可以自由头进尾删,头删尾进;

public class MyQueue2 {
    //双链表实现队列
    static class Node {
        int val;
        Node prev;
        Node next;
        public Node(int val) {
            this.val = val;
        }
    }
    //前后下标
    public Node front;
    public Node last;
    public int size;
    //入队
    public boolean offer(int val) {
        //插入的新节点
        Node newNode = new Node(val);
        //没有节点
        if(front == null) {
            front = newNode;
            last = newNode;
        }else {
            //不为空
            //链接前后节点
            newNode.prev = last;
            last.next = newNode;
            //后下标后移
            last = newNode;
        }
        size++;
        return true;
    }
    //出队:删除
    public int poll() {
        int v = -1;
        //队列为空
        if(isEmpty()) {
            return -1;
        }
        //只有一个节点
        if(front == last) {
            front = null;
            last = null;
        }else {
            //先记录值
            v = front.val;
            //前下标后移
            front = front.next;
            //找到前一个下标的next置为空
            front.prev.next = null;
            //当前prev置为空:防止空指针异常
            front.prev = null;
        }
        size--;
        return v;
    }

    private boolean isEmpty() {
        return size == 0;
    }
    public int peek() {
        if(isEmpty()) {
            return -1;
        }
        return front.val;
    }
}

队列基本操作代码实现:

   public static void main(String[] args) {
        Queue<Integer> queue = new LinkedList<>();
        //入队:队列中添加元素
        queue.offer(1);
        queue.offer(2);
        queue.offer(3);
        //看队头元素
        System.out.println(queue.peek());
        //队列的大小
        System.out.println(queue.size());
        //出队
        System.out.println(queue.poll());
        //队列是否为空
        System.out.println(queue.isEmpty());
    }

2.1 双队列实现栈

双队列实现栈:
思路:
①先定义两个队列
②入栈:是判断那个队列不为空,队列1不为空,就往队列1中放,队列2不为空,就往队列2中放,都为空默认往队列1中放;
③出栈:假设不为空队列元素个数为size个,将不为空的队列出队size-1个到另一个为空的队列,出队列size-1个队列剩余一个就为出栈元素;
④栈顶元素:假设队列1不为空,队列2空;定义一个变量为tmp, 作为队列1元素到队列2元素的过度,将队列1中元素全部传到队列2中,此时队列1最后出队的元素就是栈顶元素,并存储在tmp中,返回tmp即可;

在这里插入图片描述

代码实现:

import java.util.LinkedList;
import java.util.Queue;

public class MyStack {
    //双队列实现栈
    //构建双队列
    Queue<Integer> queue1;
    Queue<Integer> queue2;
    public MyStack() {
        queue1 = new LinkedList<>();
        queue2 = new LinkedList<>();
    }

    public void push(int x) {
        //两个对谁不为空,就入那个队
        if(!queue1.isEmpty()) {
            queue1.offer(x);
        }else if(!queue2.isEmpty()) {
            queue2.offer(x);
        }else {
            //都为空,入第一个队
            queue1.offer(x);
        }
    }

    public int pop() {
        //判断队列是否为空
        //都为空
        if(empty()) {
            return -1;
        }
        if(!queue1.isEmpty()) {
            //获取队列1大小
            int size = queue1.size();
            //队1出size-1个元素,到队2中
            while (size - 1 != 0) {
                queue2.offer(queue1.poll());
                size--;
            }
            //队1只剩1个,就是要出栈的元素
            return queue1.poll();
        }else {
            //队1为空,队2不为空
            //获取队列2大小
            int size = queue2.size();
            //队2出size-1个元素,到队1中
            while (size - 1 != 0) {
                queue1.offer(queue2.poll());
                size--;
            }
            //队2只剩1个,就是要出栈的元素
            return queue2.poll();
        }
    }

    public int top() {
        //判断队列是否为空
        //都为空
        if(empty()) {
            return -1;
        }
        if(!queue1.isEmpty()) {
            //获取队列1大小
            int size = queue1.size();
            int tmp = -1;//存储每个出队元素
            while (size != 0) {
                tmp = queue1.poll();
                queue2.offer(tmp);
                size--;
            }
            //队1最后一个出队的,就是要栈顶的元素
            return tmp;
        }else {
            //获取队列2大小
            int size = queue2.size();
            int tmp = -1;//存储每个出队元素
            while (size != 0) {
                tmp = queue2.poll();
                queue1.offer(tmp);
                size--;
            }
            //队2最后一个出队的,就是要栈顶的元素
            return tmp;
        }
    }

    public boolean empty() {
        return queue1.isEmpty() && queue2.isEmpty();
    }
}

2.2 循环队列 - 数组实现的队列

循环队列:可以看成一圈型的队列,但其实还是数组

为什么使用循环?
一个存满的数组,先出队一个,如果再进队尾rear下标就越界了,但数组中还有空间没有利用 =》 对于这种情况所以使用了循环;

在这里插入图片描述

怎么实现循环?
1.可以使用下标标记法,进队一个标记一个,从而实现循环;
2.牺牲一个空间,使用求余来实现循环 ( rear + 1) % length;(循环需要首尾相连,如图从7下标到0下标,求余就可以实现;)

实现循环队列:
思路分析:
判断循环队列是否为空还是满,就使用牺牲一个空间法,(rear + 1) == front 判断为满;
rear == front 判断为空;如下图
怎样实现循环:使用求余数的方法,可以让下标从尾下标到开始下标(如图0下标到7下标);

在这里插入图片描述

循环队列代码:

class MyCircularQueue {
    //循环链表:底层是数组,所以创建数组
    int[] elem;
    //循环的前后下标
    int front;//前
    int rear;//后
    public MyCircularQueue(int k) {
        //初始化k大小的数组
        elem = new int[k + 1];
    }
    //进队
    public boolean enQueue(int value) {
        //进队先判断队列是否满
        if(isFull()) {
            return false;
        }
        //不满进队
        elem[rear] = value;
        //rear++ =》不能使为下标到起始下标,进行循环,所以使用求余数;
        rear = (rear + 1) % elem.length;
        return true;
    }
    //出队
    public boolean deQueue() {
        //出队先判断队列是否有元素
        if(isEmpty()) {
            return false;
        }
        //前下标+1,与需要考虑构成循环,末尾到开始位置
        front = (front + 1) % elem.length;
        return true;
    }
    //获取队头元素
    public int Front() {
        if(isEmpty()) {
            return -1;
        }
        return elem[front];//不为空就返回
    }
    //获取队尾元素
    public int Rear() {
        if(isEmpty()) {
            return -1;
        }
        //牺牲一个空间法,尾下标超过尾元素1个数组空间 =》所以一般情况:尾下标需要-1才是尾元素
        //会遇到一种尾下标在(0位置)起始位置,而尾元素在最后位置,需要构成循环 =》 这里特殊情况,特殊出来0下标位置
        int index = (rear == 0) ? elem.length - 1 : rear - 1;
        return elem[index];
    }
    //判断是否为空
    public boolean isEmpty() {
        return rear == front;
    }
    //判断是否满
    public boolean isFull() {
        //循环队列使用牺牲1个空间方法,区分空和满
        //rear+1 再余 =>构成循环,尾下标就能够到起始下标;
        return (rear + 1) % elem.length == front;
    }
}

3. 双端队列

双端队列:是一种继承Queue的接口,可以用它实现栈与队列;
实现栈,队列:

  		Deque<Integer> stack1 = new ArrayDeque<>();
        Deque<Integer> stack2 = new LinkedList<>();
        Deque<Integer> queue1 = new LinkedList<>();

总结

✨✨✨各位读友,本篇分享到内容如果对你有帮助给个

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

数据结构 - 栈 与 队列 - (java) 的相关文章

随机推荐

  • Basic Level 1082 射击比赛 (20分)

    题目 本题目给出的射击比赛的规则非常简单 谁打的弹洞距离靶心最近 谁就是冠军 谁差得最远 谁就是菜鸟 本题给出一系列弹洞的平面坐标 x y 请你编写程序找出冠军和菜鸟 我们假设靶心在原点 0 0 输入格式 输入在第一行中给出一个正整数 N
  • ant-design中textArea组件获取光标位置,插入表情之后自动将光标移至文本的最后

    目前的需求是要设置一段文本 但是文本里可以插入微信表情 需要实现在插入表情之后光标位置自动移到当前文本的最后 效果图 实现代码 textArea组件
  • es查询列表如何去重?

    SearchSourceBuilder builder new SearchSourceBuilder builder collapse new CollapseBuilder name keyword 在Elasticsearch中 bu
  • python面试总结

    python面试题 python中is和 的区别 Python中对象包含的三个基本要素 分别是 id 身份标识 type 数据类型 和value 值 比较的是value值 is 比较的是id 简述read readline readline
  • 解决pip._vendor.urllib3.exceptions.ReadTimeoutError: HTTPSConnectionPool

    解决pip vendor urllib3 exceptions ReadTimeoutError HTTPSConnectionPool host files pythonhosted org port 443 Read timed out
  • 公众号(服务号)模板消息(个人通知)开发方案

    公众号消息通知 微信公众号开发文档 公众号是以微信用户的一个联系人形式存在的 消息会话是公众号与用户交互的基础 目前公众号内主要有这样几类消息服务的类型 分别用于不同的场景 1 群发消息 公众号可以以一定频次 订阅号为每天1次 服务号为每月
  • windows下启动mysql服务的命令行启动和手动启动方法

    今天遇到mysql服务无法启动 上网一查很多人也遇到mysql服务器启动不了的问题 所以就索性整理了windows下启动mysql服务的命令行启动和手动启动方法的文章 以便各位遇到同类问题的朋友进行参考 1 图形界面下启动mysql服务 在
  • Hugging Face开源库accelerate详解

    官网 https huggingface co docs accelerate package reference accelerator Accelerate使用步骤 初始化accelerate对象accelerator Accelera
  • java基础案例教程黑马程序员案例答案,真香

    掌握核心知识 1 90 几率面试被问 吃透原理 面试不慌 Spring原理 2 大厂必问Redis 赶紧码起来 Redis核心原理 3 MySQL从入门到实战都在这篇 面试笑谈优化 当然核心知识不止这三点 这只是一部分 吃透源码 1 面试源
  • 默认路由(详细解析)

    一 默认路由 1 全球最大的网段 子网掩码越短 子网掩码写成二进制形式后1的个数越少 主机位越多 该网段的地址数量就越大 因此如果想让一个网段包括全部的IP地址 就要求子网掩码短到极限 最短就是0 子网掩码变成了0 0 0 0 这也意味着该
  • Scrapy框架之Crawlspider的使用

    Scrapy存在多种爬虫类 最常用的有两种 第一种是基于basic模版创建的普通爬虫类Scrapy spider 另一种是基于crawl的规则性爬虫类scrapy spider crawlspider 一 crawlspider 经常用于数
  • 记一次应用破解——脱壳修改后重打包

    样本是在某个群里下载的 当时是有人发出来找人帮忙修改下 我是想练练手就下载下来开始修改 首先拿到应用先看了下是加壳了 腾讯的壳 然后安装看了下需要修改的地方 需求就是改一下qq群 开始动手 一 脱壳拿到dex文件 我这里直接使用脱壳机脱壳拿
  • 称重传感器HX711的驱动。适用于arduino、ESP32

    github链接 YukiTbst trust measurer 基于HX711的旋翼推力测量实验台 包含了HX711的驱动 Propeller trust measurer based on HX711 including a drive
  • Ubuntu下ftp服务器配置方法 (高级配置)

    Ubuntu下ftp服务器配置方法 Ubuntu自 带的FTP服务器是vsftpd 1 安装vsftpd Ubuntu安装软件倒不是件困难的事 输入 sudo apt get install vsftpd 安装了之后会在 home 下建立一
  • 《啥是佩奇》,python程序员眼中的佩琦

    啥是佩奇 从前天晚上开始 朋友圈就被这条广告刷屏 也许很多人 看完这个短片也有这样的想法 没错 佩奇是容易被忽略的亲情 明明是一个广告 都能这么吸引人 让人笑中带泪 泪中含笑的 告诉爷爷 你需要什么东西 爷爷给你准备 佩奇 什么是佩奇呀 这
  • Neo4j:入门基础(二)之导入CSV文件

    CSV文件 1 csv文件推荐是utf 8编码 否则会造成中文乱码 2 csv文件默认需要放在import目录下 如需从远程或者其他本地目录导入 则需要修改conf neo4j conf load csv时文件路径 默认需要放在 NEO4J
  • Cesium 之加载ArcGIS Server 4490切片服务(含orgin -400 400)

    对于ArcGIS Server发布的切片服务 在地理坐标系中Cesium默认只支持wgs84的4326坐标系 无法通过ArcGisMapServerImageryProvider直接加载CGCS2000的4490坐标系 虽然可以使用WebM
  • openGL之API学习(六十七)glTexParameter glTextureParameter

    设置纹理对象的参数 这两个函数其实是一个功能 void glTexParameterf GLenum target GLenum pname GLfloat param target Specifies the target to whic
  • DC-7靶机渗透测试

    文章目录 DC 7靶机渗透测试 1 信息收集 1 1 主机扫描 1 2 端口扫描 1 3 目录扫描 2 SSH远程登录 3 漏洞发现 4 漏洞利用 5 提权 DC 7靶机渗透测试 1 信息收集 1 1 主机扫描 netdiscover r
  • 数据结构 - 栈 与 队列 - (java)

    前言 本篇介绍栈和队列 了解栈有顺序栈和链式栈 队列底层是双链表实现的 单链表也可以实现队列 栈和队列的相互实现和循环队列 如有错误 请在评论区指正 让我们一起交流 共同进步 文章目录 前言 1 栈的认识 1 1 栈的使用 栈实现队列 2
Powered by Hwhale