当前位置: 首页 > news >正文

数据结构-----栈队列

目录

什么是栈?

时间复杂度

栈的常见操作

入栈

出栈

栈的常见应用场景

浏览器的回退和前进

虚拟机栈

出栈顺序

栈例题

检查符号是否成对出现

反转字符串

队列

什么是队列?

时间复杂度

单队列的常见操作

入队

出队

“假溢出”

循环队列

队列应用场景

KTV点歌列表

阻塞队列

线程池的任务队列

队列例题

使用队列实现栈

使用栈实现队列


什么是栈?

栈 (stack)是一种特殊的线性数据集合,只允许在栈顶 top进行加入数据(push)和移除数据(pop),按照 后进先出LIFO(Last In First Out) 的规则进行操作,也可以理解为先入后出FILO(First In Last Out);

栈的实现方式
栈的实现结构可以是一维数组链表来实现,用数组实现的栈叫作顺序栈 ,用链表实现的栈叫作链式栈 。在Java中,顺序栈使用java.util.Stack类实现,链式栈使用java.util.LinkedList类实现。

时间复杂度

假设栈中有n个元素,常见操作时间复杂度:

  • 访问指定位置的元素,时间复杂度为O(n):因为最坏情况下,访问的元素在栈底,需要遍历所有元素。
  • 入栈和出栈的时间复杂度为 O(1):因为只涉及栈顶top。

栈的常见操作

入栈

入栈操作(push)就是把新元素放入栈中,只允许从栈顶一侧放入元素,新元素将会成为新的栈顶。

出栈

出栈操作(pop)就是把元素从栈中弹出,只有栈顶元素才允许出栈,出栈元素的前一个元素将会成为新的栈顶。

栈的常见应用场景

浏览器的回退和前进

需要使用两个栈(Stack1 和 Stack2)和就能实现这个功能。比如:你按顺序查看了 1,2,3,4 这四个页面,我们依次把  1,2,3,4 这四个页面压入 Stack1中。当你想回头看 2 这个页面的时候,你点击2次回退按钮,我们依次把 4,3 这两个页面从 Stack1 弹出,然后压入 Stack2 中。假如你又想回到页面 3,你点击1次前进按钮,我们将 3 页面从 Stack2 弹出,然后压入到 Stack1 中。

虚拟机栈

每个线程拥有一块独立的内存空间,这块内存空间被设计成“栈”这种结构,被称为“虚拟机栈”。
JVM 虚拟机栈是由一个个栈帧组成,而每个栈帧中都拥有:局部变量表、操作数栈、动态链接、方法出口信息。每一次方法调用都会有一个对应的栈帧被压入 VM Stack虚拟机栈,每一个方法调用结束后,代表该方法的栈帧会从VM Stack虚拟机栈中弹出。

出栈顺序

3个元素A,B,C顺序进栈,出栈情况分析如下:

4个元素A,B,C,D顺序进栈,出栈情况分析如下:

栈例题

检查符号是否成对出现

给定一个只包括 '(',')','{','}','[',']' 的字符串,判断该字符串是否有效。
有效字符串需满足:

  • 左括号必须用相同类型的右括号闭合。
  • 左括号必须以正确的顺序闭合。

比如: "()"、"()[]{}"、"{[]}" 都是有效字符串,而 "(]" 、"([)]" 则不是

思路:

  1. 首先我们将括号间的对应规则存放在 Map 中;
  2. 创建一个栈。遍历字符串,如果字符是左括号就直接加入stack中,否则将stack 的栈顶元素与这个括号做比较,如果不相等就直接返回 false。遍历结束,如果stack为空,返回 true。
public boolean isValid(String s){// 括号之间的对应规则HashMap<Character, Character> mappings = new HashMap<Character, Character>();mappings.put(')', '(');mappings.put('}', '{');mappings.put(']', '[');Stack<Character> stack = new Stack<Character>();char[] chars = s.toCharArray();for (int i = 0; i < chars.length; i++) {if (mappings.containsKey(chars[i])) {char topElement = stack.empty() ? '#' : stack.pop();if (topElement != mappings.get(chars[i])) {return false;}} else {stack.push(chars[i]);}}return stack.isEmpty();
}

反转字符串

思路:将字符串中的每个字符先全部入栈,然后再逐个出栈。

public static void main(String[] args) {String str = "just do it";Stack<Character> stack = new Stack<>();char[] chars = str.toCharArray();for (char c : chars) {stack.push(c);}StringBuilder result = new StringBuilder();for (int i = 0, len = stack.size(); i < len; i++) {result.append(stack.pop());}System.out.println(result.toString());
}

队列

什么是队列?

  • 队列(queue)是一种线性数据结构,特点类似:行驶车辆的单向隧道
  • 队列中的元素按照先入先出(First In First Out,简称FIFO)的规则操作
  • 队列的出口端叫作队头(front),队列的入口端叫作队尾(rear)
  • 队列只允许在队头(front)进行出队 poll操作(删除)
  • 队列只允许在队尾(rear)进行入队 offer操作(添加)
  • 队列按照实现机制的不同分为:单队列和循环队列

队列的实现方式

  • 数组实现的队列叫作顺序队列 
  • 链表实现的队列叫作链式队列

基于数组实现的顺序队列

基于链表实现的链式队列

时间复杂度

假设队列中有n个元素。 

  • 访问指定元素的时间复杂度是O(n):最坏情况下,遍历整个队列
  • 插入删除元素的时间复杂度是O(1):只需要操作队头或队尾元素

单队列的常见操作

入队

入队(enqueue)就是把新元素放入队列中,只允许在队尾的位置放入元素,新元素的下一个位置将会成为新的队尾。

出队

出队(dequeue)就是把元素移出队列,只允许在队头一侧移 出元素,出队元素的后一个元素将会成为新的队头。

“假溢出”

顺序队列存在“假溢出”的问题,也就是明明有位置却不能添加。
下图是一个顺序队列,我们将前两个元素 1,2 出队,并入队两个元素 7,8。当进行入队、出队操作的时候,front 和 rear 都会持续往后移动,当 rear 移动到最后的时候,我们无法再往队列中添加数据,即使数组中还有空余空间,这种现象就是 “假溢出” 。除了假溢出问题之外,如下图所示,当添加元素 8 的时候,rear 指针移动到数组之外(越界)。

循环队列

用数组实现的 队列,可以采用循环队列的方式来维持队列容量的恒定。
例如:
步骤1 : 一个队列经过反复的入队和出队操作,还剩下2个元素,在“物理”上分布于数组的末尾位置。这时又有一个新元素将要入队。

步骤2 : 在数组不做扩容的前提下,我们可以利用已出队元素留下的空间,让队尾指针重新指回数组的首位。

步骤3 : 队尾指针指向数组首位后,整个队列的元素就“循环”起来了。在物理存储上,队尾的位置也可以在队头之前。当再有元素入队时,将其放入数组的首位, 队尾指针继续后移即可。

步骤4 : 直到(队尾下标+1)% 数组长度 = 队头下标,代表此队列真的已经满了。需要注意的是,队尾指针指向的位置永远空出1位,所以队列最大容量比数组长度小1。

综上所述,循环队列满足以下条件:

  • 队空条件:rear == front      
  • 队满条件:(rear + 1) % 数组长度 == front      
  • 计算队列长度:(rear - front + 数组长度) % 数组长度      
  • 入队:(rear + 1)% 数组长度      
  • 出队:(front + 1)% 数组长度
/*** 	循环队列*/
public class CircularQueue {private int[] array; // 基于数组实现private int front; // 队头private int rear; // 队尾public CircularQueue(int capacity) {this.array = new int[capacity];}/*** 	入队* * @param element 入队的元素*/public void offer(int element) throws Exception {if ((rear + 1) % array.length == front) {throw new Exception(" 队列已满!");}array[rear] = element;rear = (rear + 1) % array.length;}/*** 	出队* */public int poll() throws Exception {if (rear == front) {throw new Exception(" 队列已空!");}int deQueueElement = array[front];front = (front + 1) % array.length;return deQueueElement;}/*** 	输出队列*/public String toString() {StringBuilder sb = new StringBuilder();for (int i = front; i != rear; i = (i + 1) % array.length) {sb.append(array[i] + "\t");}return sb.toString();}public static void main(String[] args) throws Exception {CircularQueue myQueue = new CircularQueue(6);myQueue.offer(3);myQueue.offer(5);myQueue.offer(6);myQueue.offer(8);myQueue.offer(1);System.out.println(myQueue);myQueue.poll();myQueue.poll();myQueue.poll();System.out.println(myQueue);myQueue.offer(2);myQueue.offer(4);myQueue.offer(9);System.out.println(myQueue);}
}

队列应用场景

KTV点歌列表

使用队列保存已点歌曲列表,每次点歌时,将歌曲放入队尾。播放歌曲时,从队头取出。符合FIFO存取特点。

阻塞队列

阻塞队列可以看成在队列基础上,通过锁实现线程阻塞操作的特殊队列。当队列为空的时候,出队操作阻塞,当队列满的时候,入队操作阻塞。使用阻塞队列我们可以很容易实现“生产者 - 消费者“模型。

线程池的任务队列

线程池中没有空闲线程时,新的线程任务请求线程资源时,线程池会将这些线程任务放在队列中,当有空闲线程的时候,会循环中反复从队列中获取任务来执行。
队列分为无界队列(基于链表)和有界队列(基于数组)。无界队列的特点就是可以一直入列,除非系统资源耗尽,比如 :FixedThreadPool 使用无界队列 LinkedBlockingQueue。但是有界队列就不一样了,当队列已满的话,后面再有线程任务就会判断是否超出最大线程数,如果超出则执行拒绝策略。

队列例题

使用队列实现栈

实现 MyStack类,使用两个队列实现一个后入先出(LIFO)的栈,并支持普通栈的全部四种操作(push、top、pop 和 empty)。

public class MyStack {Queue<Integer> queue1;Queue<Integer> queue2;public MyStack() {queue1 = new LinkedList<Integer>();queue2 = new LinkedList<Integer>();}public void push(int x) {queue2.offer(x);while (!queue1.isEmpty()) {queue2.offer(queue1.poll());}Queue<Integer> temp = queue1;queue1 = queue2;queue2 = temp;}public int pop() {return queue1.poll();}public int top() {return queue1.peek();}public boolean empty() {return queue1.isEmpty();}
}

使用栈实现队列

实现Queue类,使用两个栈来模拟队列的实现,对外提供三个接口< 入队列,出队列,判空 >。

/** 	Stack栈 LIFO* 	Queue队列 FIFO* * 	使用两个Stack栈实现Queue队列*/
public class Queue {// 入队栈private Stack<Integer> inStack = new Stack<>();// 出队栈private Stack<Integer> outStack = new Stack<>();// 入队public void offer(int item) {while(!outStack.empty()) {inStack.push(outStack.pop());}// 新元素入队inStack.push(item);}// 出队public int poll() {while(!inStack.empty()) {outStack.push(inStack.pop());}return outStack.pop();}// 判断是否为空public boolean empty() {return outStack.size() == 0 && inStack.size() == 0;}
}

测试:

public static void main(String[] args) {Queue myQueue = new Queue();// 入队myQueue.offer(1);myQueue.offer(2);myQueue.offer(3);myQueue.offer(4);myQueue.offer(5);// 出队System.out.println(myQueue.poll()); // 1System.out.println(myQueue.poll()); // 2System.out.println(myQueue.poll()); // 3// 入队myQueue.offer(6);myQueue.offer(7);myQueue.offer(8);// 出队System.out.println(myQueue.poll()); // 4System.out.println(myQueue.poll()); // 5System.out.println(myQueue.poll()); // 6
}

http://www.dtcms.com/a/461144.html

相关文章:

  • 兰州网站制作有哪些怎样下载字体到wordpress
  • 【ASP.NET Core】分布式场景下ASP.NET Core中JWT应用教程
  • C++分布式语音识别服务实践——性能优化与实战部署
  • 【硬核分表】MySQL水平分表全景指南:从策略对比、全局ID到ShardingSphere实战
  • 零基础学AI大模型之Stream流式输出实战
  • Nacos 实战指南:微服务下服务注册与配置管理的完整落地
  • 网站站seo教程深圳有几个区哪个区最富裕
  • 网站seo诊断分析和优化方案企业形象设计课程标准
  • linux中jenkins正常启动外部无法访问
  • 紫砂壶网站开发与设计报告论文大型门户网站建设所具有的功能模块主要有几种类型
  • TCC 与 Saga 分布式事务:最终一致性实战指南
  • python如何把png图片转jpg
  • CentOS 7 上安装 PostgreSQL
  • PCIe协议之Margning篇之 Margining 入门
  • 业主信息查询优化说明
  • 农产品调度运维可视化
  • Javascript本地存储的方式有哪些?区别及应用场景?
  • 【深度学习05】PyTorch:完整的模型训练套路
  • 深入理解C++中的移动语义从拷贝优化到资源所有权的转移
  • 手机网站后台管理郑州制作网站电话133
  • ASP 程序:深入解析与应用实践
  • Spring Cloud与RabbitMQ深度集成:从入门到生产级实战
  • Java学习之旅第二季-15:抽象类
  • GB级csv文件处理
  • 嘉兴 做企业网站seo整站优化价格
  • 【22.2 增强决策树】
  • ComfyUI进行游戏制作需要的算力?
  • 一夜暴富!程序员都热衷炒股吗?
  • 哪些品牌的茶含片比较受欢迎?
  • 前端jquery框架