算法 - 范围查询

时间:2014-09-09 07:34:54

标签: c algorithm data-structures time-complexity

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

鉴于 n 元素的数组 A ,我们必须回答类型 i,j,X m 的查询。对于每个查询,我们必须输出范围 i,j 中大于 X 的数字。

E.g:

如果数组是:

  

3 4 1 7

并且查询 1 4 3 ,即我们必须在1到4的范围内输出大于3的数字。

输出: 2

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

约束:

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

我的方法:

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

还有其他方法可以以较小的复杂度解决它吗?

2 个答案:

答案 0 :(得分:5)

您正在寻找的数据结构是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)) n))的

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

答案 1 :(得分:0)

#include <stdio.h>
#include <stdlib.h>
/*
/*

此文件为C和Markdown。

您正在寻找的数据结构需要回答“三面性” 范围查询“。它们被称为三面因为你可以想象 你的数组代表二维集合中的n个点,其中 x坐标是数组索引,y坐标是值 在该指数;您的查询等于“打印所有y 点(x,y)的坐标,其中i <= x&lt; = j且y&gt; X“。这是 印刷价值的三个不等式。

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

*/

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

/*

根据您的使用情况,您可以使用非常简单的优先搜索树。该 树将有2 * n - 1个节点。叶节点与单个节点相关联 阵列中的位置。非叶节点与连续关联 阵列的区域。协会如下:

  • 根与整个数组相关联:位置[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 > 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);
}