binary_search,find_if和<functional> </functional>

时间:2010-03-03 13:59:24

标签: c++ stl std

std::find_if在其中一个重载函数中使用谓词。 Binders可以为用户定义的类型编写EqualityComparators,并将它们用于动态比较或静态比较。

相比之下,标准库的二进制搜索功能将比较器和const T&取为应用于比较的值。这感觉与我不一致,并且可能更低效,因为每次都必须使用两个参数调用比较器而不是将常量参数绑定到它。虽然可以以某种方式使用std::binary_search来实现std::bind,但这需要所有比较器都从std::binary_function继承。我见过的大多数代码都没有这样做。

让比较器在使用std::binary_function作为值而不是让我使用绑定器的算法时从const T&继承可能会带来什么好处?是否有理由不在这些函数中提供谓词重载?

3 个答案:

答案 0 :(得分:8)

std::binary_search的单参数谓词版本无法在O(log n)时间内完成。

考虑旧游戏“猜猜我正在考虑的信”。你可以问:“它是A吗?” “是B吗?”等等,直到你收到这封信。这是一个线性或O(n)算法。但更聪明的是问“它在M之前吗?” “它在G之前吗?” “它在我之前吗?”等等,直到你收到有问题的信。这是一个对数或O(log n)算法。

这是std::binary_search所做的,并且要做到这一点需要能够区分三个条件:

  • 候选人C是搜索项目X
  • 候选人C大于X
  • 候选人C小于X

单参数谓词P(x)仅表示“x具有属性P”或“x没有属性P”。你不能从这个布尔函数中得到三个结果。

比较器(比如,<)可以通过计算C&lt; X和X&lt; C.那么你有三种可能性:

  • !(C < X) && !(X < C) C等于X
  • C < X && !(X < C) C小于X
  • !(C < X) && X < C C大于X

请注意,X和C在不同时间都绑定到<的两个参数,这就是为什么你不能只将X绑定到<的一个参数并使用它。

编辑:感谢jpalecek提醒我binary_search使用&lt;,not&lt; =。 编辑编辑:感谢Rob Kennedy澄清。

答案 1 :(得分:1)

它们是完全不同的算法:find_if对于谓词为真的第一个项目,线性binary_search利用该范围进行排序以测试以对数时间如果给定值在其中。

binary_search的谓词指定了排序范围的函数(您很可能希望使用与排序范围相同的谓词)。

您无法利用排序来搜索满足某些完全不相关的谓词的值(无论如何您都必须使用find_if)。但是请注意,对于排序范围,您可以执行的不仅仅是使用lower_boundupper_boundequal_range测试是否存在。


问题是std::binary_function的目的是什么呢?

它所做的就是为result_typefirst_argument_typesecond_argument_type提供typedef。这些允许用户在仿函数作为模板参数的情况下找出并使用这些类型,例如

template <class T, class BinaryFunction>
void foo(const T& a, const T& b, BinaryFunction f)
{
     //declare a variable to store the result of the function call
     typename BinaryFunction::result_type result = f(a, b);
     //...
 }

但是,我认为在标准库中使用它们的唯一地方是创建其他仿函数包装器,如bind1stbind2ndnot1not2。 (如果它们被用于其他目的,那么当你使用函数作为函子时,人们会对你大喊大叫,因为它是一个不可移植的事情。)

例如,binary_negate可能实现为(GCC):

template<typename _Predicate>
class binary_negate
: public binary_function<typename _Predicate::first_argument_type,
             typename _Predicate::second_argument_type, bool>
{
protected:
  _Predicate _M_pred;

public:
  explicit
  binary_negate(const _Predicate& __x) : _M_pred(__x) { }

  bool
  operator()(const typename _Predicate::first_argument_type& __x,
     const typename _Predicate::second_argument_type& __y) const
  { return !_M_pred(__x, __y); }
};

当然,operator()也许只是一个模板,在这种情况下,那些typedef是不必要的(任何缺点?)。可能还有元编程技术来找出参数类型是什么,而无需用户明确地键入它们。我想它会在某种程度上影响C ++ 0x给出的功能 - 例如当我想为任何具有可变参数模板的arnd函数实现一个否定器时......

(IMO C ++ 98仿函数有点过于灵活和原始,例如与std::tr1::bindstd::tr1::mem_fn相比,但可能在编译器支持元编程技术时需要进行这些工作。没那么好,也许这些技术还在被发现。)

答案 2 :(得分:0)

这是对C ++中Functor概念的误解。

它与继承无关。使对象成为仿函数(有资格传递给任何算法)的属性分别是表达式object(x)object(x, y)的有效性,无论它是函数指针还是具有重载函数的对象呼叫运营商。绝对不是任何东西的继承。这同样适用于std::bind

使用二元仿函数作为比较器来自于比较器(例如std::less)是二元函子并且能够直接使用它们的好处。

恕我直言,提供或使用你提出的谓词版本没有任何好处(毕竟,它只需要传递一个参考)。使用绑定器不会有(性能)提升,因为它与算法的作用相同(绑定会通过额外的参数代替算法)。