c++数据结构中 顺序队列的队首队尾_数据结构与算法(三)栈和队列
前言
本节分享数据结构中的基础数据结构栈和队列。他们都是操作受限的线性表数据结构
栈
什么是栈?栈的最大特点就是先进后出(LIFO),对于栈中的数据来说,所有的操作都是在栈顶完成的,举个例子:叠盘子,盘子都是一个叠在一个上面,我们想要拿盘子,也只能从最上面的一个一个拿起来!
栈实现:我们可以利用数组来实现栈结构,也可以利用一个单链表实现栈结构,用数组实现的栈结构叫做:顺序栈,用单链表实现的栈结构叫做:链式栈。因为我们只能操作栈顶元素,所以不管是链式栈还是顺序栈,入栈、出栈、只涉及栈顶的数据操作,时间复杂度都是O(1)。
基于数组实现:
/** * 基于数组实现栈结构,入栈出栈时间复杂度都是O(1) * **注意**:这里的空间复杂度并不是O(n),因为空间复杂度指的是除了原本数据存储空间外, * 算法运行还需要的额外存储空间。所以这里的数组长度size,并不能说明空间复杂度就是O(n)。 * 因为存储空间只需要额外的变量来存储出栈、入栈的值,所以空间复杂度O(1) */public class ArrayStack { private String[] values;//数组 private int count;//定义栈中元素的个数 private int size;//栈的大小 //初始化数组,申请一个大小为size的数组空间 public ArrayStack(int size){ this.values=new String[size]; this.size=size; this.count=0; } //入栈操作 public boolean push(String value){ if(count == size){ return false;//栈满了,入栈失败 } //将元素直接放到当前的count位置 values[count]=value; ++count; return true; } //出栈操作 public String pop(){ if(count == 0){ return null;//栈为空 } //返回下标是count-1的元素,因为数组排序从0开始排的, // 比如三个数据对应的索引是0、1、2,而不是1、2、3 String value=values[count-1]; //元素个数减少一 --count; return value; }}
这里有一点我们需要注意,上面的代码中我们定义的栈依赖的数组长度是固定的,但是如果我们如果我们定义的栈依赖的数组的长度是支持动态扩展的呢?比如数组长度是4,当数组满了后,支持动态扩展,数组扩展长度为8,就需要将原来的数据复制到新的数组中,这个时候的入栈时间复杂就是O(n),因为出栈不涉及数组扩容,所以时间复杂度还是O(1)。
基于链表实现:
/** * 基于链表实现栈结构 */public class LinkedStack { //创建栈顶指针 Node top=null; //入栈操作 public void push(String value){ Node newNode=new Node(value,null); if(top == null){ top=newNode;//栈为空的时候 }else { //将新节点放到栈顶,通过改变新节点的指针指向top节点, //top节点指向新节点 newNode.next=top; top=newNode; } } //出栈操作 public String pop(){ if(top == null){ return null;//栈空返回null }else { //获取栈顶元素的值,通知将top指针指向top的next节点 String value=top.getData(); top=top.next; return value; } } //输出栈中的所有元素 public void printAll(){ Node node=top; while (node != null){ System.out.println(node.getData()); node=node.next; } } //定义单链表结构 private static class Node{ private String data; private Node next; public Node(String data,Node next){ this.data=data; this.next=next; } public String getData(){ return data; } }}
应用场景:当某个业务满足数据在一端插入和删除。并且满足后进先出、先进后出的特性,我们就应该首先选择栈这种结构。
习题练习:
题目描述:leetCode第20题:给定一个只包括'(', ')', '{', '}', '[', ']'的字符串,判断字符串是否有效?有效字符串必须满足:1.左括号必须拥有相同的右括号闭合。左括号必须以正确的顺序闭合
例如:
示例1:
输入:“()”
输出:true
示例2:
输入:" (] "
输出:false
代码展示:
示例3
输入:" (])] "
输出:false
代码展示:
public class Solution { //预先将左括号作为key,右括号作为value存储在map集合中 private static final Map map=new HashMap(){ { put('{','}'); put('[',']'); put('(',')'); put('?','?'); } }; public static void main(String[] args) { //结果打印true System.out.println(isValid("{([]{()}[])}")); //结果打印false System.out.println(isValid("{(")); //结果打印false System.out.println(isValid("{(]}")); } public static boolean isValid(String str){ //如果存在首字符不在map中直接返回false if(str.length()>0 && !map.containsKey(str.charAt(0))){ return false; } //用我们定义的链式栈,不过这里我把栈底层的Node节点的data改为了Character方便测试 LinkedStack linkedStack=new LinkedStack(); for(Character c:str.toCharArray()){ if(map.containsKey(c)){ //如果是左括号就入栈 linkedStack.push(c); } else { //如果遇见右括号,就出栈,通过去map中获取对应的右括号, //如果获取的右括号不等于c,就说明不符合. if((map.get(linkedStack.pop()) != c)){ return false; } } } //如果遍历完栈中元素个数不为0,则说明字符无效可能都是左括号 return linkedStack.size() == 0; }}
队列
顺序队列和链式队列
队列特点:先进先出,基本操作:入队:放一个数据到队尾,出队:从队列头取出一个数据,可以理解为一个管道,管道中的水就是数据,那么就是管道的一段进水(入队),另一端出水(出队)。
队列实现:同样也有顺序队列和链式队列,顺序队列基于数组,链式队列基于链表,相比于栈结构,只需要一个栈顶指针,然后队列结构需要两个指针,一个是head指针,指向对头,一个是队尾tail,指针指向队尾,顺序队列。代码如下:
public class ArrayQueue { //定义数组arr和数组长度 private String[] arr; private int size; //定义头指针和尾指针 private int head=0; private int tail=0; //初始化数组大小 public ArrayQueue(int capacity){ this.arr=new String[capacity]; this.size=capacity; } //入队 public boolean enQueue(String value){ //判断队列是否已满 if(tail == size){ return false; } arr[tail]=value; ++tail; return true; } //出队 public String deQueue(){ //对列空 if(head == tail){ return null; } String value =arr[head]; ++head; return value; }}
以上代码是基于数组队列的基本实现,但是有个小问题,就是队列的入队和出队执行次数到一定数量的时候,会出现当指针tail移动到队尾后,队列并没有全满的情况下,不能继续执行入队操作了,如图:
我们执行几次入队操作后的图
我们执行几次出队和入队的操作后
此时,tail指针已经到达队尾了,但是队列不为空,却不可以继续执行入队操作了,所以当tail指针移动到队尾的时候,我们需要数据迁搬迁,为了保证性能,我们不需要每次入队都进行数据迁搬迁,只需要当数据无法执行入队操作的时候执行一次集中的数据迁搬迁操作,所以上述代码中,enQueu()方法可修改为:
//入队 public boolean enQueue(String value){ //tail == size意味tail已经到了队尾 if(tail == size){ //判断当tail == size && head == 0说明队列满了 if(head == 0){ return false; } //数据迁移 for(int i=head;i arr[i-head] = arr[head]; } //充值head和tail指针 tail=tail-head; head=0; } arr[tail]=value; ++tail; return true; }
基于链表的队列同样需要两个指针,head和tail一个直指向头节点,一个指向尾节点,链式队列代码如下:
图示:
代码如下:
public class LinkQueue { //定义基础链表节点结构 private static class Node{ private String data; private Node next; public Node(String data,Node next){ this.data=data; this.next=next; } public String getData(){ return data; } } //定义对头和队尾 private Node head=null; private Node tail=null; //入队 public void enQueue(String value){ //判断队列都为空的时候入队 if(tail == null){ Node newNode=new Node(value,null); head=newNode; tail=newNode; } else { //判断队列已经有数据的时候入队 tail.next=new Node(value,null); tail=tail.next; } } //出队 public String deQueue(){ if(head == null){ return null; } String value =head.getData(); head=head.next; if(head == null){ tail=null; } return value; }}
循环队列
循环队列就像是一个环,原本的数组是有头有尾的,像是一条直线,我们把它的收尾相连就成了一个环形的循环队列,循环队列如下图:
循环队列的好处是避免了数据迁搬迁,实现循环队列需要注意确定好判断队列空和队列满的条件!
循环队列判断空的条件仍然是tail==head,判断队列满的情况如图:
通过规律可以发现,判断满的条件是(tail+1)%n=head,公式推导:(在一般情况下,我们可以看出来,当队列满时,tail+1=head。但是,有个特殊情况,就是tail=size -1而head=0时,这时候,tail+1=size ,而head=0,所以用(tail+1)%size == size%size == 0。而且,tail+1最大的情况就是 size ,不会大于 size ,这样,tail+1 除了最大情况,不然怎么余取余size都是 tail+1 本身,也就是 head。这样,表达式就出现了。)我们只需要记住就行了,当队列满时,图中的 tail 指向的位置实际上是没有存储数据的。所以,循环队列会浪费一个数组的存储空间。
基于数组的实现:
/** * 基于数组实现循环队列 * 判断队列为空tail==head * 判断队列满head=(tail+1)%size */public class CircularQueue { private String [] arr; private int size; private int head=0; private int tail=0; //初始化数组 public CircularQueue(int capacity){ this.arr=new String[capacity]; this.size=capacity; } //入队 public boolean enQueue(String value){ if(head == (tail+1)%size){ return false;//队列满了 } arr[tail]=value; tail=(tail+1)%size; return true; } //出队 public String deQueue(){ if(head == 0){ return null; } String value = arr[head]; head=(head+1)%size; return value; }}
阻塞队列和并发队列
阻塞队列其实就是在队列基础上增加了阻塞操作。简单来说,就是在队列为空的时候,从队列头取数据会被阻塞。因为此时还没有数据可取,直到队列中有了数据才能返回;如果队列已经满了,那么插入数据的操作就会被阻塞,直到队列中有空闲位置后再插入数据,然后再返回,例如java concurrent并发包用ArrayBlockingQueue来实现公平锁。
阻塞队列可用于生产者消费者模式,可以通过协调生产者和消费者的个数来提高数据的处理效。如图:
线程安全的队列我们叫并发队列,最简单的就是在enQueue()方法上加synchronized锁,但是锁的粒度比较大并发度会比较低,还可以利用CAS原子操作,可以实现非常高效的并发队列这也是循环队列比链式队列应用更加广泛的原因,java中的ConcurrentLinkedQueue中的offer方法就有用到CAS操作。
总结
本节主要分享了栈和队列的一些概念和基本特性,并且讲解了如何用数组或者链表去实现栈和队列的结构,已经一些扩展概念,对于如果想更好的掌握和运用这些结构,建议多去leetCode刷刷题,这样可以不仅可以锻炼思维逻辑,也能更加好的理解,掌握这些基本数据和使用场景。
c++数据结构中 顺序队列的队首队尾_数据结构与算法(三)栈和队列相关推荐
- c++数据结构中 顺序队列的队首队尾_数据结构与算法—队列详解
前言 栈和队列是一对好兄弟,前面我们介绍过数据结构与算法-栈详解,那么栈的机制相对简单,后入先出,就像进入一个狭小的山洞,山洞只有一个出口,只能后进先出(在外面的先出去).而队列就好比是一个隧道,后面 ...
- c++数据结构中 顺序队列的队首队尾_数据结构与算法—队列图文详解
前言 栈和队列是一对好兄弟,前面我们介绍过数据结构与算法-栈详解,那么栈的机制相对简单,后入先出,就像进入一个狭小的山洞,山洞只有一个出口,只能后进先出(在外面的先出去).而队列就好比是一个隧道,后面 ...
- c++数据结构中 顺序队列的队首队尾_数据结构 3.3 顺序队
一些废话 光有理论是远远不够的,只有自己亲自敲过代码,做过编程题才能说自己学过数据结构.但被PTA的ds习题折磨了几天之后,我领悟到一个道理: 学习理论时可以用C语言造轮子深入理解. 上机做题时使用C ...
- c++数据结构中 顺序队列的队首队尾_yiduobo的每日leetcode 622.设计循环队列
祖传的手艺不想丢了,所以按顺序写一个leetcode的题解.计划每日两题,争取不卡题吧. 622.设计循环队列https://leetcode-cn.com/problems/design-circu ...
- c++数据结构中 顺序队列的队首队尾_用队列实现栈,用栈实现队列,听起来有点绕,都搞懂了就掌握了精髓
一.背景 栈和队列是数据结构中最常用到的两种结构,有非常广泛的运用,该篇文章将通过动画的手段,展示栈和队列相互实现的底层原理,让我们真正搞懂栈和队列的特性. 二.概念 2.1 栈 栈[Stack]:是 ...
- abcde依次进入一个队列_数据结构与算法 | 一文掌握队列Queue(真题讲解)
本系列内容专为课程面向笔/面试的<数据结构与算法>总结性精讲开设,以图文并茂的方式讲解数据结构,让大家打牢基础,促进对课程内容的掌握,最后做到题解大神,大厂offer拿到手软! 目录:一文 ...
- 栈和队列都是什么结构_数据结构与算法之初识栈与队列
栈和队列 学习目标 本节我们将初步认识栈和队列,栈和队列是限定插入和删除只能在表的"端点"进行的线性表. 开始学习 01栈 是什么? 限定仅在表尾进行插入和删除操作的线性表,表尾- ...
- 数据结构与算法--利用栈实现队列
利用栈实现队列 上一节中说明了栈的特点 后进先出,我们用数组的方式实现了栈的基本操作api,因此我们对栈的操作是不考虑排序的,每个api的操作基本都是O(1)的世界,因为不考虑顺序,所以找最大,最小值 ...
- 数据结构与算法(2)——栈和队列
前言:题图无关,只是好看,接下来就来复习一下栈和队列的相关知识 前序文章: 数据结构与算法(1)--数组与链表(https://www.jianshu.com/p/7b93b3570875) 栈 什么 ...
- 【数据结构与算法】栈与队列
栈 一.什么是栈? 1.后进者先出,先进者后出,这就是典型的"栈"结构. 2.从栈的操作特性来看,是一种"操作受限"的线性表,只允许在端插入和删除数据. 二.为 ...
最新文章
- mysql 多个游标_mysql 存储过程中使用多游标
- 怎么一步步编写简单的PHP的Framework(二十一)
- Windows下配置环境变量和需不需要重启?
- html外边距的复合属性是,margin
- Oracle设置权限和还原数据库
- asp.net core web mvc之异常
- Android之判断手机黑屏以及锁屏
- 训练日志 2019.7.27
- 2018北京ICPC H. Approximate Matching(AC自动机+DP)
- 一款响应式的(电子报)Newsletter 模板 – Antwort
- 百炼-2726:采药
- !! A股历史平均市盈率走势图
- 浪潮互联网峰会张冬技术报告
- GPS定位基本原理浅析
- 细说linux挂载——mount,及其他……
- selenium之find_element_by_xpath定位元素
- Box3 代码教程 (一)
- 国家战略下的技术自强,百度飞桨的时代之歌
- namenode和datanode工作机制_NameNode与DataNode的工作原理剖析
- 数据结构之[关键路径]以及[拓扑算法优化]
热门文章
- Arcgis Javascript API 开发笔记
- Dijkstra最短路径
- UVALive 3135
- mysql 5.7 存储引擎_mysql5.7——innodb存储引擎总结-阿里云开发者社区
- lol最强最高输出的adc_lol最强adc大盘点 lol大后期胜率最高的adc除了薇恩还有谁...
- python if else break_Python条件判断 if-else for循环 while循环 break continue
- 利用openmp实现矩阵相乘_矩阵快速幂
- layui表格使用复选框批量删除_layui表格数据复选框回显设置方法,表格复选框...
- spark 动态预加载数据_Spark+TDengine 在中国电信电力测功系统监控平台上的应用实践...
- net configuration assistant 没反应_@尾款人:错过这条推送,7000多块就没了……