栈和队列总结
题目
面试题目:栈里面的元素在内存中是连续分布的么?
(这个问题有两个陷阱:
陷阱1:栈是容器适配器,底层容器使用不同的容器,导致栈内数据在内存中是不是连续分布。
陷阱2:缺省情况下,默认底层容器是deque
,那么deque
的在内存中的数据分布是什么样的呢? 答案是:不连续的。)
栈(std::stack
)是一个容器适配器,它的实现取决于底层容器的实现。在C++中,std::stack
的底层默认容器是 std::deque
,但也可以被特化为使用 std::vector
或 std::list
等。
std::stack
的元素在内存中是否连续分布,完全取决于底层容器的实现。如果底层容器是std::vector
,那么元素在内存中是连续分布的;如果底层容器是std::list
,那么元素在内存中就是非连续分布的。std::deque
的内存布局是一个稍微复杂的话题。std::deque
(双端队列)是一个由多个固定大小的数组组成的数据结构,这些数组的元数据(指向这些数组的指针)在内存中是连续存储的,但这些数组本身可能在内存中是分散的,即非连续的。因此,std::deque
的元素在内存中通常是非连续分布的。
因此,如果使用默认的 std::deque
作为 std::stack
的底层容器,那么栈的元素在内存中通常是非连续分布的。这也是为什么 std::stack
并没有提供直接访问内部元素的接口(如下标运算符)的原因。
栈在系统中的应用
-
编译器在词法分析的过程中处理括号、花括号等这个符号的逻辑,就是使用了栈这种数据结构。
-
linux系统中,cd这个进入目录的命令,
cd a/b/c/../../
这个命令最后进入a目录,系统是如何知道进入了a目录呢?也是栈的应用。
这两个问题具体在前面博客中都有整理。
- 递归的实现是栈:每一次递归调用都会把函数的局部变量、参数值和返回地址等压入调用栈中,然后递归返回的时候,从栈顶弹出上一次递归的各项参数,所以这就是递归为什么可以返回上一层位置的原因。
所以栈在计算机领域中应用是非常广泛的。
下面栈和队列的题目总结from:代码随想录:栈与队列总结
括号匹配问题
括号匹配是使用栈解决的经典问题。
建议要写代码之前要分析好有哪几种不匹配的情况,如果不动手之前分析好,写出的代码也会有很多问题。
先来分析一下 这里有三种不匹配的情况,
- 第一种情况,字符串里左方向的括号多余了 ,所以不匹配。
- 第二种情况,括号没有多余,但是 括号的类型没有匹配上。
- 第三种情况,字符串里右方向的括号多余了,所以不匹配。
这里还有一些技巧,在匹配左括号的时候,右括号先入栈,就只需要比较当前元素和栈顶相不相等就可以了,比左括号先入栈代码实现要简单的多了!
字符串去重问题
思路就是可以把字符串顺序放到一个栈中,然后如果相同的话,栈就弹出,这样最后栈里剩下的元素都是相邻不相同的元素了。
逆波兰表达式问题
本题中每一个子表达式要得出一个结果,然后拿这个结果再进行运算,那么这就是一个相邻字符串消除的过程,和栈与队列:匹配问题都是栈的强项 (opens new window)中的对对碰游戏是不是就非常像了。
队列的应用:滑动窗口最大值
主要思想是队列没有必要维护窗口里的所有元素,只需要维护有可能成为窗口里最大值的元素就可以了,同时保证队列里的元素数值是由大到小的。
那么这个维护元素单调递减的队列就叫做单调队列,即单调递减或单调递增的队列。C++中没有直接支持单调队列,需要我们自己来实现一个单调队列。
实现的单调队列并不是对窗口里面的数进行排序!如果排序的话,和优先级队列没有区别。
设计单调队列的时候,pop,和push操作要保持如下规则:
- pop(value):如果窗口移除的元素value等于单调队列的出口元素,那么队列弹出元素,否则不用任何操作
- push(value):如果push的元素value大于入口元素的数值,那么就将队列出口的元素弹出,直到push元素的数值小于等于队列入口元素的数值为止
保持如上规则,每次窗口移动的时候,只要问que.front()就可以返回当前窗口的最大值。
单调队列,首先要明确的是,题解中单调队列里的pop和push接口,仅适用于本题。
单调队列不是一成不变的,而是不同场景不同写法,总之要保证队列里单调递减或递增的原则,所以叫做单调队列。
本题目中的单调队列实现不是固定的写法。
我们用deque作为单调队列的底层数据结构,C++中deque是stack和queue默认的底层实现容器(这个我们之前已经讲过),deque是可以两边扩展的,而且deque里元素并不是严格的连续分布的。
队列的应用:求前k个高频元素
通过求前 K 个高频元素,引出另一种队列就是优先级队列。
什么是优先级队列呢?
其实就是一个披着队列外衣的堆,因为优先级队列对外接口只是从队头取元素,从队尾添加元素,再无其他取元素的方式,看起来就是一个队列。
而且优先级队列内部元素是自动依照元素的权值排列。那么它是如何有序排列的呢?
缺省情况下priority_queue利用max-heap(大顶堆)完成对元素的排序,这个大顶堆是以vector为表现形式的complete binary tree(完全二叉树)。
什么是堆呢?
堆是一棵完全二叉树,树中每个结点的值都不小于(或不大于)其左右孩子的值。 如果父亲结点是大于等于左右孩子就是大顶堆,小于等于左右孩子就是小顶堆。
所以大家经常说的大顶堆(堆头是最大元素),小顶堆(堆头是最小元素),如果懒得自己实现的话,就直接用priority_queue(优先级队列)就可以了,底层实现都是一样的,从小到大排就是小顶堆,从大到小排就是大顶堆。
本题就要使用优先级队列来对部分频率进行排序。 注意这里是对部分数据进行排序而不需要对所有数据排序!
所以排序的过程的时间复杂度是O(\log k),整个算法的时间复杂度是O(n\log k)。
参考:
https://programmercarl.com/%E6%A0%88%E4%B8%8E%E9%98%9F%E5%88%97%E6%80%BB%E7%BB%93.html#%E6%A0%88%E5%9C%A8%E7%B3%BB%E7%BB%9F%E4%B8%AD%E7%9A%84%E5%BA%94%E7%94%A8