用循环队列模拟滑动窗口动态求最值及峰峰值

一、待解决问题

对于一个数组,如果用滑动窗口的方式求解数组某一段的最值,在不考虑时间复杂度的情况下,用循环就能轻松实现,而如果考虑时间复杂度,"队列"这种先进先出的数据结构,就十分实用于这种情况。

与静态的滑动窗口相类似,当动态地获取数据时,我们也可以用循环队列来模拟一个滑动的串口。

本次针对的问题是:当传感器每0.1s读取数据时,实时计算得到过去5s的最值之差。

二、方法对比

由于博主比较久没有使用数据结构了,因此在开始时先向计算机专业的同学寻求帮助,得到了“使用最大堆”的回复,但经过对比发现,最大堆作为一种树形结构,虽然很好地模拟了队列的模式,并同时能时刻得到堆中的最大值,在输入新元素和输出最大值时,能以较小的时间复杂度实现堆中元素的快速重新排列,从而得到最大值,但缺点是无法在最大堆中直接得到最小值。

因此,我重新选择了使用“循环队列”的方式,在占用不太多的空间资源下,实现滑动串口的模拟,并动态求最值。

图1 滑动窗口

滑动窗口

三、思路和程序

1.程序设计流程图如下

2.程序代码

#define MaxQSize 8		//7+1,多出的一位元素作为队列满的检验
typedef enum {ERROR = 0, SUCCESS = !ERROR} ErrorStatus;

//初始化循环队列
ErrorStatus Init_SeQueue(SeQueue* Q)
{
	uint8_t i;
	Q->base = (uint16_t*)malloc(MaxQSize*sizeof(uint16_t));
	if(! Q->base) return(0);		//分配空间失败
	
	for(i = 0;i < MaxQSize;i++)
	{
		Q->base[i] = 0;			//将所有元素清0,避免出现其它数值的情况
	}

	Q->front = Q->rear = 0;
	return SUCCESS;
}

//求队列已存元素的个数
uint8_t SeQueue_Length(SeQueue* Q)
{
	return ((MaxQSize + Q->rear - Q->front) % MaxQSize);
}

//往队尾插入元素
ErrorStatus In_SeQueue(SeQueue* Q,uint16_t e)
{
	//若队列未满,则在队尾插入新元素
	//否则,返回ERROR
	if((Q->rear + 1)%MaxQSize == Q->front)	//队列是否已满
		return ERROR;
	Q->base[Q->rear] = e;
	Q->rear = (Q->rear + 1)%MaxQSize;		//实现队列循环,防止队尾序号溢出
	return SUCCESS;
}

//输出队首元素
ErrorStatus Out_SeQueue(SeQueue* Q,uint16_t *e,uint16_t new_dat)
{
	//若队列不空,则输出队首元素
	//否则,返回ERROR
	if(Q->front == Q->rear) return ERROR;

	*e = Q->base[Q->front];					//记录队首元素
	Q->base[Q->front] = new_dat;			//为了方便重新求最值,可直接将队首元素赋即将插入的队尾元素队尾元素
	Q->front = (Q->front + 1)%MaxQSize;		//实现队列循环,防止队头序号溢出
	return SUCCESS;
}

//滑动窗口求峰峰值,在这里就可以引用上面写好的队列基本操作来进行运算啦
void Window_PP(SeQueue *Q,uint16_t rear_dat)
{
	//step1:窗口建立
	//step2:窗口动态移动
	static uint16_t max = 0;				//这里将最大值和最小值的摆幅限制在0~4096之间
	static uint16_t min = 4096;
	uint16_t *front_dat = (uint16_t*)malloc(sizeof(uint16_t));
	uint8_t i;
	
	//队列是否满
	if(SeQueue_Length(Q) == MaxQSize - 1)	//队列满
	{
		Out_SeQueue(Q,front_dat,rear_dat);	//输出队首元素
		

		//是否重新找出队列中的最大值
		if(*front_dat == max)		//输出的队首元素为最大值
		{
			max = 0;
			for(i = 0;i < MaxQSize;i++)
			{
				if(Q->base[i] > max)
					max = Q->base[i];
			}
		}
		//是否重新找出队列中的最小值
		else if(*front_dat == min)
		{
			min = 4096;
			for(i = 0;i < MaxQSize;i++)
			{
				if(Q->base[i] < min)
					min = Q->base[i];
			}
		} 
		In_SeQueue(Q,rear_dat);		//插入新的队尾元素
		if(rear_dat > max)
			max = rear_dat;			//更新极值
		if(rear_dat < min)
			min = rear_dat;

		PP_Value = max - min;		//更新峰峰值
	}
	else
	{
		In_SeQueue(Q,rear_dat);
		if(rear_dat > max)
			max = rear_dat;
		if(rear_dat < min)
			min = rear_dat;
	}
	MAX = max;
	MIN = min;
}

四、总结

队列的特点使其十分适用于滑动窗口的模拟,来一个,走一个,First In First Out

在实现队列的初始化、插入、输出、求长度的基本操作后,就能够利用它们编写一个求极值差(峰峰值)了,具体的队列长度、最大值和最小值的范围可以自己diy。

最后要注意的是,在求最大值和最小值的过程中,当队列输出的首元素是最大值或最小值时,需要重新找到队列中的最大值或最小值,但队列的输出并没有真正的清除掉那个元素A,而只是不将元素纳入队列的运算了,等待新的元素去替换掉A。
在这里插入图片描述

注意:rear所指为队尾元素的下一位。

因此,为了能正确在全队列中重新找到最大值或最小值,可以把即将输入的元素B先替换掉A,这样就能从队列的地址初(数组下标为0)->地址尾(数组下标为 队列长度-1)来找最值,而不需要确定从哪个位置(数组下标a)到哪个元素(数组下标b)找最值,简化寻找最值这个过程。

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值