• 滑动窗口算法(一)


    某日事不多,点开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

  • 相关阅读:
    礼物(枚举,二分)
    空调(差分)
    Prefix Equality(哈希)
    py导入时段报表+清理旧文件
    php 0504
    php 0511
    php 0512
    php 0506
    php 0505
    php 0510
  • 原文地址:https://www.cnblogs.com/cdfive2018/p/10054563.html
Copyright © 2020-2023  润新知