滑動窗口算法(一)


某日事不多,點開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


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM