【问题标题】:rationale for std::lower_bound and std::upper_bound?std::lower_bound 和 std::upper_bound 的基本原理?
【发布时间】:2014-06-26 14:20:04
【问题描述】:

STL 提供二分查找函数 std::lower_bound 和 std::upper_bound, 但我倾向于不使用它们,因为我无法记住它们的作用, 因为他们的合同对我来说似乎完全是个谜。

只看名字, 我猜“lower_bound”可能是“last lower bound”的缩写,
即排序列表中的最后一个元素 同样,我猜“upper_bound”可能是“第一个上限”的缩写,
即排序列表中 >= 给定 val(如果有)的第一个元素。

但是文档说他们做的事情与此完全不同—— 对我来说,这似乎是一种倒退和随机的混合。 套用文档:
- lower_bound 找到 >= val
的第一个元素 - upper_bound 找到 > val

的第一个元素

所以 lower_bound 根本找不到下限;它找到第一个 upper 边界!? 而upper_bound 会找到第一个严格上限

这有意义吗?? 你怎么记得的?

【问题讨论】:

    标签: c++ stl lower-bound upperbound


    【解决方案1】:

    如果范围 [first, last) 中有多个元素,其值等于您要搜索的值 val,则范围 [l, u) 其中

    l = std::lower_bound(first, last, val)
    u = std::upper_bound(first, last, val)
    

    恰好是在 [first, last) 范围内等于 val 的元素范围。所以lu相等范围 的“下限”和“上限”。如果您习惯于以半开区间的方式思考,那是有道理的。

    (请注意,std::equal_range 将在一次调用中返回成对的下限和上限。)

    【讨论】:

    • 作为副作用,如果项目是唯一的,std::lower_bound 是您的标准二分搜索,如果元素在范围内,则返回元素,如果不在范围内,则返回 end()
    • 几个优秀的答案几乎立即出现。这对我来说似乎是最清楚的(并且 equal_range 参考非常有用且非常合适),因此可以接受。谢谢大家!
    • @MooingDuck std::lower_bound 如果元素不在集合中,则不一定会返回 end() - 你会得到元素应该去的插入位置。因此,如果要进行二分查找,您还需要检查结果的等价性/相等性。
    【解决方案2】:
    std::lower_bound
    

    返回一个迭代器,该迭代器指向范围 [first, last) 中不小于(即大于或等于)值的第一个元素。

    std::upper_bound
    

    返回一个迭代器,指向范围 [first, last) 中大于值的第一个元素。

    因此,通过混合下限和上限,您可以准确描述范围的开始位置和结束位置。

    这有意义吗??

    是的。

    例子:

    想象向量

    std::vector<int> data = { 1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6 };
    
    auto lower = std::lower_bound(data.begin(), data.end(), 4);
    
    1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6
                      // ^ lower
    
    auto upper = std::upper_bound(data.begin(), data.end(), 4);
    
    1, 1, 2, 3, 3, 3, 3, 4, 4, 4, 5, 5, 6
                               // ^ upper
    
    std::copy(lower, upper, std::ostream_iterator<int>(std::cout, " "));
    

    打印:4 4 4


    http://en.cppreference.com/w/cpp/algorithm/lower_bound

    http://en.cppreference.com/w/cpp/algorithm/upper_bound

    【讨论】:

      【解决方案3】:

      在这种情况下,我认为一张图片值一千字。假设我们使用它们在以下集合中搜索2。箭头显示了两者将返回的迭代器:

      因此,如果集合中已经存在多个具有该值的对象,lower_bound 将为您提供一个引用其中第一个的迭代器,upper_bound 将提供一个引用紧接在最后一个之后的对象。

      这(除其他外)使返回的迭代器可用作hint 参数到insert

      因此,如果您使用这些作为提示,您插入的项目将成为具有该值的新第一项(如果您使用了lower_bound)或具有该值的最后一项(如果您使用了upper_bound)。如果该集合之前不包含具有该值的项目,您仍将获得一个可用作 hint 的迭代器,以将其插入集合中的正确位置。

      当然,你也可以在没有提示的情况下插入,但是使用提示可以保证插入以恒定的复杂性完成,前提是可以在迭代器指向的项目之前立即插入要插入的新项目(如在这两种情况下都会这样做)。

      【讨论】:

        【解决方案4】:

        我接受了 Brian 的回答,但我刚刚意识到另一种有用的思考方式,这让我更加清楚,所以我添加这个以供参考。

        将返回的迭代器视为指向,而不是指向元素 *iter,而只是 before 该元素,即 before 该元素和列表中的前一个元素 if有一个。这样一想,两个函数的契约就变得对称了:lower_bound 找到从 =val 的过渡位置,upper_bound 找到从 val 的过渡位置。或者换句话说,lower_bound 是比较等于 val 的项目范围的开始(即 std::equal_range 返回的范围),upper_bound 是它们的结束。

        我希望他们会在文档中这样谈论它(或给出的任何其他好的答案);这样就不会那么神秘了!

        【讨论】:

          【解决方案5】:

          考虑顺序

          1 2 3 4 5 6 6 6 7 8 9

          6 的下限是前 6 的位置。

          6 的上限是 7 的位置。

          这些位置作为共同的(开始,结束)对,指定 6 值的运行。


          例子:

          #include <algorithm>
          #include <iostream>
          #include <vector>
          using namespace std;
          
          auto main()
              -> int
          {
              vector<int> v = {1, 2, 3, 4, 5, 6, 6, 6, 7, 8, 9};
              auto const pos1 = lower_bound( v.begin(), v.end(), 6 );
              auto const pos2 = upper_bound( v.begin(), v.end(), 6 );
              for( auto it = pos1; it != pos2; ++it )
              {
                  cout << *it;
              }
              cout << endl;
          }
          

          【讨论】:

            【解决方案6】:

            源代码实际上有第二个解释,我发现它对理解函数的含义很有帮助:

            lower_bound:查找可以插入[val]的第一个位置 不改变顺序。

            upper_bound:查找可以插入 [val] 的 last 位置 不改变顺序。

            this [first, last) 形成一个范围,可以插入 val 但仍保持容器的原始顺序

            lower_bound 返回“第一”,即找到“范围的下边界”

            upper_bound 返回“最后一个”,即找到“范围的上边界”

            【讨论】:

              【解决方案7】:

              这两个函数非常相似,因为它们会在排序后的序列中找到一个插入点,从而保持排序。如果序列中没有与搜索项相等的现有元素,它们将返回相同的迭代器。

              如果您尝试在序列中查找某些内容,请使用lower_bound - 如果找到,它将直接指向该元素。

              如果您要插入序列,请使用upper_bound - 它会保留重复项的原始顺序。

              【讨论】:

              • 听起来不错的建议,但我不认为它解决了为什么术语“lower_bound”和“upper_bound”对这些操作有任何意义,对于那些被条款和文档感到困惑的人.
              • @DonHatch 其他人已经解决了这个问题,如果这个答案对你没有用,很抱歉。我想将这些信息提供给未来的 Google 员工,他们想知道为什么要使用一个与另一个。
              【解决方案8】:

              是的。这个问题绝对有道理。当有人给这些函数起名字时,他们只考虑具有重复元素的排序数组。如果您有一个包含唯一元素的数组,“std::lower_bound()”更像是搜索“上限”,除非它找到实际元素。

              这就是我对这些函数的记忆:

              • 如果您正在执行二分搜索,请考虑使用 std::lower_bound(),并阅读手册。 std::binary_search() 也是基于它。
              • 如果您想在唯一值的排序数组中找到某个值的“位置”,请考虑使用 std::lower_bound() 并阅读手册。
              • 如果您有在排序数组中搜索的任意任务,请阅读 std::lower_bound() 和 std::upper_bound() 的手册。

              自从您上次使用这些功能后一两个月后仍未阅读手册,几乎肯定会导致错误。

              【讨论】:

                【解决方案9】:

                想象一下,如果你想在[first, last) 中找到等于val 的第一个元素,你会怎么做。您首先从严格小于val 的第一个元素中排除,然后从最后一个向后排除 - 1 严格大于val 的元素。那么剩下的范围就是[lower_bound, upper_bound]

                【讨论】:

                  【解决方案10】:

                  对于数组或向量:

                  std::lower_bound: 返回一个迭代器,该迭代器指向

                  范围内的第一个元素
                  • 小于或等于值。(对于数组或向量按降序排列)
                  • 大于或等于值。(对于数组或向量按升序排列)

                  std::upper_bound: 返回一个迭代器,指向范围内的第一个元素

                  • 小于值。(对于数组或向量按降序排列)

                  • 大于值。(对于数组或向量按升序排列)

                  【讨论】:

                  • 这不是真的,这就是问题的症结所在。对于按降序排列的数组,std::lower_bound 返回“一个迭代器,它指向范围 [first, last) 中不小于(即大于或等于)值的第一个元素,如果没有找到这样的元素,则返回 last ",因此它将返回第一个元素或最后一个元素(取决于该值是大于还是小于该第一个元素)。换句话说,如果数组是递减的,那么如果第一个元素不是,数组中的后续元素都不会大于 value。
                  猜你喜欢
                  • 1970-01-01
                  • 2017-08-30
                  • 1970-01-01
                  • 1970-01-01
                  • 1970-01-01
                  • 2016-10-18
                  • 2015-10-27
                  • 2011-11-21
                  • 1970-01-01
                  相关资源
                  最近更新 更多