【问题标题】:A better than O(N) solution for searching a vector of sorted intervals比 O(N) 更好的搜索排序区间向量的解决方案
【发布时间】:2022-08-19 15:15:08
【问题描述】:

给定一组排序区间(first >= second),按区间的第一个元素排序:

{1, 3}, {1, 2}, {2, 4}, {2, 2}, {2, 3}, {3, 5}, {3, 3}, {3, 7}

是否有一种有效的算法来确定与给定相交的第一个区间 输入间隔?例如:

Query ({0, 0}) = returns end()
Query ({2, 4}) = returns iterator to element 0
Query ({3, 8}) = returns iterator to element 0
Query ({4, 9}) = returns iterator to element 2
Query ({7, 8}) = returns iterator to element 7
Query ({8, 9}) = returns end()

高效我的意思是比 O(N) 更好。我有一种模糊的感觉下限或者上限这个问题的解决方案,但我没有精神力来解决它是什么。

这是我不满意的 O(N) 解决方案。

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

int main()
{
    using Interval = std::pair<int, int>;
    using Sequence = std::vector<Interval>;
    using Iterator = Sequence::const_iterator;

    auto Query = [](Sequence const & sequence, Interval const & interval) -> Iterator
    {
        return std::find_if(sequence.begin(), sequence.end(), [interval](Interval const & other) {
            return interval.first <= other.second && interval.second >= other.first;
        });
    };

    auto Print = [](Sequence const & sequence, Iterator const & iterator) -> void
    {
        if (iterator == sequence.cend())
        {
            std::cout << \"end()\\n\";
        }
        else
        {
            std::cout << std::to_string(std::distance(sequence.cbegin(), iterator)) << \"\\n\";
        }
    };

    Sequence sequence = {
        {1, 3}, { 1, 2 }, { 2, 4 }, { 2, 2 }, { 2, 3 }, { 3, 5 }, { 3, 3 }, { 3, 7 }
    };

    auto result = Iterator();

    result = Query(sequence, { 0, 0 });

    Print(sequence, result);

    result = Query(sequence, { 2, 4 });

    Print(sequence, result);

    result = Query(sequence, { 3, 8 });

    Print(sequence, result);

    result = Query(sequence, { 4, 9 });

    Print(sequence, result);

    result = Query(sequence, { 7, 8 });

    Print(sequence, result);

    result = Query(sequence, { 8, 9 });

    Print(sequence, result);
}

输出:

end()
0
0
2
7
end()
  • 您是否一定要以这种方式存储数据,或者这是您迄今为止想出但愿意改变的东西?我问 b/c 有一个更好的数据结构,我认为......
  • 您不能跳过任何左边界低于请求区间右边界的区间,因为右边界(没有排序/约束)总是可以假设一个可能使检查的区间相交的值,所以你不能使用二分搜索立即跳转到“有趣的区域”。您可以使用此数据布局的唯一改进是在检查的间隔左边界大于请求的间隔右边界时提前退出。
  • @NathanOliver 我不认为你可以进行二进制搜索。向量排序为剩下区间的点;正确的点可以是任意的。
  • @NathanOliver二进制搜索可以确定的唯一一件事是您可以在向量中的哪个位置结束搜索,因为每个后续值都将从一个太大的值开始,以至于无法在那里找到所需的值。我认为您仍然需要在该范围内执行线性搜索,如果这样做,您无论如何都会找到这个早期的结束值,您不需要先搜索它。
  • 我发现了一个叫做“间隔树”的东西......我想知道这是否是 Lorro 的意思......dgp.toronto.edu/public_user/JamesStewart/378notes/22intervals

标签: c++ search vector


【解决方案1】:

不可能有比线性算法更快的算法。考虑输入,其中每个元素的第一个值为 0,第二个值在某个范围内是随机均匀的。考虑查询为{x,x+1},其中x 在同一范围内。

由于您想要“与给定输入相交的第一个间隔”,因此排序现在没用了。您必须全部扫描。

因此,您无法击败O(N)

【讨论】:

  • 从技术上讲,这就是答案 - 但是,在 cmets OP 中写道,他们愿意改变输入的数据结构(这允许更快)。
  • 如果输入按开始排序,然后按结束排序,则对于这种情况,您仍然可以更快地找到间隔。但是,如果输入是{i, x - 1} for i in [0, x - 1(,那么您必须扫描每个输入。
【解决方案2】:

正如 cmets 中所说,使用问题中概述的数据结构,你不能做得比线性更好

您不能跳过任何左边界低于请求区间右边界的区间,因为右边界(没有排序/约束)总是可以假设一个可能使检查的区间相交的值,所以你不能使用二分搜索立即跳转到“有趣的区域”。

尽管一旦到达左边界大于查询右边界的区间,您就可以提前退出。

但是,鉴于 OP 可以更改数据表示形式,一旦您使间隔不重叠,这将变得更加容易;鉴于我们只对相交/不相交测试感兴趣,这不是什么大问题。

从排序的数据开始,合并重叠间隔只是一次线性传递的问题:保持“当前”元素,并在读取重叠元素时保持增长;一旦你得到一个不相交的,推动“当前”的,并使其成为新的“当前”。这也可以很容易地在原地工作,作为通常的remove_if 算法的变体。

对不相交的间隔进行排序后,您可以将二进制搜索应用于您的问题以进行 O(log n) 查询。而不是vector&lt;pair&gt;,更有利的结构是一个普通的vector&lt;int&gt;,其(排序的)边界扁平化为一个序列。

使用这种表示,查询如下所示:

  • 做一个upper_bound寻找查询区间的左边界;
  • 如果你得到end,那么没有更大的界限,所以我们没有交集;
  • 否则,从迭代器中获取索引:
    • 如果索引为奇数,则表示大于搜索值的第一个边界是正确的边界;这意味着查询左边界落在一个区间内,因此我们有一个交集;
    • 否则,查询的左边界落在两个区间之间的一个洞里,所以我们必须检查查询的右边界; upper_bound 找到的(偶数索引)元素是下一个区间的左边界:如果它小于查询的右边界,则我们有一个交集,否则查询区间完全落入洞内,所以没有交叉点。

【讨论】:

  • 有趣的。但我不太明白你所说的扁平化为一个序列是什么意思。
  • @Robinson 不是有一个对列表,每个对代表一个区间,而是将其展平为左/右边界;例如,{{1, 3}, {5, 9}, {11, 27}} 变为 {1, 3, 5, 9, 11, 27}。偶数索引是左边界,奇数索引是右边界。像这样展平间隔可以更容易地处理,如答案中所述。
  • 这个问题要求第一个相交的区间,而不仅仅是它是否相交。您正在更改输入数据,而不仅仅是数据结构,并且只能在它相交时回答,但不再是原始区间中的哪个相交。
【解决方案3】:

对于给定的输入,无法检查每个间隔(最坏情况)。

但是,如果您有很多查询,您可以一次处理输入以使查询更快。

首先创建

struct Interval {
    int start, end;
    size_t first_index;
};
std::vector<Interval> intervals;

然后从输入间隔中填充它,修改每个间隔以便没有重叠。

假设您有(1, 4)(3, 6),然后首先添加{1, 4, 0},然后添加{4, 6, 1}。间隔重叠的部分(3, 4) 查询需要返回第一个间隔,以便您从第二个中剪掉该部分。

此过程需要O(n) 时间,但之后您可以使用二进制搜索来查找间隔。因此,它只会在许多查询中更快地摊销。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2021-05-23
    • 2019-09-13
    • 1970-01-01
    • 2021-07-13
    • 2022-01-18
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多