在哪里可以获得“有用的” C ++二进制搜索算法?


106

我需要一个与C ++ STL容器兼容的二进制搜索算法,就像std::binary_search在标准库的<algorithm>标头中一样,但是我需要它返回指向结果的迭代器,而不是简单的布尔值告诉我元素是否存在。

(附带说明,标准委员会在为binary_search定义API时到底在想什么?!)

我主要关心的是我需要二进制搜索的速度,因此,尽管可以使用其他算法查找数据,如下所述,但我想利用这样一个事实,即我的数据经过排序以获得二进制的好处搜索,而不是线性搜索。

到目前为止lower_boundupper_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


2
关于编辑:这就是为什么std :: equal_range是解决方案的原因。否则,您将必须测试是否相等(或等价得多)
卢克·赫米特

在使用(lower / upper)_bound之后,您必须测试是否相等(请参见下面的答案)。
卢·图拉耶(Lu Touraille)

lower_bound和upper_bound文档指出必须对范围进行排序,因此可以将它们实现为二进制搜索。
生动的

@vividos,欢呼!您发现的只是我需要了解的文档!谢谢!
罗伯特·古尔德,2009年

Robert,lower / upper_bound / equal_range算法不适用于未排序的范围。您很幸运地看到他们使用您所采集的元素样本。
卢克·赫米特

Answers:


97

有没有这样的功能,但你可以用写一个简单的一个std::lower_boundstd::upper_boundstd::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)将迭代器返回给定项目的方法。但是,您的要求可能与集合的使用不兼容(例如,如果您需要多次存储同一元素)。


是的,这是可行的,并且我现在有一个类似的实现,但是从某种意义上说,它没有利用情境的上下文,在这种情况下是排序的数据,这是一个“天真的”实现。
罗伯特·古尔德

5
我不太理解您的评论,因为lower_bound只能用于排序的数据。复杂度低于使用查找(请参见编辑)。
卢·图拉

4
为了补充Luc的答案,请查看Matt Austern的经典文章“ 为什么不应该使用set和应该使用什么”(C ++ Report 12:4,2000年4月),以了解为什么使用带排序矢量的二进制搜索通常比std :: set更可取,这是一个基于树的关联容器。
ZunTzu 2012年

16
不要使用*i == val!宁可使用!(val < *i)。原因是不lower_bound使用<==(即T甚至不需要具有可比性)。(请参见Scott Meyers的有效STL,以了解平等等效之间的区别。)
gx_13年

1
@CanKavaklıoğlu没有位于的元素end。C ++标准库中的范围以半开间隔表示:结束迭代器在最后一个元素之后 “指向” 。这样,它可以由算法返回以指示未找到任何值。
卢·图拉耶

9

你应该看看std::equal_range。它将返回所有结果范围的一对迭代器。


根据cplusplus.com/reference/algorithm/equal_range,std::equal_range的成本大约是std :: lower_bound的两倍。看来它包装了对std :: lower_bound的调用和对std :: upper_bound的调用。如果您知道您的数据没有重复项,那是过大了,std :: lower_bound(如顶部答案所示)是最佳选择。
Bruce Dawson 2015年

@BruceDawson:cplusplus.com仅提供参考实现来指定行为;对于实际的实现,您可以检查自己喜欢的标准库。例如,在llvm.org/svn/llvm-project/libcxx/trunk/include/algorithm中,可以看到对Lower_bound和upper_bound的调用是在不相交的间隔上进行的(在一些手动二进制搜索之后)。话虽如此,它可能会更昂贵,尤其是在具有多个值匹配的范围内。
Matthieu M.

6

有一组:

http://www.sgi.com/tech/stl/table_of_contents.html

搜索:

另请注意:

他们可能认为搜索容器可能会得出不止一个结果。但是,在奇怪的情况下,您只需要测试是否存在优化版本就可以了。


3
正如我前面提到的,binary_search不返回迭代器,这就是为什么我正在寻找替代方法。
罗伯特·古尔德

1
是的我知道。但它适合二进制搜索算法的集合。因此,这对其他人很了解。
马丁·约克

8
就像STL中的许多其他内容一样,binary_search的名称也错误。我讨厌那个。测试存在与寻找事物并不相同。
OregonGhost

2
如果您想知道要查找的元素的索引,这些二进制搜索功能将无用。我必须为此任务编写自己的递归函数。我希望将template <class T> int bindary_search(const T&item)添加到下一个C ++版本中。
周克敏

3

如果std :: lower_bound太低了,您可能需要检查boost :: container :: flat_multiset。它是使用二进制搜索实现为已排序向量的std :: multiset的直接替代。


1
良好的联系;以及链接中的一个很好的链接:lafstern.org/matt/col1.pdf,它描述了如何使用排序矢量而不是集合(尽管两者均为log(N))实现查找,它们具有明显更好的比例常数,并且〜速度快一倍(缺点是插入时间更长)。
Dan Nissenbaum 2013年

2

最短的实现,想知道为什么它不包含在标准库中:

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


我可以想到两个不在标准库中的原因:他们认为这很容易实现,但是主要原因可能是,如果值不能与* first互换,则可能需要operator()()的反向版本。
user877329

1

检查此功能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)

该函数包含在Qt<QtAlgorithms>一部分的标头中。


1
不幸的是,该算法与STL容器不兼容。
bartolo-otrit


0
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 <= end; 中= 4; (array [mid]是5)!= 3现在,3位于mid的左侧,小于5。因此,我们仅搜索数组的左侧部分,因此,start = 0和end = 3; mid = 2。因为array [mid] == 3,所以我们得到了要搜索的数字。因此,我们返回其索引等于中点。


1
拥有代码是很好的,但是您可以通过简短地解释它对新手的工作方式来改善答案。
Taegost

有人错误地将您的帖子标记为低质量。一个唯一的代码,答案不是低质量的。它会尝试回答这个问题吗?如果不是,则标记为“不是答案”或建议删除(如果在审阅队列中)。b)技术上不正确吗?下注或评论。
Wai Ha Lee

0

返回范围内位置的解决方案可能是这样的,仅使用迭代器上的操作即可(即使迭代器不算术也可以使用):

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;

}
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.