目录
为什么要写这篇
仍记得当初写了一篇去除极值的均值滤波算法相关的博客,该算法用在了ADC采样上面。当初偶然看见还有一种递推均值滤波算法,用在了实时波形输出上面。没有代码实现,本着早晚会用到以及要使用抽象数据类型队列的想法,开始了这篇博客的筹划。
递推均值滤波算法实现方式不见得只有一种方式。环形队列 或 链式队列,先实现自认为比较难一点儿的,之后再有时间则实现相对容易点的。
后期补充:今天我看到了最简单可行的递推均值滤波算法,简单到一目了然,比链式队列更常用。
为什么要用队列实现
主要因为递推这两个字吧,前面的要出去,后面的要进来,然后就自然而然想到了队列的出队和入队。
程序是怎么实现的
- 假设已经有了队列的数据类型以及常用的操作,可以直接调用。
- 先初始化队列,不知道队列初始化是为什么,总知道指针初始化是为什么吧,队列的实现需要指针。
- 然后判断队列是否为满,队列有设置项数最大值,均值滤波的实现需要几个数做均值就设置项数最大值为多少,若没有最大值限制,既极大占用空间,也不适合队列遍历。
- 产生随机数存放到队列中。
- 如果队列为满了,这才是需要的,先从队列中删除一项,然后再将随机数存放到队列中。
- 调试需要:查看产生的随机数。
- 在一个合适的时间,遍历队列,获取平均值。
- 调试需要:遍历队列,逐项输出。
- 程序结束前清空队列。
程序实现结果
程序代码
test_q.c 文件
#include <stdio.h> /* 调用printf 函数 */
#include <time.h> /* 调用time 函数 */
#include <stdlib.h> /* 调用srand rand 函数 */
#include "queue.h" /* 调用队列的数据类型以及操作函数 */
#define DEBUG_OUTPUT 1
int main(void)
{
Queue line;
Item temp;
srand((unsigned)time(NULL));
InitializeQueue(&line);
for(uint8_t i=0; i<15; i++)
{
if(!QueueIsFull(&line))
{
temp = rand() % 256;
#if DEBUG_OUTPUT
printf("random_val is %d \n", temp);
#endif
EnQueue(temp, &line);
}else
{
DeQueue(&temp, &line);
if(!QueueIsFull(&line))
{
temp = rand() % 256;
#if DEBUG_OUTPUT
printf("random_val is %d \n", temp);
#endif
EnQueue(temp, &line);
}
}
}
if(QueueIsFull(&line))
{
Q_DATA_type sum = GetQueueSum(&line);
printf("\n Queue sum is %d,average value is %d \n\n",sum, sum/MAXQUEUE);
}else
{
printf(" \n The data is not full, the mean filtering cannot be performed !\n\n");
}
#if DEBUG_OUTPUT
ShowQueueNode(&line);
#endif
EmptyTheQueue(&line);
return 0;
}
queue.h 文件
#ifndef __QUEUE_H__
#define __QUEUE_H__
#include <stdbool.h> /* 定义bool 类型 */
#include <stdio.h> /* 定义输入输出 */
#include <stdint.h> /* 定义常用数据类型 */
#include <stdlib.h> /* 定义 malloc free exit */
#define MAXQUEUE 10
typedef int Item;
typedef uint32_t Q_DATA_type;
typedef struct node
{
Item item;
struct node* next;
}Node;
typedef struct queue
{
Node* front; /* 指向队列首项的指针 */
Node* rear; /* 指向队列尾项的指针 */
int items; /* 队列中的项数 */
}Queue;
/*
* 使用链表来实现队列的好处是删除首项时不必移动其余元素
* 只需重置头指针指向新的首元素即可
*/
/**
* 初始化队列
* 前提条件:pq指向一个队列
* 后置条件:队列被初始化为空
*/
void InitializeQueue(Queue* pq);
/**
* 检查队列是否已满
* 前提条件:pq指向被初始化的队列
* 后置条件:如果队列已满则返回true, 否则返回false
*/
bool QueueIsFull(const Queue* pq);
/**
* 检查队列是否为空
* 前提条件:pq指向被初始化的队列
* 后置条件:如果队列为空则返回true, 否则返回false
*/
bool QueueIsEmpty(const Queue* pq);
/**
* 确定队列中的项数
* 前提条件:pq指向被初始化的队列
* 后置条件:返回队列中的项数
*/
int QueueItemCount(const Queue* pq);
/**
* 在队列末尾添加项
* item 是要被添加在队列末尾的项
* 前提条件:pq指向被初始化的队列
* 后置条件:如果队列不为满,item将被添加在队列末尾,返回true
* 后置条件:否则,队列不改变,该函数返回false
*/
bool EnQueue(Item item, Queue* pq);
/**
* 在队列的开头删除项
* 前提条件:pq指向被初始化的队列
* 后置条件:如果队列不为空,队列首端的item将被拷贝到*pitem中,并被删除,函数返回true
* 后置条件:如果该操作使得队列为空,则重置队列为空
* 后置条件:如果队列在操作前为空,则函数返回false
*/
bool DeQueue(Item* pitem, Queue* pq);
/**
* 清空队列
* 前提条件:pq指向被初始化的队列
* 后置条件:队列被清空
*/
void EmptyTheQueue(Queue* pq);
/**
* 显示队列数据
* 前提条件:pq指向被初始化的队列
* 后置条件:printf输出队列中所有项的数据
*/
void ShowQueueNode(const Queue* pq);
/**
* 得到队列数据和
* 前提条件:pq指向被初始化的队列
* 后置条件:返回队列中所有项的数据和
*/
Q_DATA_type GetQueueSum(const Queue* pq);
#endif
queue.c 文件
/**
* @file queue.c
* @brief
* @details 类型属性:可以存储一系列项
* @details 类型操作:初始化队列为空
* @details 类型操作:查询队列是否为空
* @details 类型操作:查询队列是否为满
* @details 类型操作:确定队列中的项数
* @details 类型操作:在队列末尾添加项
* @details 类型操作:在队列开头删除项
* @details 类型操作:清空队列
* @details 类型操作:遍历链表,获取队列数据和
* @details 类型操作:遍历链表,显示队列数据
*
*
* @author Lv_*_Guang
* @version 1.0.0
* @date 2021-01-05
*
* @copyright Copyright (c) 2021
*
* @par 修改日志:
* <table>
* <tr><th>Date <th>Version <th>Author <th>Description
* <tr><td>2021-01-05 <td>1.0.0 <td>Lv_*_Guang <td>修改内容描述
* </table>
*/
#include "queue.h"
static void CopyToNode(Item item, Node* pn);
static void CopyToItem(Node* pn, Item* pi);
static void Traverse(const Queue* pq, void(*pfun)(Item item));
static void ShowNodeItem(Item item);
static void QueueValueSum(Item item);
static Q_DATA_type GetQueueValueSum(void);
static void ClearQueueValueSum(void);
/**
* InitializeQueue
* @brief 初始化队列为空
* @param pq 参数描述: 队列首指针
*/
void
InitializeQueue(Queue* pq)
{
pq->front = pq->rear = NULL;
pq->items = 0;
}
/**
* QueueIsFull
* @brief 队列是否为满
* @param pq 参数描述: 队列首指针
* @retval true 返回值描述: 队列为满
* @retval false 返回值描述: 队列非满
*/
bool
QueueIsFull(const Queue* pq)
{
return pq->items == MAXQUEUE;
}
/**
* QueueIsEmpty
* @brief 队列是否为空
* @param pq 参数描述: 队列首指针
* @retval true 返回值描述: 队列为空
* @retval false 返回值描述: 队列非空
*/
bool
QueueIsEmpty(const Queue* pq)
{
return pq->items == 0;
}
/**
* QueueItemCount
* @brief 查询队列中项的数目
* @param pq 参数描述: 队列首指针
* @retval int 返回值描述: 队列在项的数目
*/
int
QueueItemCount(const Queue* pq)
{
return pq->items;
}
/**
* EnQueue
* @brief 从队列尾添加一项
* @param item 参数描述: 结构体
* @param pq 参数描述: 队列首指针
* @retval true 返回值描述: 添加成功
* @retval false 返回值描述: 添加失败
*
* @note 把项添加到队列中,步骤:
* @note 创建一个新节点
* @note 把项拷贝到节点中
* @note 设置节点的next指针为NULL,表明该节点是最后一个节点
* @note 设置当前尾节点的next指针指向新节点,把新节点链接到队列中
* @note 把rear指针指向新节点,以便找到最后的节点
* @note 项数加1
*/
bool
EnQueue(Item item, Queue* pq)
{
Node* pnew;
if(QueueIsFull(pq))
{
return false;
}
pnew = (Node*)malloc(sizeof(Node));
if(pnew == NULL)
{
printf("Unable to allocate memory! \n");
exit(1);
}
CopyToNode(item, pnew);
pnew->next = NULL;
if(QueueIsEmpty(pq))
{
pq->front = pnew;
}else
{
pq->rear->next = pnew; /* 链接到队列尾端 */
}
pq->rear = pnew; /* 记录队列尾端的位置*/
pq->items++;
return true;
}
/**
* DeQueue
* @brief 从队列头删除一项
* @param pitem 参数描述: 结构体指针
* @param pq 参数描述: 队列首指针
* @retval true 返回值描述: 删除成功
* @retval false 返回值描述: 删除失败
*
* @note 把项从队列首端删除,涉及以下几个步骤:
* @note 把项拷贝到给定的变量中
* @note 释放空出的节点使用的内存空间
* @note 重置首指针指向队列中的下一个项
* @note 如果删除最后一项,把首指针和尾指针都重置为NULL
* @note 项数减1
*/
bool
DeQueue(Item* pitem, Queue* pq)
{
Node* pt;
if(QueueIsEmpty(pq))
{
return false;
}
CopyToItem(pq->front, pitem);
pt = pq->front;
pq->front = pq->front->next;
free(pt);
pq->items--;
if(pq->items == 0)
{
pq->rear = NULL;
}
return true;
}
/**
* EmptyTheQueue
* @brief 清空队列
* @param pq 参数描述: 队列首指针
*/
void
EmptyTheQueue(Queue* pq)
{
Item dummy;
while(!QueueIsEmpty(pq))
{
DeQueue(&dummy, pq);
}
}
/**
* CopyToNode
* @brief 从结构体赋值到链表节点
* @param item 参数描述: 结构体指针
* @param pn 参数描述: 链表节点指针
*/
static void
CopyToNode(Item item, Node* pn)
{
pn->item = item;
}
/**
* CopyToItem
* @brief 从链表节点复制到结构体中
* @param pn 参数描述: 链表节点指针
* @param pi 参数描述: 结构体指针
*/
static void
CopyToItem(Node* pn, Item* pi)
{
*pi = pn->item;
}
static uint8_t term_val = 0;
/**
* ShowNodeItem
* @brief 显示队列节点数据
* @param item 节点数据
*/
static void
ShowNodeItem(Item item)
{
printf("Queue node %d item is %d \n", term_val, item);
}
/**
* Traverse
* @brief 遍历链表,执行某种操作
* @param pq 队列指针
* @param pfun 函数指针
* @note 目前可调用函数有:queue_valeue_sum 和 show_node_item
*/
static void
Traverse(const Queue* pq, void(*pfun)(Item item))
{
Node *pnode = pq->front;
while(pnode != NULL)
{
(*pfun)(pnode->item);
term_val++;
pnode = pnode->next;
}
term_val = 0;
}
/* 后续添加,对队列数值求和 */
static Q_DATA_type q_val_sum = 0;
/**
* QueueValueSum
* @brief 队列各节点数据求和
* @param item 节点数据
*/
static void
QueueValueSum(Item item)
{
q_val_sum += item;
}
/**
* GetQueueValueSum
* @brief 获取队列数据和
* @return 队列数据和
*/
static Q_DATA_type
GetQueueValueSum(void)
{
return q_val_sum;
}
/**
* ClearQueueValueSum
* @brief 清除队列和
*/
static void
ClearQueueValueSum(void)
{
q_val_sum = 0;
}
/**
* GetQueueSum
* @brief 获取队列数据和
* @param pq 队列指针
* @return 队列数据和
*/
Q_DATA_type
GetQueueSum(const Queue* pq)
{
Q_DATA_type sum;
Traverse(pq, QueueValueSum);
sum = GetQueueValueSum();
ClearQueueValueSum();
return sum;
}
/**
* ShowQueueNode
* @brief 显示队列数据
* @param pq 队列指针
*/
void
ShowQueueNode(const Queue* pq)
{
Traverse(pq, ShowNodeItem);
}