【问题标题】:Algorithm - Range Query算法 - 范围查询
【发布时间】:2014-11-02 13:05:12
【问题描述】:

我正在尝试解决以下问题:

给定一个由 n 个元素组成的数组 A,我们必须回答 i,j,X 类型的 m 个查询时间>。对于每个查询,我们必须输出 i,j 范围内大于 X 的数字。

例如:

如果数组是:

3 4 1 7

查询是 1 4 3,即我们必须输出大于 3 的数字,范围为 1 到 4。

输出: 2

因为三个数都大于 3 (4, 7)

约束:

1 < n < 10^5
1 < A[i] < 10^9

我的方法:

我尝试使用 sqrt(n) 的段的段树来处理它。它给出了O(sqrt(n))的时间复杂度。

有没有其他更复杂的方法来解决它?

【问题讨论】:

  • 我怎样才能在少于 |P-Q| 的时间内回答“C”类型的查询? .我应该使用什么数据结构,这样我才能非常快速地回答这些查询。
  • 这是你家庭作业的一部分吗?
  • 没有。我只是想解决它。我看起来像是实现段树的问题。但我知道如何解决这个问题..
  • 我已经改变了问题。我想我现在应该更清楚了。
  • 编辑了我的答案以描述 O(log^2 n) 算法。

标签: c algorithm data-structures time-complexity


【解决方案1】:

您要查找的数据结构是 2D range tree。但是,以下方法具有 O(sqrt(n) log n) 操作时间,可能更容易实现。 (我将把改进留给 O(sqrt(n log n)) 作为练习。)

将奶牛分成 sqrt(n) 个连续的 sqrt(n) 奶牛块。对于每个块,正常存储符号并按排序顺序添加。处理 M 查询时,进行必要的更改并采取措施(时间 O(sqrt(n) log n))。处理 C 查询时,在未排序的数组中对部分重叠的块使用蛮力(时间 O(sqrt(n))),在排序的数组中对完全包含的块使用二分搜索(时间 O(sqrt(n) log n))。

这是 O(log^2 n) 查询时间版本。保留一个segment tree 的已排序多集,其中每个已排序多集包含该段中奶牛的符号。处理 M 查询时,从包含该母牛的段的所有多重集中删除母牛的旧符号。以类似的方式重新插入奶牛的新标志。处理 C 查询时,将查询间隔划分为 O(log n) 段,并检查每个已排序多重集中在范围内的元素数量。支持后一种操作的最佳方式可能是二叉搜索树,其中每个节点都存储其左子子树中的节点数。我首先不建议这样做的原因是 (i) 它需要更多的实现工作 (ii) 对于 n = 100000,运行时间函数的差异是 sqrt(n)/log(n)**(3 /2) ~ 8,这很可能会被两种方法的相对缓存友好性和后者的额外复杂性所吞噬。

【讨论】:

    【解决方案2】:
    #include <stdio.h>
    #include <stdlib.h>
    /*
    
    /*
    

    这个文件是 C 和 Markdown。

    你要找的数据结构需要回答“三面 范围查询”。它们被称为三面,因为你可以想象 您的数组表示二维集中的 n 个点,其中 x 坐标是数组索引,y 坐标是值 在那个索引处;然后,您的查询相当于“打印所有 y i X" 的点 (x,y) 的坐标。这是 打印值的三个不等式。

    一种非常简单的数据结构,可以支持三种大小的范围 查询是优先搜索树 (PST)。

    */
    
    typedef struct NODE {
      int y_max;
      struct NODE *left, *right;
    } Node;
    
    /*
    

    对于您的使用,您可以使用一个非常简单的优先级搜索树。这 树将有 2*n - 1 个节点。叶节点与单个关联 数组中的位置。非叶节点与一个连续的 阵列的区域。关联如下:

    • 根与整个数组相关联:positions [0, n)

    • 范围为 [a,b) 的节点的左子节点与 位置 [a, floor((a + b)/2))

    • 范围为 [a,b) 的节点的右子节点与 位置 [floor((a+b)/2), b)

    如果区域为空,则不为其存储节点。

    关联是隐式的,不存储在任何地方;有可能 从 n 和树的形状推断出来。

    每个节点额外存储所有节点中的最大值 值存储在其关联区域中。

    例如,如果您的数组是 {60, 70, 80, 90, 100},那么树 节点及其关联的区域和值是:

                           [0,5):100
                          /         \
                  [0,2):70           [2,5):100
                 /       \          /         \
         [0,1):60   [1,2):70    [2,3):80     [3,5):100
                                            /         \
                                        [3,4):90   [4,5):100
    

    使用递归构造 PST 需要线性时间:

    */
    
    int Max(int x, int y) { return x > y ? x : y; }
    
    Node * Construct(int n, int ys[]) {
      if (!n) return NULL;
      Node *result = malloc(sizeof(Node));
      if (1 == n) {
        result->y_max = ys[n];
        result->left = result->right = NULL;
      } else {
        // To find y_max, we first recurse:
        result->left = Construct(n / 2, ys);
        result->right = Construct(n - n / 2, ys + n / 2);
        // The the y_max is the max of the child y_max values:
        result->y_max = Max(result->left->y_max, result->right->y_max);
      }
      return result;
    }
    
    /*
    

    要查询 PST,您需要找到树的所有叶子 给定的[i, j] 区域和y_max &gt; X。这也可以做到 递归:

    */
    
    void Query(int a, int b, Node *pst, int i, int j, int X) {
      if (!pst || a > j || b < i || pst->y_max <= X) return;
      if (b - a == 1) printf("%d ", pst->y_max);
      Query(a, (a + b) / 2, pst->left, i, j, X);
      Query((a + b) / 2, b, pst->right, i, j, X);
    }
    
    /*
    

    仔细计算表明时间复杂度为 O(log n + k), 其中 k 是报告的节点数。请注意,下限 任何支持这些查询的数据结构都是 Ω(k),因为 仅打印结果就需要这么多时间。

    你可以通过谷歌搜索“优先级”找到许多仔细的核算 搜索树”。

    上面的数据结构在很多方面都不是最优的,但它是 这种方式解释起来很简单。它可以被组织存储在一个 大小为 n/2 + O(1) 的数组,而不是上面的 Θ(n) 树节点。


    可以通过遍历树在 O(log n) 时间内执行更新 递归并在备份途中重建y_max

    */
    
    void Update(int i, int v, int a, int b, Node *pst) {
      if (a + 1 == b) {
        pst->y_max = v;
        return;
      }
      // Recurse down one subtree:
      int mid = (a + b) / 2;
      if (i < mid) {
        Update(i, v, a, mid, pst->left);
      } else {
        Update(i, v, mid, b, pst->right);
      }
      pst->y_max = Max(pst->left->y_max, pst->right->y_max);
    }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2012-08-11
      • 2019-05-20
      • 2012-01-09
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多