许多排序数组中的二进制搜索

Posted

技术标签:

【中文标题】许多排序数组中的二进制搜索【英文标题】:Binary search in many sorted arrays 【发布时间】:2013-10-01 13:42:19 【问题描述】:

我有许多带有排序数据的数组。我需要在这个数组中执行二进制搜索。如果此数组中的键范围不相交,则可以按范围对数组进行排序,然后像使用单个数组一样执行二进制搜索。但就我而言,这个数组中的键范围可以重叠。在这种情况下,只能执行过滤以排除某些数组,然后对另一部分进行排序。 在我的情况下,大多数数组不会重叠,因此过滤在大多数情况下只会返回一个数组,但不良数据仍有可能破坏性能。

在这种情况下是否可以使用更好的算法?可以稍微修改数组,添加一些元数据或指向其他数组的链接。

更新 该数组是由磁盘存储支持的数据页。我为此使用内存映射文件。我可以非常快速地对页面内的数据进行排序,因为此过程不涉及复制。但是要合并两个页面,我需要在页面之间复制大量数据。 我有非常大量的数据,TB!但是每页只有8Mb,所以可以快速搜索。不时添加到存储中的新页面。页面包含时间序列数据,因此它已经部分排序,新数组大部分时间不会与旧数据重叠。

【问题讨论】:

您忘记添加问题了。如果您添加一个,我们可以尝试回答。 谢谢,我真的愿意 :) 我可能遗漏了一些东西,但你为什么不能单独对每个数组运行二进制搜索? 您希望我们改进您的过滤算法(选择要搜索的 [重叠?] 数组)还是整体结构?在后一种情况下,请说明您需要许多小数组来做什么。 我可以,但是对于每个数组我都知道键范围,我不需要全部搜索。 【参考方案1】:

如果此数组中的键范围不相交,则可以按范围对数组进行排序,然后像使用单个数组一样执行二进制搜索。但就我而言,这个数组中的键范围可以重叠。

您仍然可以对它们进行排序。您可以使用interval tree 来存储它们并以对数时间检索要搜索的数组,而不是简单地按边界过滤所有数组。由于您有很多数组,而且它们很少相互重叠,因此这应该会显着提升性能。

【讨论】:

【参考方案2】:

如果您只计划执行几个查询,我认为您无法改进您的算法 - 我相信它已经相当不错了。如果您希望执行大量查询,我建议您将数组合并为一个数组并对其执行二进制搜索。合并与归并排序的算法相同,并且是线性的。所以只要查询的数量能弥补线性合并,它是值得的。

【讨论】:

我也是这么想的,但是这样做有一些问题。这个数组是由持久存储支持的内存页面,合并所有这些页面是非常多的 I/O!我有 TB 的这个数组(一个数组只有 8Mb)。在一页中排序和合并数据非常便宜(不需要复制数据),但是排序和合并所有数据很困难(需要在页面之间复制数据)。另一个问题是可以添加新数组并且它可以与合并数据重叠,所以我需要定期执行部分排序。 是的,数据搜索了很多次。问题是这些数据也是不时更新的。【参考方案3】:

8MB 页面中的 TB 意味着您可以处理几百万个页面。每个页面都在内部排序,页面中的值可以(很少,但可以)相互重叠。

我希望找到正确页面的影响比在页面中找到正确条目的影响更大。

因此我推荐以下方法:

维护一个数组,每页包含最低和最高键(lowestPageKeyhighestPageKey)。 进行二分搜索以获得合适的页面,并在页面内进行第二次二分搜索。 要在searchKey 上查找合适的页面,请在元数据中进行范围拟合二进制搜索。 使用条件lowestPageKey <= searchKey <= highestPageKey 查找正确的页面。 如果lowestPageKey > searchKey,您可以继续使用数组的下半部分 如果highestPageKey < searchKey,您可以继续使用数组的上半部分

这样您就可以找到正确的页面,并且可以在找到的页面中进行第二次二分搜索。

我的另一个问题:如果页面中的值重叠,您可以找到多个包含搜索键的条目(或多个页面)。在这种情况下,您期望得到什么?随机一页/条目,所有页面/条目,第一页/最后一页/条目还是错误消息?

【讨论】:

我需要两个查询:点查询 - 返回具有特定键(或最接近)的项目;范围查询 - 从可能占据多个页面的数组中返回键的范围。 好的,那么第一个查询将始终是范围版本。第二个(在候选页面内)您可以在两者之间进行选择【参考方案4】:

您暗示您对大多数静态数据有很多查询,所以我会假设。你在正确的轨道上。只是不要排除重叠的数组。跟踪重叠。这里是如何。首先编译范围索引。如果阵列是不相交的,它们将是块。当两个数组重叠时:

|     A    |
     |       B       |

分成三个范围:

| A  | AB  |   B     |

如图所示,范围索引只记录下限和上限以及覆盖该范围的数组列表。

现在搜索索引(在内存中)以确定要搜索的数组。然后去搜索所有这些。作为进一步的优化,可以使用块边界来限制数组搜索。换句话说,如果你得到上面的块 AB,你可以在搜索时排除 A 和 B 的一部分。

如何高效地编译和更新索引?我建议interval tree。此页面提供伪代码。如果您使用 C++ 进行编程,则可以使用 the relevant Boost library 来获得优势。

对于区间树,每个数组都是一个区间。当你用一个点查询树时,你会得到所有相关的区间。这些是需要搜索的数组。

【讨论】:

【参考方案5】:

维护多组具有不相交范围的数组。

当执行二分搜索时,在这些组上并行执行,或者在基于最小优先的组上尝试。

对于每个组,维护范围,并且每当新页面到达时,将其附加到与此新页面没有不相交范围的最大组。如果页面不属于任何组,请创建一个新的。

正如您所说,大多数情况下范围不重叠,拥有这些额外组的机会要少得多,但是当这种异常发生时算法可以适应。

【讨论】:

以上是关于许多排序数组中的二进制搜索的主要内容,如果未能解决你的问题,请参考以下文章

数组中的二分查找

对已旋转 N 次的排序数组进行二进制搜索的最佳方法是啥[重复]

排序数组中的时间复杂度

Swift:标准数组的二进制搜索?

如何在从左到右和从上到下排序的二维数组中搜索数字?

算法建议:查找排序数组中的数字计数