我在哪里可以获得“有用的”C++ 二分搜索算法?
Posted
技术标签:
【中文标题】我在哪里可以获得“有用的”C++ 二分搜索算法?【英文标题】:Where can I get a "useful" C++ binary search algorithm? 【发布时间】:2010-10-01 13:41:57 【问题描述】:我需要一个与 C++ STL 容器兼容的二进制搜索算法,类似于标准库的 <algorithm>
标头中的 std::binary_search
,但我需要它返回指向结果的迭代器,而不是简单的布尔值告诉我元素是否存在。
(顺便说一句,标准委员会在为 binary_search 定义 API 时到底在想什么?!)
我在这里主要关心的是我需要二分查找的速度,所以虽然我可以使用其他算法找到数据,如下所述,但我想利用我的数据已排序的事实来获得好处二分搜索,而不是线性搜索。
到目前为止,如果缺少数据,lower_bound
和 upper_bound
将失败:
//lousy pseudo code
vector(1,2,3,4,6,7,8,9,0) //notice no 5
iter = lower_bound_or_upper_bound(start,end,5)
iter != 5 && iter !=end //not returning end as usual, instead it'll return 4 or 6
注意:只要它与容器兼容,我也可以使用不属于 std 命名空间的算法。比如,boost::binary_search
。
【问题讨论】:
关于编辑:这就是为什么 std::equal_range 是解决方案。否则,您将不得不测试是否相等(或等价更多) 您必须在使用 (lower/upper)_bound 后测试是否相等(请参阅下面的答案)。 lower_bound 和upper_bound 文档声明必须对范围进行排序,因此它们可以实现为二进制搜索。 @vividos,万岁!您只找到了我需要了解的文档!谢谢! 罗伯特,lower/upper_bound/equal_range 算法不适用于未排序的范围。您很幸运地看到他们使用您采集的元素样本。 【参考方案1】:没有这样的函数,但你可以用std::lower_bound
、std::upper_bound
或std::equal_range
编写一个简单的函数。
一个简单的实现可以是
template<class Iter, class T>
Iter binary_find(Iter begin, Iter end, T val)
// Finds the lower bound in at most log(last - first) + 1 comparisons
Iter i = std::lower_bound(begin, end, val);
if (i != end && !(val < *i))
return i; // found
else
return end; // not found
另一个解决方案是使用std::set
,它保证了元素的顺序并提供了一个方法iterator find(T key)
,它返回一个给定项目的迭代器。但是,您的要求可能与使用集合不兼容(例如,如果您需要多次存储同一个元素)。
【讨论】:
是的,这行得通,我现在有一个类似的实现,但是它是一个“幼稚”的实现,因为它没有利用情境的上下文,在这种情况下是排序数据。跨度> 我不太明白您的评论,因为 lower_bound 只能用于已排序的数据。复杂度低于使用 find(见编辑)。 为了补充 Luc 的答案,请查看 Matt Austern 的经典文章 Why You Shouldn't Use set, and What You Should Use Instead(C++ 报告 12:4,2000 年 4 月)以了解为什么使用排序向量的二进制搜索通常比 std::set 更可取,后者是基于树的关联容器。 不要使用*i == val
!而是使用!(val < *i)
。原因是lower_bound
使用<
,而不是==
(即T
甚至不需要是相等可比的)。 (请参阅 Scott Meyers 的 Effective STL,了解 equality 和 equivalence 之间的区别。)
@CanKavaklıoğlu 没有位于end
的元素。 C++ 标准库中的范围用半开间隔表示:结束迭代器“点”在最后一个元素之后。因此,它可以通过算法返回以指示未找到任何值。【参考方案2】:
你应该看看std::equal_range
。它将返回一对迭代器到所有结果的范围。
【讨论】:
根据cplusplus.com/reference/algorithm/equal_range,std::equal_range 的成本大约是 std::lower_bound 的两倍。它似乎包装了对 std::lower_bound 的调用和对 std::upper_bound 的调用。如果您知道您的数据没有重复项,那么这太过分了,而 std::lower_bound (如最佳答案所示)是最佳选择。 @BruceDawson: cplusplus.com 只提供了一个reference 实现来指定行为;对于实际实现,您可以查看您最喜欢的标准库。例如,在llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm 中,我们可以看到对 lower_bound 和 upper_bound 的调用是在不相交的时间间隔内进行的(经过一些手动二分搜索之后)。话虽如此,它可能会更昂贵,尤其是在具有多个匹配值的范围内。【参考方案3】:有一组:
http://www.sgi.com/tech/stl/table_of_contents.html
搜索:
lower_bound upper_bound equal_range binary_search单独说明:
他们可能认为搜索容器可以得出多个结果。但在您只需要测试是否存在的奇怪情况下,优化版本也会很好。
【讨论】:
binary_search 没有返回我前面提到的迭代器,这就是我寻找替代方案的原因。 是的,我知道。但它适合二进制搜索算法集。所以很高兴让其他人知道。 binary_search 就像 STL 中的许多其他东西一样,只是命名错误。我讨厌那个。测试存在与寻找某物不同。 这些二分搜索功能在您想知道要查找的元素的索引时没有用。我必须为此任务编写自己的递归函数。我希望这个,template如果 std::lower_bound 对您来说太低级,您可能需要检查boost::container::flat_multiset。 它是 std::multiset 的直接替代品,使用二进制搜索实现为排序向量。
【讨论】:
好链接;还有一个很好的链接in链接:lafstern.org/matt/col1.pdf,它描述了如何使用排序向量而不是集合(尽管两者都是 log(N))实现查找具有显着 更好的比例常数,速度快两倍(缺点是插入时间较长)。【参考方案5】:最短的实现,想知道为什么它没有包含在标准库中:
template<class ForwardIt, class T, class Compare=std::less<>>
ForwardIt binary_find(ForwardIt first, ForwardIt last, const T& value, Compare comp=)
// Note: BOTH type T and the type after ForwardIt is dereferenced
// must be implicitly convertible to BOTH Type1 and Type2, used in Compare.
// This is stricter than lower_bound requirement (see above)
first = std::lower_bound(first, last, value, comp);
return first != last && !comp(value, *first) ? first : last;
来自https://en.cppreference.com/w/cpp/algorithm/lower_bound
【讨论】:
我能想到这不在标准库中的两个原因:他们认为它很容易实现,但主要原因可能是它可能需要一个反向版本的 operator()() 如果value 不能与 *first 互换。【参考方案6】:int BinarySearch(vector<int> array,int var)
//array should be sorted in ascending order in this case
int start=0;
int end=array.size()-1;
while(start<=end)
int mid=(start+end)/2;
if(array[mid]==var)
return mid;
else if(var<array[mid])
end=mid-1;
else
start=mid+1;
return 0;
示例:考虑一个数组,A=[1,2,3,4,5,6,7,8,9] 假设你要搜索 3 的索引 最初,start=0 和 end=9-1=8 现在,因为 start
【讨论】:
拥有代码固然好,但您可以通过简要说明其对语言新手的工作原理来改进答案。 有人错误地flagged your post as low-quality。一个code-only answer is not low-quality。它是否试图回答这个问题?如果不是,则标记为“不是答案”或建议删除(如果在审核队列中)。 b) 它在技术上不正确吗?投反对票或发表评论。 不确定这与 std:lower_bound 解决方案的实际运行时间相比如何,但我个人更喜欢这个漂亮且可读的解决方案。它是一个干净而简单的二进制搜索返回索引的实现。不知道为什么评价这么差!!是的,它不是一个模板,是的,它不返回一个迭代器——但是我们应该调查一下有多少人在除整数向量之外的任何东西上进行二分搜索吗?【参考方案7】:检查这个函数,qBinaryFind:
RandomAccessIterator qBinaryFind ( RandomAccessIterator begin, RandomAccessIterator end, const T & value )
执行范围的二分查找 [begin, end) 并返回位置 值的发生。如果有 没有出现值,返回 结束。
范围内的项目 [begin, end) 必须按升序排序;看 qSort()。
如果出现多次 相同的值,其中任何一个都可以是 回来。使用 qLowerBound() 或 qUpperBound() 如果你需要更好 控制。
例子:
QVector<int> vect; vect << 3 << 3 << 6 << 6 << 6 << 8; QVector<int>::iterator i = qBinaryFind(vect.begin(), vect.end(), 6); // i == vect.begin() + 2 (or 3 or 4)
该函数包含在<QtAlgorithms>
标头中,该标头是Qt 库的一部分。
【讨论】:
很遗憾,该算法与 STL 容器不兼容。【参考方案8】:std::lower_bound() :)
【讨论】:
OP:“到目前为止lower_bound 和upper_bound 都失败了,因为...”【参考方案9】:返回范围内位置的解决方案可能是这样的,只使用迭代器上的操作(即使迭代器不算术它也应该工作):
template <class InputIterator, typename T>
size_t BinarySearchPos(InputIterator first, InputIterator last, const T& val)
const InputIterator beginIt = first;
InputIterator element = first;
size_t p = 0;
size_t shift = 0;
while((first <= last))
p = std::distance(beginIt, first);
size_t u = std::distance(beginIt, last);
size_t m = p + (u-p)/2; // overflow safe (p+u)/2
std::advance(element, m - shift);
shift = m;
if(*element == val)
return m; // value found at position m
if(val > *element)
first = element++;
else
last = element--;
// if you are here the value is not present in the list,
// however if there are the value should be at position u
// (here p==u)
return p;
【讨论】:
以上是关于我在哪里可以获得“有用的”C++ 二分搜索算法?的主要内容,如果未能解决你的问题,请参考以下文章
学习数据结构笔记(11) --- [二分搜索树(BinarySearchtTree)]