滑动窗口算法(一)

某日事不多,点开sentinel-core代码学习,想看看qps、rt等是怎么统计的。

点开StatisticSlot类,发现里面是用DefaultNode增加qps,然后尝试点开

DefaultNode->StatisticNode->ArrayMetric->MetricsLeapArray->LeapArray...

晕...怎么这么多类- -||| 中间还有MetricBucket、LongAdder...

其中LeapArray类有个属性AtomicReferenceArray<WindowWrap<T>> array,表示这个jdk类也是第一次见orz

看注释写道Using sliding window algorithm,使用了滑动窗口算法。可是这代码太深奥T_T,表示打扰了,还是洗洗睡了Zzz....

--------------------------------------------------------------------------------------------------------------------------------------------

言归正传,虽然sentinel中的代码还看不懂,通过滑动窗口几个字,百度了解一下:)

找到一个经典的问题:

(一)给定一组大小为n的整数数组,计算长度为k的子数组和的最大值。

比如

数组为:1,2,3,4

最大值为:3+4=7

数组为:-1,4,7,-3,8,5,-2,6

最大值为:7-3+8=12

想到最简单思路,那就遍历所有子数组呗,求和然后比较。

        int index = 0;// 记录最大子数组第1个元素的索引,目前是0
        int maxSum = 0;// 记录最大子数组和,目前是从左开始第1个子数组
        for (int i = 0; i < k; i++) {
            maxSum += array[i];
        }

        for (int i = 1; i <= array.length - k; i++) {// 遍历所有子数组,求和并比较
            int curSum = 0;
            for (int j=0; j < k; j++) {// 计算当前子数组和
                curSum += array[i + j];
            }
            if (curSum > maxSum) {// 如果大于最大和,则记录
                maxSum = curSum;
                index = i;
            }
        }

运用滑动窗口思路,遍历时不嵌套循环计算所有值;外层遍历相当于窗口向右滑动,每次减去失效值加上最新值,即为当前窗口的和,然后再比较。

        int index = 0;// 记录最大子数组第1个元素的索引,目前是0
        int maxSum = 0;// 记录最大子数组和,目前是从左开始第1个子数组
        for (int i = 0; i < k; i++) {
            maxSum += array[i];
        }

        int curWindowSum = maxSum;
        for (int i = 1; i <= array.length - k; i++) {// 从下个元素开始,即窗口向右滑动
            curWindowSum = curWindowSum - array[i - 1] + array[k + i - 1];// 减去失效值,加上最新值
            if (curWindowSum > maxSum) {// 如果大于最大和,则记录
                maxSum = curWindowSum;
                index = i;
            }
        }

可以看到代码差不多,只不过在计算求和时,采取了滑动窗口技术(思路),通过一减一加求和,消除了内部的循环。
注:这里为了突出语义,将变量名curSum改为curWindowSum

完整代码如下:

package com.cdfive.learn2018.algorithm;

/**
 * 求数组array长度为k的子数组的最大和
 *
 * 算法1-cal
 * 遍历所有子数组,求和并比较
 * 嵌套循环 O(n*k)
 *
 * 算法2-calByLeapWinow
 * 窗口向右滑动,减去失效值加上最新值
 * 单层循环 O(n)
 *
 * input:  array=[1,2,3,4] k=2
 * output: 7 // 3+4
 *
 * input:  array=[-1,4,7,-3,8,5,-2,6] k=3
 * output: 12 // 7-3+8
 *
 * @author cdfive
 * @date 2018-12-02
 */
public class SimpleLeapWindow1 {

    public static void main(String[] args) {
        cal(new int[]{1, 2, 3, 4}, 2);
        cal(new int[]{-1,4,7,-3,8,5,-2,6}, 3);

        System.out.println("-------------");

        calByLeapWinow(new int[]{1, 2, 3, 4}, 2);
        calByLeapWinow(new int[]{-1,4,7,-3,8,5,-2,6}, 3);
    }

    /**
     * 遍历所有子数组,求和并比较
     * 嵌套循环 O(n*k)
     */
    public static void cal(int[] array, int k) {
        if (array.length == 0 || k <= 0 || k > array.length) {// 非法参数不处理
            return;
        }

        int index = 0;// 记录最大子数组第1个元素的索引,目前是0
        int maxSum = 0;// 记录最大子数组和,目前是从左开始第1个子数组
        for (int i = 0; i < k; i++) {
            maxSum += array[i];
        }

        for (int i = 1; i <= array.length - k; i++) {// 遍历所有子数组,求和并比较
            int curSum = 0;
            for (int j=0; j < k; j++) {// 计算当前子数组和
                curSum += array[i + j];
            }
            if (curSum > maxSum) {// 如果大于最大和,则记录
                maxSum = curSum;
                index = i;
            }
        }

        /**打印结果*/
        System.out.print(maxSum + " // ");// 打印最大和
        System.out.print(array[index]);// 先打印第1个值
        for (int i = 1; i < k; i++) {
            int value = array[i + index];
            System.out.print(value >= 0 ? ("+" + value) : value);// 非负数前面打印+号
        }
        System.out.println();
    }

    /**
     * 窗口向右滑动,通过减失效值加最新值求和并比较
     * 单层循环 O(n)
     */
    public static void calByLeapWinow(int[] array, int k) {
        if (array.length == 0 || k <= 0 || k > array.length) {// 非法参数不处理
            return;
        }

        int index = 0;// 记录最大子数组第1个元素的索引,目前是0
        int maxSum = 0;// 记录最大子数组和,目前是从左开始第1个子数组
        for (int i = 0; i < k; i++) {
            maxSum += array[i];
        }

        int curWindowSum = maxSum;
        for (int i = 1; i <= array.length - k; i++) {// 从下个元素开始,即窗口向右滑动
            curWindowSum = curWindowSum - array[i - 1] + array[k + i - 1];// 减去失效值,加上最新值
            if (curWindowSum > maxSum) {// 如果大于最大和,则记录
                maxSum = curWindowSum;
                index = i;
            }
        }

        /**打印结果*/
        System.out.print(maxSum + " // ");// 打印最大和
        System.out.print(array[index]);// 先打印第1个值
        for (int i = 1; i < k; i++) {
            int value = array[i + index];
            System.out.print(value >= 0 ? ("+" + value) : value);// 非负数前面打印+号
        }
        System.out.println();
    }
}

--------------------------------------------------------------------------------------------------------------------------------------------

参考:

滑动窗口法详解 https://blog.csdn.net/sty945/article/details/79846516

Window Sliding Technique https://www.geeksforgeeks.org/window-sliding-technique/

[LeetCode]Sliding Window Algorithm相关题目总结 https://blog.csdn.net/Ellie_/article/details/76781877

原文地址:https://www.cnblogs.com/cdfive2018/p/10054563.html