滑动窗口工具(无依赖文件),高效实现,O(1)自动统计均值、方差、标准差、中位数、最大值、最小值、维持滑动有序数组,(纯干货)(相关技术+C++源代码)

相关技术:

  1. 二分查找
  2. 有序滑动数组
  3. 滑动窗口O(1)均值更新方法
  4. 滑动窗口O(1)方差更新方法
  5. 二进制快速开平方算法

该算法实现了一个滑动窗口,用于维护一个固定长度的数组。该算法提供了两个步进函数:

  • 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];
}
  • 12
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值