我有两个排序的数组Haystack
和Needles
。我需要迭代Needles
,每次在Haystack
中找到值大于Needle
的第一个点,以便执行下一步。
例如:
double [] dHaystack = { 1.2, 2.6, 7.0, 9.3, 19.4 }
double [] dNeedles = { 1.4, 6.4, 6.5, 7.0, 10.3 }
// expected indices 0 1 1 2 3
所以我应该得到的指数是第一个等于或小于针值的指数。
显而易见的方法是从大海捞针的每个针头开始迭代,或者从最后找到的索引向前迭代(因为Needles也被排序)。
但是我脑子里的一部分是喊“一分为二”!这里的二分法实际上会更快,因为编译器会发现优化比简单的块读取和迭代更难吗?它需要一个令人难以置信的长干草堆值得吗?
答案 0 :(得分:2)
您需要考虑该方案,
n * lg(m)< N + M 强>,
其中n是Needle的大小,m是Haystack的大小。
因此,这一切都取决于n和m值的各种组合。
答案 1 :(得分:1)
std :: upper_bound将为第一个元素提供迭代器,如果它们都不适用,则为集合的“更大”或“结束”
upper_bound接收开始和结束的迭代器,结束是集合结束的一个。如果您正在迭代不断增加的搜索值列表,那么您当然不需要遍历整个集合,但您的“开始”可以进一步向右移动。
当然,只有5个元素的干草堆,你使用什么样的搜索算法并不重要,但是如果它变得非常大,使用线性搜索可能会非常慢,特别是如果只有很少的针。< / p>
这种情况下两种尺寸都很重要。例如,如果您的搜索空间N很大但搜索的项目数(M)很小,则O(M log N)实际上要小得多。 (例如,M = 20,N = 16K,则log N = 15,M log N为300),与O(M + N)相比,在这种情况下为16K。如果M的大小与N大致相同,则O(M log N)实际上比O(N)差很多。
因此,根据您的馆藏大小,您可以选择使用哪种算法。
答案 2 :(得分:1)
显而易见的方法是从最后找到的索引开始迭代...(因为Needles也是排序的)。
是
但是我脑子里的一部分是喊“一分为二”!这里的二分法实际上会更快,因为编译器会发现优化比简单的块读取和迭代更难吗?它需要一个令人难以置信的长干草堆值得吗?
我不认为编译器优化是一个问题(它只是删除了不必要的工作),而不是实际固有的必要工作量。如果两组的大小相似,那么我会坚持使用明显的方法。如果干草堆比针组大得多,则二分或甚至插值可能会产生稍好的性能。除非这对你的应用程序至关重要,否则你不太可能注意到这些差异,如果它是你应该进行基准测试,特别是因为你可以使用std::set
和上限或下限快速获得一个有效的实现(我永远不会记得我需要的 - 不要经常使用),如果您的图书馆支持,可以使用最后一个位置作为起始位置的提示。
答案 3 :(得分:1)
使用std :: upper_bound,它是随机访问迭代器的O(log n),并在最短和最简单的代码中提供您所需的内容。
在您担心每分钟性能之前,请测试您当前的代码(可能还有测试备选方案)instead of making assumptions。特别注意,您可以从每次迭代的最后找到的索引开始搜索(第一个参数为upper_bound)。
// Available in Boost, C++0x, and many other places. Implementation copied
// here for the sake of the example.
template<class T, int N>
T* end(T (&a)[N]) {
return a + N;
}
void example() {
double haystack[] = {1.2, 2.6, 7.0, 9.3, 19.4};
double needles[] = {1.4, 6.4, 6.5, 7.0, 10.3};
double *begin = haystack;
for (double *n = needles; n != end(needles); ++n) {
double *found = std::upper_bound(begin, end(haystack), *n);
if (found == end(haystack)) break;
std::cout << *n << " at index " << (found - haystack) << '\n';
begin = found;
}
}