算法基礎 - RMQ-ST算法(在線算法)

RMQ問題

RMQ(Range Minimum/Maximum Query),即區間最值查詢,是指這樣一個問題:對於一個長度N的數組,在多次詢問中,每次都以O(1)的時間得到區間[a, b]的最大值或最小值。

在線算法

說來慚愧,到現在纔剛開始清楚說的在線算法和離線算法是什麼意思,所謂在線算法就是說,每次請求及時處理,處理完之後,直接返回,然後等待處理下一次請求。

所以一般在線算法有個預處理過程,預處理數據之後,能夠更快速的處理每次請求的結果,但是會有一個相對長一點的預處理過程。

本文說的ST算法預處理的時間是(NlogN)。

離線算法

所謂離線算法只是在來了非常多的請求之後,一次性處理多個請求,能夠不依賴於預處理數據,卻能一次完成多個請求的結果。

ST ( Sparse Table ) 算法

ST(Sparse Table)算法是一個非常有名的在線處理RMQ問題的算法,它可以在O(nlogn)時間內進行預處理,然後在O(1)時間內回答每個查詢。其思想就是保存以i爲起點的某段數據的最小值。

預處理數據

預處理,用動態規劃(DP)解決。思想接近於二路歸併排序過程的分治思想。

  1. 既然是DP思想,首先要記錄每步的狀態。
A[i]是要求區間最值的數列,F[i, j]表示從第i個數起連續2^j個數中的最大值。 這裏的F[i, j]就是每步的狀態。

例如:

A數列爲:3 2 4 5 6 8 1 2 9 7

F[1,0]表示第1個數起,長度爲2^0=1的最大值,其實就是3這個數。
同理 : F[1,1] = max(3,2) = 3, F[1,2]=max(3,2,4,5) = 5,F[1,3] = max(3,2,4,5,6,8,1,2) = 8;

  1. 狀態轉移方程
    既然有每步的狀態了,開始找狀態轉移方程。
F[i, j] = max(F[i, j-1], F[i+2^(j-1)][j-1]);

怎麼理解呢?

最初始狀態:F[0, 0] = A[0]; F[1,0] = A[1]; F[2,0] = A[2]; 意思是說每單個元素的最大值都是自己(因爲只有一個元素)

下一個狀態:F[0, 1] = max(F[0,0], F[1,0]) = max(A[0], A[1]);意思是說把F[0, 1] 分成兩段,例如要求得F[3, 2] = max(A[3] , A[4], A[5], A[6]) 其實就是求四個元素的前兩個以及後兩個的最大值。在求F[3,2]的時候已經知道了F[3, 1]和F[5,1]了。

代碼如下:

void RMQ(int num){
    for (int j = 1; j < 31; j++) {
        for (int i = 0 ; i <= num; i++) {
            if (i + (1<<j) -1 <= num) {
                maxNum[i][j] = max(maxNum[i][j-1], maxNum[i+(1<<(j-1))][j-1]);
                minNum[i][j] = min(minNum[i][j-1], minNum[i+(1<<(j-1))][j-1]);
            }
        }
    }
}

查詢區間

那麼查詢怎麼做呢?畢竟我們存放的都是2次冪個數的最小值。假如查詢的區間是奇數個,或不是2次冪個數怎麼弄?

其實很簡單,就是把頭尾分開!

假如我們需要查詢的區間爲(i,j),那麼我們需要找到小於這個閉區間(左邊界取i,右邊界取j)的最大冪(可以重複,比如查詢5,6,7,8,9,我們可以查詢5678和6789)

  1. 區間的長度爲j - i + 1
  2. 可以取k=log2( j - i + 1) 區間是1->3 就取 log2( 3 - 1 + 1) = 1小於等於區間的最大冪
  3. RMQ(A, i, j)=max{F[i , k], F[ j - 2 ^ k + 1, k]}

F[i, k] 是右邊界起點向左找,F[ j - 2 ^ k + 1, k]是左邊界作爲終點,向右找起點。 一定要用小於等於區間的最大冪是因爲要能夠兩個F加在一起能夠覆蓋整個區間。

完成代碼如下

//
//  main.cpp
//  RMQ_ST
//
//  Created by Alps on 16/5/17.
//  Copyright © 2016年 chen. All rights reserved.
//

#include <iostream>
#include <cstring>
#include <string>
#include <vector>
#include <cmath>
#include <algorithm>

#ifndef MAXARR
#define MAXARR 100 //默認數組最多元素
#endif

using namespace std;

int maxNum[MAXARR][32];
int minNum[MAXARR][32];

void RMQ(int num){
    for (int j = 1; j < 31; j++) {
        for (int i = 0 ; i < num; i++) {
            if (i + (1<<j) -1 < num) {
                maxNum[i][j] = max(maxNum[i][j-1], maxNum[i+(1<<(j-1))][j-1]);
                minNum[i][j] = min(minNum[i][j-1], minNum[i+(1<<(j-1))][j-1]);
            }
        }
    }
}

int main(int argc, const char * argv[]) {
    int M,N;
    scanf("%d",&M);
    for (int i = 0; i < M; i++) {
        scanf("%d",&maxNum[i][0]);
        minNum[i][0] = maxNum[i][0]; //初始化狀態
    }
    RMQ(M);
    scanf("%d",&N); //查詢次數
    int start = 0, end = 0;
    for (int i = 0; i < N; i++) {
        scanf("%d%d",&start,&end); //查詢的區間
        int k  = log2(end-start+1);
        cout<<max(maxNum[start][k], maxNum[end-(int)(1<<k)+1][k])<<endl; //最大值
        cout<<min(minNum[start][k], minNum[end-(int)(1<<k)+1][k])<<endl; //最小值
    }
    return 0;
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章