提示:栈和队列相关例题见
栈和队列——相关例题
一、前言
- 栈和队列是两种常用的、重要的数据结构。
- 栈和队列是限定插入和删除只能在表的端点进行的线性表。
- 普通线性表的插入和删除操作如下:
- 栈和队列的各项具体操作应该由具体情况进行代码的编写,故在此不进行详细编写,在例题当中会进行体现。
二、栈
- 栈是先进后出。类似于手电筒的电池,弹夹里的子弹和堆摞的盘子。
- 如果问题求解的过程具有后进先出的特性时,则求解的算法中也必然需要使用栈。
1. 栈的定义和特点
- 栈(stack) 是一个特殊的线性表,是限定仅在一端(通常是表尾)进行插入和删除操作的线性表。
- 由于最后一个进行的要最先被删除,因此又称为后进先出的线性表,检测 LIFO 结构。
- 插入元素到栈顶(即表尾)的操作,称为 入栈。
- 从栈顶(即表尾)删除最后一个元素的操作,称为 出栈 。
- 栈只能在栈顶进行操作
2. 栈的操作
2.1 入栈示意图
2.2 出栈示意图
3. 栈与一般线性表的对比
结构规则 | 一般线性表 | 栈 |
---|
逻辑结构 | 一对一 | 一对一 |
存储结构 | 顺序栈、链栈 | 顺序栈、链栈 |
运算规则 | 随机存取 | 后进先出 |
4. 顺序栈
- 存储方式:同一般线性表的顺序存储结构完全相同,利用一组地址连续的存储单元依次存放自栈底到栈顶的数据元素。栈底一般在低地址端。
- 设 top 指针,指示栈顶元素在顺序栈中的位置。但是,为了操作方便,通常 top 指示真正的 栈顶元素之上 的下标地址。
- 设 base 指针,指示栈底元素在顺序栈中的位置。
- 使用 stacksize 表示栈可使用的最大容量。
- 栈满时,要分配更大的空间,作为栈的存储空间,将原栈的内容移入新栈。
5. 链栈
- 链栈时运算受限的单链表,只能在 链表头部 进行操作。
- 链表的头指针就是栈顶,不需要头节点。
- 空栈相当于头指针指向空,插入和删除仅在栈顶处执行。
6. 栈与递归
- 若一个对象部分地包含自己,或者自己给自己定义,则称这个对象是递归地。
- 若一个过程直接地或间接地调用自己,则称这个过程是递归的过程。
- 递归必备的三个条件:
- (1)能将一个问题转变成一个新问题,而新问题与原问题的解法相同或类同,不同的仅是处理的对象,且这些处理对象是变化有规律的。
- (2)可以通过上述转化而使问题简化。
- (3)必须有一个明确的递归出口,或称递归的边界。
- 借助栈改写递归:
- (1)递归程序在执行时需要系统提供栈来实现。
- (2)仿照递归算法执行过程中递归工作栈的状态变化可写出相应的非递归程序。
- (3)改写后的非递归算法与原来的递归算法相比,结构不够清晰,可读性较差,有的还需要经过一系列优化。
三、队列
- 队列是先进先出。类似于生活中的排队问题。
- 由于队列的操作具有先进先出的特性,使得队列成为程序设计中解决类似排队问题的有用工具。
1. 队列的定义和特点
- 队列(queue) 是一种先进先出的线性表。在表一端插入(表尾),在另一端(表头)删除。
- 由于最后一个进行的要最先被删除,因此又称为后进先出的线性表,检测 LIFO 结构。
- 插入元素到栈顶(即表尾)的操作,称为 入栈。
- 从栈顶(即表尾)删除最后一个元素的操作,称为 出栈 。
- 栈只能在栈顶进行操作
2. 队列的操作
- 关键是掌握入队和出队操作,具体实现以顺序队或链队的不同而不同。
3. 队列与一般线性表的对比
结构规则 | 一般线性表 | 栈】队列 |
---|
逻辑结构 | 一对一 | 一对一 |
存储结构 | 顺序栈、链栈 | 顺序队、链队 |
运算规则 | 随机存取 | 先进先出 |
4. 顺序队列
- 队列的顺序表示——一维数组。
- 顺序队的四要素(初始时front=rear=-1):
- rear指向队尾元素;front指向队头元素的前一个位置。
- 队空条件:front = rear 。
- 队满条件:rear=MaxSize-1 。
- 元素e进队:rear ++; data[rear] = e 。
- 元素e出队:front ++; e = data[front] 。
5. 链队列
- 若用户无法估计所用队列的长度,则应该使用链队列。
- 链队列运算指针变化状况:
- (1) 空队列:
四、单调栈
- 单调栈是栈的一种特例,在一般情况下使用较少。
- 单调栈分为单调递增栈和单调递减栈。
- 单调递增栈是栈中元素从栈底到栈顶是递增的。
- 单调递减栈是栈中元素从栈底到栈顶是递减的。
- 应用:求解下一个大于 x 元素或者是小于 x 元素的位置。
- 给一个数组,返回一个大小相同的数组,返回的数组的第 i 个位置的值应当是,对于原数组中的第 i 个元素,至少往右走多少步,才能遇到一个比自己大的元素(如果之后没有比自己大的元素,或者已经是最后一个元素,则在返回数组的对应位置放上 -1 )。
1. 伪代码
stack<int> st;
for (遍历这个数组){
if (栈空 || 栈顶元素大于等于当前比较元素){
入栈;
}
else{
while (栈不为空 && 栈顶元素小于当前元素){
栈顶元素出栈;
更新结果;
}
当前数据入栈;
}
}
2. 单调栈的应用
- 左边区间第一个比它小的数,第一个比它大的数。
- 确定这个元素是否是区间最值。
- 右边区间第一个大于它的值。
- 到 右边区间第一个大于它的值的距离。
- 确定以该元素为最值的最长区间。
五、单调队列
- 单调队列是队列的一种特例,在一般情况下使用较少。
- 单调队列中的元素始终保持着单增或者单减的特性。
- 单调队列不只是做到了有序,还可以在每次加入或者删除元素时都保持序列里的元素有序,即队首元素始终是最小值或者最大值。
- 单调队列分为单调递增队列和单调递减队列两种。
- 单调递增队列:保证队列头元素一定是当前队列的最小值,用于维护区间的最小值。
- 单调递减队列:保证队列头元素一定是当前队列的最大值,用于维护区间的最大值。
- 实现单调队列,主要分为三个部分:
- 去尾操作 :队尾元素出队列。当队列有新元素待入队,需要从队尾开始,删除影响队列单调性的元素,维护队列的单调性。(删除一个队尾元素后,就重新判断新的队尾元素)。
- 删头操作 :队头元素出队列。判断队头元素是否在待求解的区间之内,如果不在,就将其删除。(这个很好理解呀,因为单调队列的队头元素就是待求解区间的极值)。
- 取解操作 :经过上面两个操作,取出 队列的头元素 ,就是 当前区间的极值 。
- 单调队列与普通队列有些不同,因为右端既可以插入又可以删除,因此在代码中通常用一份数组和 两个指针来实现,而不是使用 STL 中的 queue。如果一定要使用 STL ,那么则可以使用双端队列(即两端都可以插入和删除),即deque 。
1. 单调队列的应用
- 可以查询区间最值(不能维护区间k大,因为队列中很有可能没有k个元素)。
- 优化DP,单调队列一般是用于优化动态规划方面问题的一种特殊数据结构,且多数情况是与定长连续子区间问题相关联。
2. 与单调栈的异同
2.1 相同点
- 单调队列和单调栈的“头部”都是最先添加的元素,“尾部”都是最后添加的元素。
- 递增和递减的判断依据相同:从栈底(队尾)到栈顶(队首),元素大小的变化情况。所以队列和栈是相反的。
- 两者维护的时间复杂度都是 O(n),每个元素都只操作一次。
2.2 不同点
- 单调队列可以从队列头弹出元素,可以方便地根据入队的时间顺序(访问的顺序)删除元素。
- 单调队列和单调栈维护的区间不同。当访问到第i个元素时,单调栈维护的区间为 [0,i),而单调队列维护的区间为 (lastpop,i)
- 单调栈无法获取 [0,i) 的区间最大值/最小值。因为单调队列可以访问“头部”和“尾部”,而单调栈只能访问栈顶(也就是“尾部”)。