【问题标题】:C++ Priority Queue - ordering intervalsC++ 优先队列 - 排序间隔
【发布时间】:2021-03-22 06:28:17
【问题描述】:

最初给定一个时间间隔。每次,我们选择最大的间隔并将其分成两半。如果出现平局,则选择起点最低的区间。

例如 - [0,9] 第一次拆分 - P1 [0,4] 和 P2 [4,9]

第二次拆分: dist(P1) = 3 => 如果选择 P1,新的间隔将是 [0,2] 和 [2,4]。 dist(P2) = 4 => 如果选择 P2,则新区间为 [4, 6] 和 [6,9] 在这两种情况下,我们都必须创建距离为 1 的子区间。所以,这是平局。并且,我们选择 P1 作为 P1

[0,2], [2, 4], [4, 9]

第三次分裂: [0,2], [2, 4], [4,6], [6,9]

第四次分裂: 有一个平局,s0,被选中 [0,2] [0,1], [1,2], [2,4], [4,6], [6, 9]

第五分裂: [0,1], [1,2], [2,3], [3,4], [4,6], [6,9]

位居榜首的可能候选人:[4,6]

但是,我总是将 [1,2] 放在首位。

#include <iostream>
#include <queue>
using namespace std;
int main()
{
    auto dist{ [](const auto & p) {
        return p.second - p.first - 1;
    } };
    auto comp{ 
        [&dist](const auto & p1, const auto & p2) {
            if (abs(dist(p1) - dist(p2)) <= 1) {
                return p1.first > p2.first;
            }
            return dist(p1) < dist(p2);
        }
    };
    priority_queue<pair<int, int>, vector<pair<int, int>>, decltype(comp)> maxQ{comp};
    maxQ.push({ 0, 9 }); // initial interval
    for (int i{ 0 }; i < 5; ++i) {
        auto ii{ maxQ.top() };
        maxQ.pop();
        int mid = (ii.first + ii.second) / 2;
        maxQ.push({ ii.first, mid });
        maxQ.push({ mid, ii.second });
    }

    while (!maxQ.empty()) {
        auto& ii{ maxQ.top() };
        cout << ii.first << " : " << ii.second << endl;
        maxQ.pop();
    }
}

我得到以下输出:

1 : 2
6 : 9
0 : 1
2 : 3
3 : 4
4 : 6

IMO,1 : 2 间隔不应位于顶部。有人可以在这里帮助我吗,为什么会这样。

【问题讨论】:

    标签: c++


    【解决方案1】:

    原来这个问题与优先级队列比较器的设计方式有关,参考The reason of using `std::greater` for creating min heap via `priority_queue`

    其要点是,当比较两个节点时,如果比较器返回 true,则 p1 将低于 p2。所以一个基本的“>”比较器,顶部有较小的节点,底部有较大的节点。

    为了可视化这个问题,我在调试器中运行了它,这是将 (1,2) 置于 (6,9) 之上的时刻。这是优先级队列的当前状态:

    2 : 4
    6 : 9
    4 : 6
    0 : 1
    1 : 2
    

    我们看到 (2,4) 在前面 (6,9),这是意料之中的,因为我们的比较函数表明 (2,4)

    然后,代码会弹出优先级队列的顶部,这意味着将 (2,4) 替换为新的最大间隔。 C++ 中的优先级队列是如何做到这一点的,它们是交换堆的第一个元素和最后一个元素,然后将其大小减少 1(因此我们丢失了原始的第一个元素)。

    所以在交换和减小大小之后,我们的堆看起来像这样:

    1 : 2
    6 : 9
    4 : 6
    0 : 1
    

    那么,由于之前被认为是最小的元素现在位于队列的顶部,我们需要找到它的正确位置。

    所以 (1,2) 将查看它的孩子 (6,9) 和 (4,6),看看哪个更重要。

    对于我们的比较运算符,(4,6) 是更重要的节点。

    然后将 (1,2) 与前两个节点中最重要的 (4,6) 进行比较,以查看是否需要执行交换以使队列有效。

    然后它发现 (1,2) 更重要,因为 1

    1 : 2
    6 : 9
    4 : 6
    0 : 1
    

    【讨论】:

    • 感谢您的回复。在这里,我选择了初始区间 [0,9]。第一次拆分 - P1 [0,4] 和 P2 [4,9] 对于第二次拆分:dist(P1) = 3 => 如果选择 P1,新的间隔将是 [0,2] 和 [2,4]。 dist(P2) = 4 => 如果选择 P2,新的区间是 [4, 6] 和 [6,9] 在这两种情况下,我们必须创建距离 1 的子区间。所以,这是平局。并且,我们选择 P1 作为 P1
    • 对,问题是它会导致像您面临的问题一样的级联问题。我将编辑我的评论以逐步完成它。
    【解决方案2】:

    我们可以清楚地看到[1,2][4,6] 之前排序,通过将其插入您的比较器:

    comp([1,2], [4,6])
      if (abs(dist([1,2]) - dist([4,6])) <= 1) {   // abs(0 - 1) <= 1   or  1 <= 1
        return 1 > 4;   //  false
      }
      return dist([1,2]) < dist([4,6]);   //  not reached
    

    只有您可以更正比较器以实现您的目标,但是如果您希望在[1,2] 之后[4,6] 之后排序,则现有代码是错误的。

    不过,根据您的描述,您可以尝试一下:

    if (abs(dist(p1)) == abs(dist(p2)))
    

    但我会竭尽全力确保您的订购是严格弱的,因为它必须适用于容器。多撒一些abs 可能会有所帮助。

    总的来说,这是一个相当复杂的比较器,乍一看并不容易理解。

    【讨论】:

    • 谢谢。现在这很有意义。我可以在这里看到问题。
    【解决方案3】:

    我认为这是因为间隔的顺序不严格。 例如P1(0,1)、P2(4,6) 和 P3(6,9)

    P1 应该在 P2 之前。 P2 应该在 P3 之前。 P3 应该在 P1 之前。

    这太疯狂了。我怎么能在这里设置严格的对排序?

    【讨论】:

    • 你可以通过执行线性搜索来做到这一点。
    猜你喜欢
    • 2012-11-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-28
    • 2012-03-28
    • 1970-01-01
    • 2014-08-05
    • 1970-01-01
    相关资源
    最近更新 更多