单调队列---数据结构与算法

简介

队列也是一种受限制的线性表和栈相类似,栈是先进后出,而队列是先进先出,就好像一没有底的桶,往里面放东西,如图

在这里也是用数组来实现队列,用数组实现的叫做顺序队列

队列的数组模拟

复制代码
const int N = 1000010;

//在队尾插入元素 队头弹出元素
int q[N],hh,tt=-1; //hh代表队头 tt代表队尾

//插入
q[++tt] = x ;

//弹出
hh++ ;

//判断队列是否为空 
if(hh <= tt ) not empty
else empty

//取出队头元素
q[hh] ;

单调队列

单调队列也就是说其中的元素始终保持单调性

常见应用:找出滑动窗口中的最大值/最小值

如题:给定一个大小为 n ≤ 10^6 的数组。 有一个大小为 k 的滑动窗口,它从数组的最左边移动到最右边。 你只能在窗口中看到 k 个数字。 每次滑动窗口向右移动一个位置。 以下是一个例子: 该数组为 [1 3 -1 -3 5 3 6 7],k 为 3。

输入格式

输入包含两行。

第一行包含两个整数 n 和 k,分别代表数组长度和滑动窗口的长度。

第二行有 n 个整数,代表数组的具体数值。

同行数据之间用空格隔开。

输出格式

输出包含两个。

第一行输出,从左至右,每个位置滑动窗口中的最小值。

第二行输出,从左至右,每个位置滑动窗口中的最大值。

输入样例

复制代码
8 3
1 3 -1 -3 5 3 6 7

输出样例

复制代码
-1 -3 -3 -3 3 3
3 3 5 5 6 7

例子的输出解释:

窗口的位置 最小值 最大值
【1 3 -1】 -3 5 3 6 7 -1 3
1【 3 -1 -3】 5 3 6 7 -3 3
1 3 【-1 -3 5 】3 6 7 -3 5
1 3 -1【 -3 5 3 】6 7 -3 5
1 3 -1 -3 【5 3 6 】7 3 6
1 3 -1 -3 5【 3 6 7】 3 7

对于解开这题我们依旧尝试暴力做法

既然求滑动窗口中的最大值和最小值,那我们只需要让窗口滑动 n - k + 1 次,再每次都对窗口里的k个数,求出最大/小值就可以了,以下是部分代码

复制代码
//求最小值
for (int i = 0 ; i < n - k + 1 ; i++)
{
	int min = -1;
    
	for (int j = i; j < i + k; j++)
	{
		if (a[i] <= min)
		{
			min = a[i];
		}
	}

	printf("%d ", min);
}
//求最大值
//

这样的做法不仅要遍历数组一遍,在这个过程中窗口还要遍历,相当于遍历了 n * k 遍,非常浪费时间。

优化的方向也是和之前的单调栈类似,有很多元素根本就不可以在后边用到, 例如

我们求最小值时,当 a [ x ] >= a [ y ] 并且 x > y ,这种情况就可以将 a [ x ] 从我们这个队列删除

反之,易得。

这就得到了队列的单调性

对于这个循环中我们需要做3个步骤

  1. 检测队列是否为空 并且 队头是否滑出了窗口 ,这是什么意思呢?窗口的大小固定是 k ,我们要保证队列中的元素全是窗口里的数的下标,队头保存的下标如果是窗口左边的下标,就说明要将队头的元素移出队列(这里只需要判断一次,因为每次循环最多添加一个元素)

  2. 检测队列是否为空 并且队尾所指向的元素是否大于等于此时的元素,如果为真,要将队尾移出队列

3.再将我们此时指向的元素的下标加入队列

4.打印最小值,肯定不是每一次循环都需要打印,你会发现前 k - 1 次不需要打印,变成 i 的话就是i >= k - 1(i从0开始),因为此时窗口都没有满

现在将以上步骤变成代码

复制代码
#define _CRT_SECURE_NO_WARNINGS 1


#include <iostream>

using namespace std;

const int N = 1000010;
int n, k;
int a[N], q[N]; //q 数组是队列
int hh, tt = -1; //hh是队头 tt是队尾

int main(void)
{
	scanf("%d%d", &n, &k);

	for (int i = 0; i < n; i++)
	{
		scanf("%d", &a[i]);
	}

	for (int i = 0; i < n; i++)
	{
		if (hh <= tt && q[hh] < i - k + 1) hh++; // i - k + 1 就是窗口的最左边的下标
		while (hh <= tt && a[q[tt]] >= a[i]) tt--;

		q[++tt] = i;
		if (i >= k - 1 ) printf("%d ", a[q[hh]]);
	}
	puts(""); //打印空字符串,虽然打印为空,但是使用 puts() 显示字符串时,系统会自动在其后添加一个换行符


	hh = 0, tt = -1; //记得要初始化数列
	for (int i = 0; i < n; i++)
	{
		if (hh <= tt && q[hh] < i - k + 1) hh++; 
		while (hh <= tt && a[q[tt]] <= a[i]) tt--;

		q[++tt] = i;
		if (i >= k - 1) printf("%d ", a[q[hh]]);
	}
	puts(""); 

	return 0;
}

运行图

成功得到结果

相关推荐
PPPPPaPeR.3 小时前
光学算法实战:深度解析镜片厚度对前后表面折射/反射的影响(纯Python实现)
开发语言·python·数码相机·算法
看我干嘛!3 小时前
python第五次作业
算法
历程里程碑3 小时前
Linux 库
java·linux·运维·服务器·数据结构·c++·算法
Sheep Shaun3 小时前
如何让一个进程诞生、工作、终止并等待回收?——探索Linux进程控制与Shell的诞生
linux·服务器·数据结构·c++·算法·shell·进程控制
Pluchon3 小时前
硅基计划4.0 简单模拟实现AVL树&红黑树
java·数据结构·算法
生锈的键盘3 小时前
推荐算法实践:交叉特征的理解
算法
小龙报3 小时前
【51单片机】从 0 到 1 玩转 51 蜂鸣器:分清有源无源,轻松驱动它奏响新年旋律
c语言·数据结构·c++·stm32·单片机·嵌入式硬件·51单片机
dllxhcjla3 小时前
数据结构和算法
数据结构
乌萨奇也要立志学C++3 小时前
【洛谷】BFS 求解最短路:从马的遍历到迷宫问题的实战解析
算法·宽度优先
石去皿3 小时前
【嵌入式就业6】计算机组成原理与操作系统核心机制:夯实底层基础
c++·面试·嵌入式