【问题标题】:Questions about std::lower_bound and std::upper_bound关于 std::lower_bound 和 std::upper_bound 的问题
【发布时间】:2019-05-19 07:57:48
【问题描述】:

我正在努力优化对具有“几乎”排序数据的数据结构的查找。我相当有信心它的“几乎”细节实际上并不重要,但不确定

实际的数据结构比 SO 所需的更复杂,所以我对其进行了简化。简化版是std::vector<Level>,它有价格、买价和卖价:

  • 价格严格上涨
  • 出价通常按升序排列
  • 询问通常按降序排列

当我说一般时,我的意思是数据有一个长序列,通常是零,后面跟着有意义的值,但有些零实际上可能是负数。但是,我只会搜索正值,因此所有零和负值都不是有意义的返回值

以下是我的 SO 简化程序的测试数据:

//                        Price  Bid  Ask    Index
levels.emplace_back(Level( 42.0,   0, 150)); //  0
levels.emplace_back(Level( 43.0,   0,  71)); //  1
levels.emplace_back(Level( 44.0,   0,  70)); //  2
levels.emplace_back(Level( 45.0,   0,  70)); //  3
levels.emplace_back(Level( 46.0,   0,  69)); //  4
levels.emplace_back(Level( 47.0,   0,   0)); //  5
levels.emplace_back(Level( 48.0,  -1,  -1)); //  6
levels.emplace_back(Level( 49.0,   0,   0)); //  7
levels.emplace_back(Level( 50.0,  80,   0)); //  8
levels.emplace_back(Level( 51.0,  81,   0)); //  9
levels.emplace_back(Level( 52.0,  81,   0)); // 10
levels.emplace_back(Level( 53.0,  82,   0)); // 11
levels.emplace_back(Level( 54.0, 201,   0)); // 12

当我在此结构中搜索某个 Bid“Seek Bid”时,我想找到 Bid 大于或等于“Seek Bid”的第一个级别的价格

当我在这个结构中搜索一些 Ask 时,“Seek Ask”,我想找到最后一个 Level 的价格,其 Ask 大于或等于“Seek Ask”

以下是我的 SO 简化程序:

#include <algorithm>
#include <iostream>
#include <vector>

struct Level final {
    Level() = delete;
    Level(const double a_price, const int a_bid, const int a_ask) :
        m_price(a_price),
        m_bid  (a_bid),
        m_ask  (a_ask)
    {}

    const double m_price;
    const int    m_bid;
    const int    m_ask;
};

int main(int argc, char** argv) {
    if (argc != 3) {
        std::cout << "Usage: " << argv[0] << " <Seek Bid> <Seek Ask>\n";
        exit(1);
    }

    std::vector<Level> levels;

    //                        Price  Bid  Ask    Index
    levels.emplace_back(Level( 42.0,   0, 150)); //  0
    levels.emplace_back(Level( 43.0,   0,  71)); //  1
    levels.emplace_back(Level( 44.0,   0,  70)); //  2
    levels.emplace_back(Level( 45.0,   0,  70)); //  3
    levels.emplace_back(Level( 46.0,   0,  69)); //  4
    levels.emplace_back(Level( 47.0,   0,   0)); //  5
    levels.emplace_back(Level( 48.0,  -1,  -1)); //  6
    levels.emplace_back(Level( 49.0,   0,   0)); //  7
    levels.emplace_back(Level( 50.0,  80,   0)); //  8
    levels.emplace_back(Level( 51.0,  81,   0)); //  9
    levels.emplace_back(Level( 52.0,  81,   0)); // 10
    levels.emplace_back(Level( 53.0,  82,   0)); // 11
    levels.emplace_back(Level( 54.0, 201,   0)); // 12

    const int seekBid = atoi(argv[1]);
    const int seekAsk = atoi(argv[2]);
    std::cout << "Seek Bid: " << seekBid << ", Seek Ask: " << seekAsk << '\n';

    if (seekBid <= 0 || seekAsk <= 0) {
        std::cout << "Seek Bid or Seek Ask is not positive\n";
        exit(1);
    }

    // If the last Level's Bid is < Seek Bid then what I am looking for doesn't exist
    if (levels.back().m_bid < seekBid)
        std::cout << "Cannot satisfy Seek Bid\n";
    else {
        // Find the first Level with a Bid <= Seek Bid
        // Not sure why I need to specify < instead of <= but appears to work
        const auto it = std::lower_bound(
            levels.begin(),
            levels.end(),
            seekBid,
            [](const Level& a_level, const int a_bid) { return a_level.m_bid < a_bid; }
        );
        std::cout << "Bid Price: " << it->m_price << ", Bid Index: " << &*it - &levels[0] << '\n';
    }

    // If the first Level's Ask is < Seek Ask then what I am looking for doesn't exist
    if (levels.front().m_ask < seekAsk)
        std::cout << "Cannot satisfy Seek Ask\n";
    else {
        // Find the last Level with Ask <= Seek Ask
        // Need to use std::prev due to how std::upper_bound works
        // Not sure why I need to specify < instead of <= but appears to work
        const auto it = std::prev(std::upper_bound(
            levels.begin(),
            levels.end(),
            seekAsk,
            [](const int a_ask, const Level& a_level) { return a_level.m_ask < a_ask; }
        ));
        std::cout << "Ask Price: " << it->m_price << ", Ask Index: " << &*it - &levels[0] << '\n';
    }

    return 0;
}

以下是运行我的 SO 测试程序的一些示例。 “Seek Bid”为 81 而“Seek Ask”为 70 的情况非常重要,因为有两个 81 Bids 和两个 70 Asks。在实际程序中找到第一个 81 Bid 和最后一个 70 Ask 很重要:

Seek Bid: 79, Seek Ask: 68
Bid Price: 50, Bid Index: 8
Ask Price: 46, Ask Index: 4

Seek Bid: 80, Seek Ask: 69
Bid Price: 50, Bid Index: 8
Ask Price: 46, Ask Index: 4

Seek Bid: 81, Seek Ask: 70
Bid Price: 51, Bid Index: 9
Ask Price: 45, Ask Index: 3

Seek Bid: 82, Seek Ask: 71
Bid Price: 53, Bid Index: 11
Ask Price: 43, Ask Index: 1

所有这些结果都是正确的,但是这些是我的问题:

  • 我有必要把所有的负数都归零吗 在搜索之前以保证正确的结果在使用之前 std::lower_boundstd::upper_bound 考虑到我只是 寻找积极的价值观?换句话说,做负面的 考虑到我的搜索要求,是否会导致任何类型的未定义行为?
  • std::lower_bound 工作原理的描述 en.cppreference.com 和 cplusplus.com 非常混乱,我只 意识到在我的 lambda 表达式中使用 &lt; 而不是 &lt;= 是“正确的” 通过反复试验。如果我是,为什么使用 &lt;= 不“正确” 寻找第一个/最后一个级别 &lt;= 我正在搜索的内容 为了?

【问题讨论】:

标签: c++


【解决方案1】:

一般要求在Compare 中描述。使用提供的比较,必须有一个单一的顺序,以便等效元素组在该顺序中具有特定的位置。 lower_boundupper_bound 要求输入是这样的顺序。

是否有必要在搜索之前将所有负数都归零以保证正确的结果。

在这种特殊情况下不是,因为它只会根据给定的正值测试Levels,而不是相互测试。您的comp0 视为等同于-1,因此它们“故障”并不重要。在此数据集中搜索 0 或负数将是未定义的行为。

如果我正在寻找我正在搜索的第一个/最后一个级别 &lt;=,为什么使用 &lt;= 不“正确”?

因为这打破了严格弱序的不对称要求。如果您只想要更大的值,请使用upper_bound

【讨论】:

  • 您对我的第一个问题的回答真的很有帮助,但您能澄清一下您对我的第二个问题的回答吗?我目前使用std::lower_boundstd::upper_bound 分别用于Bid 和Ask 并且都使用&lt; 而不是&lt;= 得到正确的结果
【解决方案2】:

几乎所有(有序的)stl 容器都依赖于严格的弱排序。严格的弱排序根据一个项目的优先级来定义元素的相对位置。

因此,严格的弱排序具有以下属性:

  • 对于 S 中的所有 x,x
  • 对于 S 中的所有 x、y,如果 x
  • 对于 S 中的所有 x、y、z,如果 x
  • 对于 S 中的所有 x、y、z,如果 x 与 y 不可比(x

如果您希望这些 STL 容器和算法按规定工作,您提供的比较必须提供这种严格的弱排序。

参考,更多细节:

https://en.cppreference.com/w/cpp/named_req/Compare

https://github.com/bashrc-real/Codearchive/blob/master/cpp/Strict_weak_ordering_and_stl.md

https://en.wikipedia.org/wiki/Weak_ordering

【讨论】:

    【解决方案3】:

    std::lower_boundstd::upper_bound 执行简单的二分查找。他们不搜索特定的元素值,而是搜索分区点。您应用std::lower_bound 的范围不需要进行排序。 requirement 是:

    范围[first, last) 必须根据表达式element &lt; valuecomp(element, value) 进行分区,即表达式为true 的所有元素必须位于表达式为false 的所有元素之前。


    我有必要在搜索之前将所有负数都设为零吗...

    没有。如果value 为正数,则您的范围始终根据表达式element &lt; value 进行分区。

    如果我正在寻找我正在搜索的第一个/最后一个级别 &lt;=,为什么使用 &lt;= 不“正确”?

    因为std::lower_bound 依赖于&lt; 关系,而不是&lt;=。粗略地说,它从!(b &lt; a) 派生出a &lt;= b

    【讨论】:

    • 你能澄清第二个答案吗?对我来说!(b &lt; a) 意味着b &gt;= a,所以我还不明白
    • @asimes, lower_bound 说:给我一个范围,一个value 和一个a &lt; value 关系,我会给你第一个元素&gt;= value 的(位置)背部。如果你给它&lt;= 而不是&lt;,它就不能做它宣传的事情。这是一份合同。
    猜你喜欢
    • 2014-06-26
    • 2017-08-30
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2016-10-18
    • 2015-10-27
    • 2011-11-21
    • 1970-01-01
    相关资源
    最近更新 更多