相关技术:
- 二分查找
- 有序滑动数组
- 滑动窗口O(1)均值更新方法
- 滑动窗口O(1)方差更新方法
- 二进制快速开平方算法
该算法实现了一个滑动窗口,用于维护一个固定长度的数组。该算法提供了两个步进函数:
step()
:如果输入为空,则删除窗口中最旧的数据并更新统计量。如果输入不为空,则将数据添加到窗口中,并将窗口中最旧的数据弹出。step(in_data)
:将数据添加到窗口中,并将窗口中最旧的数据弹出。该算法的算法逻辑如下:
step()
:
- 如果输入为空并且窗口为空,则返回空。
- 弹出窗口中最旧的数据。
- 如果输入为空,则删除最旧的数据并更新统计量。
- 如果输入不为空,则将数据添加到窗口中,并更新统计量。
step(in_data)
:
- 将数据添加到窗口中。
- 弹出窗口中最旧的数据。
- 更新统计量。
该算法的统计量包括:
max()
:最大值median()
:中位数min()
:最小值mean()
:平均值mean2()
:平方均值var()
:方差std()
:标准差该算法使用了以下辅助函数:
_binary_search()
:二分查找函数,用于查找数组中元素的位置。_insert()
:将元素插入到数组中,并保持数组有序。_delete()
:从数组中删除元素,并保持数组有序。_delete_insert()
:删除一个元素并插入另一个元素,并保持数组有序。该算法的优点是:
- 时间复杂度为 O(1),每个步骤只需要 O(1) 的时间。
- 空间复杂度为 O(n),其中 n 为窗口的大小。
该算法的应用场景包括:
- 实时数据处理
- 数据分析
- 其他需要维护固定大小窗口的场景
signal_window_nosort.h
#ifndef SLIDING_WINDOW_NO_SORT_H
#define SLIDING_WINDOW_NO_SORT_H
float Q_rsqrt(float number); // 快速开平方
template<typename T>
class SlidingWindow {
public:
// 构造函数,初始化窗口
explicit SlidingWindow(int length = 11, bool SORT_INFO = false) {
n = 0; // 初始化长度为0
window = new T[length]; // 初始创建窗口数组
if (SORT_INFO) sorted = new T[length]; // 初始创建排序后的窗口数组
_start = 0; // 初始化开始位置为0
_length = length; // 记录窗口总长度
_mean = 0; // 初始化窗口内的均值为0
_mean2 = 0; // 初始化窗口内的平方均值为0
}
// 析构函数,释放内存
~SlidingWindow() {
delete[] window;
delete[] sorted;
}
// ===========================================
// 步进函数,重载无输入的情况
// @return : 滑动窗口步过的一个数据
T step();
// ===========================================
// 步进函数,重载有输入的情况
// @param in_data: 滑动窗口新输入的数据
// @return : 滑动窗口步过的一个数据
T step(T in_data);
// ===========================================
T max() { return (sorted == 0) ? T(0) : sorted[n - 1]; } // 返回最大值
T median() { return (sorted == 0) ? T(0) : sorted[n / 2]; } // 返回中位数
T min() { return (sorted == 0) ? T(0) : sorted[0]; } // 返回最小值
T first() { return window[_start]; } // 返回窗口的第一个位置值
T middle() { return window[(_start + n / 2) % _length]; } // 返回窗口的中间位置值
T last() { return window[(_start + n) % _length]; } // 返回窗口的最后一个位置值
T mean() { return _mean; } // 返回均值
T mean2() { return _mean2; } // 返回平方均值
T var() { return n == 0 ? 0 : (_mean2 - _mean * _mean) / n; } // 返回方差
T std() { return n == 0 ? 0 : Q_rsqrt(var()); } // 返回标准差
T len() { return n; } // 返回窗口中现有数据的长度
T sort(int i) { return (sorted == 0) ? T(0) : sorted[i]; } // 返回窗口中现有数据的长度
T win(int i) { return window[(i + _start) % n]; } // 返回窗口中现有数据的长度
private:
T *sorted = 0; // 存储窗口数据的排序副本数组
T *window; // 存储窗口数据的数组
int n; // 窗口中有效数据的数量
int _start; // 窗口的开始指针
int _length{}; // 窗口的总长度
T _mean; // 窗口数据的均值
T _mean2; // 窗口数据的平方的均值
// ==============================================================================
// 二分查找算法,查找 target 在 array 中的索引。
// 如果不存在,返回最近的元素的索引。
// @param array: 待查找数组,升序排列
// @param target: 待查找元素
// @return: 元素索引
int _binary_search(T *array, int length, T target);
// ==============================================================================
// 该函数用于插入一个元素,并保持数组有序。
// @param data_array: 待操作的数组,输入和输出都默认从小到大排序
// @param insert_data: 要插入的元素
// @param _length: 代操作数组的长度
void _insert(T *data_array, T insert_data, int length);
// ==============================================================================
// 该函数用于删除一个元素,并保持数组有序。
// @param data_array: 待操作的数组,输入和输出都默认从小到大排序
// @param delete_data: 要删除的元素
// @param _length: 待操作的数组的长度
void _delete(T *data_array, T delete_data, int length);
// ==============================================================================
// 该函数用于删除一个元素并插入另一个元素,并保持数组有序。
// @param data_array: 待操作的数组,输入和输出都默认从小到大排序
// @param _length: 待操作的数组的长度
// @param delete_data: 要删除的元素
// @param insert_data: 要插入的元素
void _delete_insert(T *array, int length, T delete_data, T insert_data);
};
// 显式实例化,指定可以使用的模板数据类型,有哪些
template
class SlidingWindow<float>;
#endif //SLIDING_WINDOW_NO_SORT_H
signal_window_nosort.cpp
#include "sliding_window_no_sort.h"
float Q_rsqrt( float number )
{
long i;
float x2, y;
const float threehalfs = 1.5F;
x2 = number * 0.5F;
y = number;
i = * ( long * ) &y; // evil floating point bit level hacking
i = 0x5f3759df - ( i >> 1 ); // what the fuck?
y = * ( float * ) &i;
y = y * ( threehalfs - ( x2 * y * y ) ); // 1st iteration
y = y * ( threehalfs - ( x2 * y * y ) ); // 2nd iteration, this can be removed
y = y * ( threehalfs - ( x2 * y * y ) ); // 3nd iteration, this can be removed
return y*number;
}
template<typename T>
T SlidingWindow<T>::step(T in_data) {
T out_data = window[_start]; // 弹出窗口中最旧的数据
window[(_start + n) % _length] = in_data; // 将数据添加到窗口中
if (n == _length) {
_start = (_start + 1) % _length;
_delete_insert(sorted, n, out_data, in_data);
_mean += (in_data - out_data) / n;
_mean2 += (in_data * in_data - out_data * out_data) / n;
} else {
n++;
_insert(sorted, in_data, n);
_mean = (in_data + _mean * (n - 1)) / n;
_mean2 = (in_data * in_data + _mean2 * (n - 1)) / n;
}
return out_data;
}
template<typename T>
T SlidingWindow<T>::step() {
// 如果输入为空并且窗口为空,则返回空
if (n == 0)
return 0;
// 弹出窗口中最旧的数据
T out_data = window[_start];
// 如果输入为空,则删除最旧的数据并更新统计量
n--; // 总长度-1
_start = (_start + 1) % _length; // 开始位置 + 1
_delete(sorted, out_data, n); // 从有序列表中删除一个数
if (n == 0) {
_mean = 0;
_mean2 = 0;
} else {
_mean = (_mean*(n+1) - out_data) / n;
_mean2 = (_mean2*(n+1) - out_data*out_data) / n;
}
return out_data;
}
template<typename T>
int SlidingWindow<T>::_binary_search(T *array, int length, T target) {
int low = 0, high = length - 1;
int mid;
while (low <= high) {
mid = (low + high) / 2;
if (array[mid] == target)
return mid;
else if (array[mid] < target)
low = mid + 1;
else
high = mid - 1;
}
// 没有找到目标元素,返回最近的元素
if (mid > 0 && target - array[mid - 1] < array[mid] - target)
return mid - 1;
else
return mid;
}
template<typename T>
void SlidingWindow<T>::_delete_insert(T *array, int length, T delete_data, T insert_data) {
if (array == 0)
return;
// 如果要删除的元素和要插入的元素相同,则直接返回
if (delete_data == insert_data)
return;
// 使用二分查找找到要删除元素的位置
int index = _binary_search(array, length, delete_data);
// 由于二分查找的特性,如果没有找到精确匹配,它会返回最接近的较小索引
// 因此,我们需要检查该索引处的元素是否确实是要删除的元素
if (index < length && array[index] != delete_data) return;
int i = index;
// 如果要删除的元素小于要插入的元素,则从右向左移动元素
if (delete_data < insert_data)
while (i < length - 1 && array[i + 1] < insert_data) {
array[i] = array[i + 1];
i += 1;
}
// 否则,从左向右移动元素
else
while (i > 0 && array[i - 1] > insert_data) {
array[i] = array[i - 1];
i -= 1;
}
// 将要插入的元素插入到找到的位置
array[i] = insert_data;
}
template<typename T>
void SlidingWindow<T>::_insert(T *array, T insert_data, int length) {
if (array == 0)
return;
int i = length - 1;
while (i > 0 && array[i - 1] > insert_data) {
array[i] = array[i - 1];
i -= 1;
}
// 将要插入的元素插入到找到的位置
array[i] = insert_data;
}
template<typename T>
void SlidingWindow<T>::_delete(T *array, T delete_data, int length) {
if (array == 0)
return;
// 使用二分查找找到要删除元素的位置
int index = _binary_search(array, length, delete_data);
// 由于二分查找的特性,如果没有找到精确匹配,它会返回最接近的较小索引
// 因此,我们需要检查该索引处的元素是否确实是要删除的元素
if (index < length && array[index] != delete_data)
return;
for (int i = index; i < length - 1; i++) // 如果要从右向左移动元素, 删除index位置的元素
array[i] = array[i + 1];
}