📜  ≈O(1)时间复杂度的扩展Mo算法

📅  最后修改于: 2021-06-26 12:16:11             🧑  作者: Mango

给定n个元素的数组和q个范围查询(本文中的范围总和)且不进行更新,任务是以有效的时间和空间复杂性来回答这些查询。应用平方根分解后,范围查询的时间复杂度为O(√ n) 。通过对数组的较早分解的块进行平方根分解,可以将平方根因子减小为恒定的线性因子。

先决条件:莫氏算法|前缀数组

方法 :
当我们对给定数组应用平方根分解时,查询范围和需要O(√ n)时间。
在此,计算在考虑中的块(角块)之间的块总和,这需要进行O(√ n)迭代。

初始数组:
初始数组

将数组分解为块:
1级分解

并且起始块和结束块上的总和的计算时间都需要O(√ n)迭代。
这将使我们每个查询的时间复杂度为:

= O(√n) + O(√n) + O(√n)
= 3 * O(√n)   
~O(√n)

在这里,我们可以巧妙地降低查询算法的运行时间复杂度,方法是计算逐块前缀总和,然后使用它来计算在所考虑的块之间的块中累积的总和。考虑下面的代码:

interblock_sum[x1][x2] = prefixData[x2 - 1] - prefixData[x1];

上表计算所需时间为:

= O(√n) * O(√n)  
~O(n)

注意:由于块x1和x2可能携带部分数据,因此我们未考虑这些块的总和。

前缀数组:
前缀数组

假设我们要查询范围从4到11的总和,我们考虑块0和块2之间的总和(不包括块0和块1中包含的数据),可以使用绿色块中的总和来计算如上图所示。

块0和块2之间的总和= 42 – 12 = 30

为了计算黄色块中剩余的总和,请考虑分解级别2的前缀数组,然后再次重复该过程。

在这里,请注意,尽管我们的运行时与上一方法相似,但已大大降低了每个查询的时间复杂度:

我们新的时间复杂度可以计算为:

= O(√n) + O(1) + O(√n)
= 2 * O(√n)   
~O(√n)    

2级的平方根分解:
2级分解

此外,我们对从先前分解保留的每个分解块再次应用平方根分解。现在,在这个级别上,每个块中大约有√ n个子块,这些子块在上一级别进行了分解。因此,我们只需要在这些块上运行一次范围查询两次,一次用于启动块,一次用于结束块。

2级分解所需的预计算时间:
1〜&sqrt; n级别的块数
级别2〜 √  n中的块数

一级分解块的二级分解运行时:

= O(√n)

所有块的2级分解的总体运行时间:

= O(√n) * O(√n)
~O(n)

现在,我们可以在O(√√ n)时间查询我们的2级分解块。
因此,我们将整体时间复杂度从O(√ n)降低O(√√ n)

查询边缘块所花费的时间复杂度:

= O(√√n) + O(1) + O(√√n)
= 2 * O(√√n)
~O(√√n)

总时间复杂度可以计算为:

= O(√√n)+O(1)+O(√√n)
= 2 * O(√√n)   
~O(√√n)  

3级的平方根分解:
3级分解

使用此方法,我们可以一次又一次地递归地分解数组d次,以将时间复杂度降低到恒定的线性度

O(d * n1/(2^d)) ~ O(k), as d increases this factor converges to a constant linear term

下面提供的代码表示三重平方根分解,其中d = 3:

O(q * d * n1/(2 ^ 3)) ~ O(q * k) ~ O(q) 

[其中q代表范围查询数]

// CPP code for offline queries in
// approx constant time.
#include
using namespace std;
  
int n1;
  
// Structure to store decomposed data
typedef struct 
{
    vector data;
    vector> rdata; 
    int blocks; 
    int blk_sz; 
}sqrtD;
  
vector> Sq3;
vector Sq2;
sqrtD Sq1;
  
// Square root Decomposition of 
// a given array
sqrtD decompose(vector arr)
{
    sqrtD sq; 
    int n = arr.size();
    int blk_idx = -1; 
    sq.blk_sz = sqrt(n); 
    sq.data.resize((n/sq.blk_sz) + 1, 0);
      
    // Calculation of data in blocks
    for (int i = 0; i < n; i++)
    {
        if (i % sq.blk_sz == 0)
        {
            blk_idx++;
        }
        sq.data[blk_idx] += arr[i];
    }
      
    int blocks = blk_idx + 1;
    sq.blocks = blocks;
      
    // Calculation of prefix data
    int prefixData[blocks];
    prefixData[0] = sq.data[0];
    for(int i = 1; i < blocks; i++)
    {
        prefixData[i] = 
          prefixData[i - 1] + sq.data[i];
    }
      
    sq.rdata.resize(blocks + 1,
             vector(blocks + 1));
      
    // Calculation of data between blocks
    for(int i = 0 ;i < blocks; i++)
    {
        for(int j = i + 1; j < blocks; j++)
        {
            sq.rdata[i][j] = sq.rdata[j][i] =
            prefixData[j - 1] - prefixData[i];
        }
    } 
      
    return sq;
}
  
// Square root Decomposition at level3
vector> tripleDecompose(sqrtD sq1,
                      sqrtD sq2,vector &arr)
{
    vector> sq(sq1.blocks,
                      vector(sq1.blocks));
      
    int blk_idx1 = -1;
      
    for(int i = 0; i < sq1.blocks; i++)
    {
        int blk_ldx1 = blk_idx1 + 1;
        blk_idx1 = (i + 1) * sq1.blk_sz - 1;
        blk_idx1 = min(blk_idx1,n1 - 1); 
  
        int blk_idx2 = blk_ldx1 - 1;
          
        for(int j = 0; j < sq2.blocks; ++j)
        {
            int blk_ldx2 = blk_idx2 + 1;
            blk_idx2 = blk_ldx1 + (j + 1) * 
                       sq2.blk_sz - 1;
            blk_idx2 = min(blk_idx2, blk_idx1);
          
            vector ::iterator it1 = 
                        arr.begin() + blk_ldx2;
            vector ::iterator it2 = 
                        arr.begin() + blk_idx2 + 1;
            vector vec(it1, it2);
            sq[i][j] = decompose(vec);     
        }
    }
    return sq;         
}
  
// Square root Decomposition at level2
vector doubleDecompose(sqrtD sq1, 
                              vector &arr)
{
    vector sq(sq1.blocks);
    int blk_idx = -1;
    for(int i = 0; i < sq1.blocks; i++)
    {
        int blk_ldx = blk_idx + 1;
        blk_idx = (i + 1) * sq1.blk_sz - 1;
        blk_idx = min(blk_idx, n1 - 1);
        vector ::iterator it1 = 
                    arr.begin() + blk_ldx;
        vector ::iterator it2 = 
                    arr.begin() + blk_idx + 1;
        vector vec(it1, it2);
        sq[i] = decompose(vec);
    }
      
    return sq;     
}
  
// Square root Decomposition at level1
void singleDecompose(vector &arr)
{
    sqrtD sq1 = decompose(arr);
    vector sq2(sq1.blocks); 
    sq2 = doubleDecompose(sq1, arr);
      
    vector> sq3(sq1.blocks,
           vector(sq2[0].blocks));
             
    sq3 = tripleDecompose(sq1, sq2[0],arr);
          
    // ASSIGNMENT TO GLOBAL VARIABLES
    Sq1 = sq1;
    Sq2.resize(sq1.blocks);
    Sq2 = sq2;
    Sq3.resize(sq1.blocks, 
        vector(sq2[0].blocks));
    Sq3 = sq3;
}
  
// Function for query at level 3
int queryLevel3(int start,int end, int main_blk,
                int sub_main_blk, vector &arr)
{
    int blk_sz= Sq3[0][0].blk_sz;
  
    // Element Indexing at level2 decomposition
    int nstart = start - main_blk * 
        Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz;
    int nend = end - main_blk * 
        Sq1.blk_sz - sub_main_blk * Sq2[0].blk_sz;
  
    // Block indexing at level3 decomposition 
    int st_blk = nstart / blk_sz;
    int en_blk = nend / blk_sz;
      
    int answer = 
        Sq3[main_blk][sub_main_blk].rdata[st_blk][en_blk]; 
      
    // If start and end point don't lie in same block 
    if(st_blk != en_blk)
    { 
        int left = 0, en_idx = main_blk * Sq1.blk_sz +
                      sub_main_blk * Sq2[0].blk_sz + 
                      (st_blk + 1) * blk_sz -1;
      
        for(int i = start; i <= en_idx; i++)
        {
            left += arr[i]; 
        }
          
        int right = 0, st_idx = main_blk * Sq1.blk_sz + 
                       sub_main_blk * Sq2[0].blk_sz +
                       (en_blk) * blk_sz; 
          
        for(int i = st_idx; i <= end; i++)
        {
            right += arr[i]; 
        }
          
        answer += left; 
        answer += right; 
    }
    else
    {
        for(int i = start; i <= end; i++)
        {
            answer += arr[i];
    } 
}
return answer;     
}
  
// Function for splitting query to level two
int queryLevel2(int start, int end, int main_blk,
                vector &arr)
{
    int blk_sz = Sq2[0].blk_sz;
  
    // Element Indexing at level1 decomposition
    int nstart = start - (main_blk * Sq1.blk_sz);
    int nend = end - (main_blk * Sq1.blk_sz);
  
    // Block indexing at level2 decomposition 
    int st_blk = nstart / blk_sz;
    int en_blk = nend / blk_sz;
      
    // Interblock data level2 decomposition 
    int answer = Sq2[main_blk].rdata[st_blk][en_blk]; 
      
    if(st_blk == en_blk)
    {
        answer += queryLevel3(start, end, main_blk,
                              st_blk, arr);
    } 
    else 
    { 
        answer += queryLevel3(start, (main_blk *
                  Sq1.blk_sz) + ((st_blk + 1) * 
                  blk_sz) - 1, main_blk, st_blk, arr);
          
        answer += queryLevel3((main_blk * Sq1.blk_sz) +
                  (en_blk * blk_sz), end, main_blk, en_blk, arr);
    } 
    return answer;     
}
  
// Function to return answer according to query
int Query(int start,int end,vector& arr)
{
    int blk_sz = Sq1.blk_sz;
    int st_blk = start / blk_sz;
    int en_blk = end / blk_sz;
      
    // Interblock data level1 decomposition     
    int answer = Sq1.rdata[st_blk][en_blk]; 
  
    if(st_blk == en_blk)
    {
        answer += queryLevel2(start, end, st_blk, arr);
    } 
    else 
    { 
        answer += queryLevel2(start, (st_blk + 1) * 
                  blk_sz - 1, st_blk, arr);
        answer += queryLevel2(en_blk * blk_sz, end, 
                              en_blk, arr);
    }
      
    // returning final answer
    return answer;
}
  
// Driver code
int main() 
{     
    n1 = 16;
      
    vector arr = {7, 2, 3, 0, 5, 10, 3, 12, 
                       18, 1, 2, 3, 4, 5, 6, 7};
  
    singleDecompose(arr);
      
    int q = 5;
    pair query[q] = {{6, 10}, {7, 12},
                  {4, 13}, {4, 11}, {12, 16}};
      
    for(int i = 0; i < q; i++)
    {
        int a = query[i].first, b = query[i].second;
        printf("%d\n", Query(a - 1, b - 1, arr));
    }
      
    return 0;
}
输出:
44
39
58
51
25

时间复杂度: O(q * d * n 1 /(2 ^ 3) )&ap; O(q * k)&ap; O(q)
辅助空间: O(k * n)&ap;上)
注意:本文仅解释将平方根分解为进一步分解的方法。

如果您希望与行业专家一起参加现场课程,请参阅《 Geeks现场课程》和《 Geeks现场课程美国》。