? 工欲善其事,必先利其器。

栈和队列 - Stack And Queue

如何理解栈呢?

后进者先出,先进者后出,这就是典型的 "栈" 结构

04_栈和队列-栈结构

从栈的操作特性上来看,栈是一种“操作受限”的线性表,只允许在一端插入和删除数据。

事实上,从功能上来说,数组或者链表确实都可以替代栈,但你要知道,特定的数据结构是对特定场景的抽象,而且,数组或链表暴漏了太多的操作接口,操作上的确灵活运用,但同时使用时就变的不可控,自然也就更容易出错。

当某个数据集合只涉及在一端插入和删除数据,并且满足后进先出、先进后出的特性,就应该首选“栈”这种数据结构

实现栈

既然数组或者链表可以替代栈,那么同样的,使用数组和链表可以实现栈。用数组实现的栈叫做顺序栈,用链表实现的栈叫做链式栈。

// 基于数组实现的顺序栈public class ArrayStack {  private String[] items;  // 数组  private int count;       // 栈中元素个数  private int n;           //栈的大小

  // 初始化数组,申请一个大小为n的数组空间  public ArrayStack(int n) {    this.items = new String[n];    this.n = n;    this.count = 0;  }

  // 入栈操作  public boolean push(String item) {    // 数组空间不够了,直接返回false,入栈失败。    if (count == n) return false;    // 将item放到下标为count的位置,并且count加一    items[count] = item;    ++count;    return true;  }

  // 出栈操作  public String pop() {    // 栈为空,则直接返回null    if (count == 0) return null;    // 返回下标为count-1的数组元素,并且栈中元素个数count减一    String tmp = items[count-1];    --count;    return tmp;  }}

上面代码是拷贝网上的,使用java的数组实现栈。

了解了定义和基本操作,那它的操作的时间、空间复杂度是多少呢?

不管是顺序栈还是链式栈,存储数据只需要一个大小为 n 的数组就够了。在入栈和出栈过程中,只需要一两个临时变量存储空间,所以空间复杂度是 O(1)。

注意,这里存储数据需要一个大小为 n 的数组,并不是说空间复杂度就是 O(n)。因为,这 n 个空间是必须的,无法省掉。所以我们说空间复杂度的时候,是指除了原本的数据存储空间外,算法运行还需要额外的存储空间

不管是顺序栈还是链式栈,入栈、出栈只涉及栈顶个别数据的操作,所以时间复杂度都是 O(1)。

栈的应用 - 四则运算

四则运算是编译器利用栈来实现的表达式求值。

对于四则运算来说,我们大脑可以很快的计算出来,但是对于计算机而言,理解四则运算本来就难。

那么编译器如何通过栈来解决四则运算,首先我们需要两个栈,一个保存操作数,一个保存运算符。从左向右遍历表达式,当遇到数字,我们就直接压入操作数栈;当遇到运算符,就与运算符栈的栈顶元素进行比较。

如果比运算符栈顶元素的优先级高,就将当前运算符压入栈;如果比运算符栈顶元素的优先级低或者相同,从运算符栈中取栈顶运算符,从操作数栈的栈顶取 2 个操作数,然后进行计算,再把计算完的结果压入操作数栈,继续比较。

04_栈和队列-四则运算

队列

栈是后进先出,那有没有先进先出的呢?当然有,就是队列。

先进先出,这就是典型的“队列”

栈只支持两个基本操作:入栈 push()和出栈 pop()。队列跟栈非常相似,支持的操作也很有限,最基本的操作也是两个:入队 enqueue(),放一个数据到队列尾部;出队 dequeue(),从队列头部取一个元素。

04_栈和队列-队列

所以,队列跟栈一样,也是一种操作受限的线性表数据结构

队列的概念很好理解,基本操作也很容易掌握。作为一种非常基础的数据结构,队列的应用也非常广泛,特别是一些具有某些额外特性的队列,比如循环队列、阻塞队列、并发队列。它们在很多偏底层系统、框架、中间件的开发中,起着关键性的作用。比如高性能队列 Disruptor、Linux 环形缓存,都用到了循环并发队列;Java concurrent 并发包利用 ArrayBlockingQueue 来实现公平锁等。

实现队列

跟栈一样,队列可以用数组来实现,也可以用链表来实现。用数组实现的栈叫作顺序栈,用链表实现的栈叫作链式栈。同样,用数组实现的队列叫作顺序队列,用链表实现的队列叫作链式队列

// 用数组实现的队列public class ArrayQueue {  // 数组:items,数组大小:n  private String[] items;  private int n = 0;  // head表示队头下标,tail表示队尾下标  private int head = 0;  private int tail = 0;

  // 申请一个大小为capacity的数组  public ArrayQueue(int capacity) {    items = new String[capacity];    n = capacity;  }

  // 入队  public boolean enqueue(String item) {    // 如果tail == n 表示队列已经满了    if (tail == n) return false;    items[tail] = item;    ++tail;    return true;  }

  // 出队  public String dequeue() {    // 如果head == tail 表示队列为空    if (head == tail) return null;    // 为了让其他语言的同学看的更加明确,把--操作放到单独一行来写了    String ret = items[head];    ++head;    return ret;  }}

对于栈来说,我们只需要一个栈顶指针就可以了。但是队列需要两个指针:一个是 head 指针,指向队头;一个是 tail 指针,指向队尾。

当 a、b、c、d 依次入队之后,队列中的 head 指针指向下标为 0 的位置,tail 指针指向下标为 4 的位置。

当我们调用两次出队操作之后,队列中 head 指针指向下标为 2 的位置,tail 指针仍然指向下标为 4 的位置。

04_栈和队列-队列运行

随着不停地进行入队、出队操作,head 和 tail 都会持续往后移动。当 tail 移动到最右边,即使数组中还有空闲空间,也无法继续往队列中添加数据了。

在数组那一节,我们也遇到过类似的问题,就是数组的删除操作会导致数组中的数据不连续。你还记得我们当时是怎么解决的吗?对,用数据搬移!

每次进行出队操作都相当于删除数组下标为 0 的数据,要搬移整个队列中的数据,这样出队操作的时间复杂度就会从原来的 O(1) 变为 O(n)。实际上,我们在出队时可以不用搬移数据。如果没有空闲空间了,我们只需要在入队时,再集中触发一次数据的搬移操作。

所以改造下上面的代码后:

   // 入队操作,将item放入队尾  public boolean enqueue(String item) {    // tail == n表示队列末尾没有空间了    if (tail == n) {      // tail ==n && head==0,表示整个队列都占满了      if (head == 0) return false;      // 数据搬移      for (int i = head; i         items[i-head] = items[i];      }      // 搬移完之后重新更新head和tail      tail -= head;      head = 0;    }

    items[tail] = item;    ++tail;    return true;  }

从代码中我们看到,当队列的 tail 指针移动到数组的最右边后,如果有新的数据入队,我们可以将 head 到 tail 之间的数据,整体搬移到数组中 0 到 tail-head 的位置。

04_栈和队列-队列数据搬移

出队操作的时间复杂度仍然是 O(1),但是入队操作时需要使用前面提到的时间复杂度中的摊还分析,时间复杂度仍是 O(1) 。

循环队列

刚才用数组来实现队列的时候,在 tail==n 时,会有数据搬移操作,这样入队操作性能就会受到影响。那有没有办法能够避免数据搬移呢?我们来看看循环队列的解决思路。

循环队列,顾名思义,它长得像一个环。原本数组是有头有尾的,是一条直线。现在我们把首尾相连,扳成了一个环。如下图所示

04_栈和队列-循环队列

队列应用

队列这种数据结构很基础,平时的业务开发不大可能从零实现一个队列,甚至都不会直接用到。而一些具有特殊特性的队列应用却比较广泛,比如阻塞队列和并发队列。

阻塞队列其实就是在队列基础上增加了阻塞操作。简单来说,就是在队列为空的时候,从队头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回。

线程安全的队列我们叫作并发队列。最简单直接的实现方式是直接在 enqueue()、dequeue() 方法上加锁,但是锁粒度大并发度会比较低,同一时刻仅允许一个存或者取操作。实际上,基于数组的循环队列,利用 CAS 原子操作,可以实现非常高效的并发队列。这也是循环队列比链式队列应用更加广泛的原因。

历史系列文章
  1. 算法与数据结构(四)- 链表

  2. 算法与数据结构(三)- 数组

  3. 算法与数据结构(二)- 算法分析

  4. 算法与数据结构(一)- 概述

队列的基本操作_算法与数据结构(五) 栈和队列相关推荐

  1. 队列的基本操作_算法设计:数据结构-队列

    一.队列 1.队列的概念 队列是一种"先进先出(first in first out)"的数据结构,它是一种有序线性表的抽象数据类型.队列在计算机 领域的应用也相当的广泛,例如计算 ...

  2. 算法与数据结构(part5)--栈与队列

    学习笔记,仅供参考,有错必纠 文章目录 算法与数据结构–基于python 栈 栈是什么 栈(ADT)的操作 栈的实现 队列 队列是什么 队列的操作 队列的实现 双端队列 双端队列是什么 双端队列的操作 ...

  3. 浅谈算法和数据结构: 五 优先级队列与堆排序

    原文:浅谈算法和数据结构: 五 优先级队列与堆排序 在很多应用中,我们通常需要按照优先级情况对待处理对象进行处理,比如首先处理优先级最高的对象,然后处理次高的对象.最简单的一个例子就是,在手机上玩游戏 ...

  4. 数据结构:栈和队列(Stack Queue)【详解】

    友情链接:数据结构专栏 目录 栈和队列 [知识框架] 栈 一.栈的基本概念 1.栈的定义 2.栈的常见基本操作 二.栈的顺序存储结构 1.栈的顺序存储 2.顺序栈的基本算法 (1)初始化 (2)判栈空 ...

  5. C语言【数据结构】栈和队列【OJ题(C++)、选择题】

    目录 一.OJ题 1.225. 用队列实现栈 2.232. 用栈实现队列 3.622. 设计循环队列 4.20. 有效的括号 二.选择题 1.下列关于栈的叙述正确的是(B) 2.一个栈的入栈序列为AB ...

  6. 10.数据结构:栈和队列

    大家好,我王有志又回来啦.关注王有志,回复DSA获取数据结构和算法学习资源. 最近被全链路优化搞得焦头烂额,等抽出时间来和大家分享下我司正在做的"全面提速工程". 今天我们来学习线 ...

  7. 数据结构之—栈和队列

    目录 引言 一.栈(stack) 1.栈的应用 2.栈的实现 二.队列 1.基础队列的实现 2.循环队列 ​编辑 1)概念 2)如何判断环形队列为空⭐ 总结:环形队列是否已满条件:(tail+1)%d ...

  8. 数据结构之栈和队列以及如何封装栈和队列,栈和队列的实例(进制转换和击鼓传花)

    什么是数据结构? 不同的书对数据结构有不同的定义,例如: "数据结构是数据对象,以及存在于该对象的实例和 组成实例的数据元素之间的各种联系.这些联系可以通过定义相关的函数来给出." ...

  9. 【数据结构】栈与队列区分push pop offer poll containsKey put等

    目录 前言 正文 队列 栈 map集合 前言 算法中经常会用到栈和队列等数据结构 但是经常弄混他们的进与取的代码算法 此文主要是做一个区分度 用法以及注意事项详情可看我之前的文章 [数据结构]栈和队列 ...

最新文章

  1. 使用JCIFS获取远程共享文件
  2. 线上比赛中关于视觉AI组与信标组补充说明
  3. as 关联 android源码,android studio 2.x以上关联源码
  4. QEMU和KVM 中断处理过程
  5. 消息队列MQ/JMS/Kafka,你都了解多少?
  6. 不同虚拟局域网Vlan配置DHCP服务器
  7. Replace Method with Method Object(以函数对象取代函数)
  8. 学习c#不容错过的网站
  9. mex2 Inputs and Outputs
  10. 从一个MFC工程移植对话框类到另一个MFC工程
  11. CentOS6.0升级内核为6.2
  12. 【一天一个NLP任务】(Day 1)——BERT解决中文情绪分类任务
  13. 毕设题目:Matlab图像配准
  14. 周伟焜:IBM为何重造信仰
  15. 树莓派4B连接显示器 黑屏、左上角有光标问题
  16. Packetfence 开源网络准入系统
  17. Unity 个人玩法Demo合集
  18. 使用Python将PDF转换为WORD
  19. TensorFlow报错:AttributeError: module 'tensorflow._api.v1.train' has no attribute 'SummaryWriter'等
  20. 【STM32利用CuBe MX生成HID设备】2-给游戏控制器添加X\Y轴

热门文章

  1. python自动控制库_一个可以自动化控制鼠标键盘的库:PyAUtoGUI
  2. 无锡c语言编程培训学校,无锡c语言培训班——C语言基础知识
  3. layui向body添加html_layui 各项配置
  4. json里面的list数据取不出来_sql盲注的困局:利用DNSlog快速导出数据
  5. android网络盒子改,有什么办法可以把淘汰下来的安卓手机改造成电视盒子用?...
  6. php issign为false,支付宝接口集成及错误排除
  7. hbasehlog_HBase原理--RegionServer核心组件之HLog
  8. 【Python爬虫】微信公众号历史文章和文章评论API分析
  9. python面向对象基础之类与实例
  10. Python档案袋( 命令行操作 及 Os与Shutil文件操作补充 )