【问题标题】:Is there a C++ MinMax Heap implementation?有 C++ MinMax Heap 实现吗?
【发布时间】:2011-01-16 04:54:12
【问题描述】:

我正在寻找类似 stl 中的算法(push_heappop_heapmake_heap),但能够有效地弹出最小值和最大值。 AKA 双端优先级队列。如here 所述。

双端优先级队列的任何干净实现也可以作为替代方案,但是这个问题主要是关于 MinMax Heap 实现。

我的google-fu没有结果,但肯定存在吧?

【问题讨论】:

  • 怎么样:std::priority_queue<T, std::deque<T>, C>?
  • @Dirk - 我认为 std::priority_queue 只保证它的第一个元素是最大的。最后一个也有保证吗?
  • @Manuel:我正在回复帖子的“双端优先级队列的任何干净实现 [...]”部分。
  • @Dirk - 您的建议是双端优先级队列吗? - 在我看来,它就像一个单端优先级队列,恰好存储在 std::deque 而不是 std::vector 或数组或其他任何东西中。
  • 我遇到过这个问题,我找到了解决方案:forestofcode.blogspot.com/2010/12/… 这是一个使用 MINMAX 堆的双端优先级队列的实现。我还没有阅读所有答案,所以你的问题可能已经解决了。最好的,阿提拉

标签: c++ algorithm data-structures heap


【解决方案1】:

如果您正在寻找算法实现,请尝试搜索 Github

【讨论】:

  • 超链接已损坏,导致此答案已过时。
  • @AmeyaVS 知道了。更新为 GH 搜索。
【解决方案2】:

对不起,代码冗长,但它可以正常工作,只是它可能会使阅读变得复杂,并且可能包含一些不必要的变量,而且我没有上传插入函数。将其用作包含 .h 文件。

#include <math.h>
#define bool int
#define true 1
#define false 0
#define Left(i) (2 * (i))
#define Right(i) (2 * (i) + 1)
#define Parent(i) ((i) / 2)

void TrickleDown(int* A, int n)
{
    int i;
    for (i = 1; i <= n / 2; i++)
    {

        if (isMinLevel(i, n) == true)
            TrickleDownMin(A, i, n);
        else
            TrickleDownMax(A, i, n);
        Print(A, n);
        printf("i = %d\n", i);
    }
}

int isMinLevel(int i, int n)//i is on min level or not
{
    int h = 2;
    if (i == 1)
        return true;
    while (true)
    {
        if (i >= pow(2, h) && i <= pow(2, h + 1) - 1)
            return true;
        else if (i > n || i < pow(2, h))
            return false;
        h += 2;
    }
}

void swap(int* a, int* b)
{
    *a ^= *b;
    *b ^= *a;
    *a ^= *b;
}

void TrickleDownMin(int* A, int i, int n)
{
    int m, lc, rc, mc, lclc, lcrc, mlc, rclc, rcrc, mrc;
    int child;
    lc = Left(i);
    if (lc > n) // A[i] has no children
        return;
    else
    {
        rc = Right(i);
        mc = rc > n ? lc : (A[lc] > A[rc]) ? rc : lc;
        child = true; // keep tracking m comes from children or grandchildren
        // m = smallest of children and grand children
        lclc = Left(lc);
        if (lclc <= n)
        {
            lcrc = Right(lc);
            mlc = lcrc > n ? lclc :(A[lclc] > A[lcrc]) ? lcrc : lclc;
            mc = mlc > mc ? mc : mlc;
            if (mc == mlc)
                child = false;
        }
        rclc = Left(rc);
        if (rclc <= n)
        {
            rcrc = Right(rc);
            mrc = rcrc > n ? rclc : (A[rclc] > A[rcrc]) ? rcrc : rclc;
            mc = mrc > mc ? mc : mrc;
            if (mc == mrc)
                child = false;
        }
        m = mc;
        if (!child)//m is one of the child of i
        {
            if (A[m] < A[i])
            {
                swap(&A[m], &A[i]);
                if (A[m] > A[Parent(m)])
                    swap(&A[m], &A[Parent(m)]);
                TrickleDownMin(A, i, m);
            }
        }
        else    //m is child of i
        {
            if (A[m] < A[i])
                swap(&A[i], &A[m]);
        }

    }
}

void TrickleDownMax(int* A, int i, int n)
{
    int m, lc, rc, mc, lclc, lcrc, mlc, rclc, rcrc, mrc;
    int child;
    lc = Left(i);
    if (lc > n)//A[i] has no children
        return;
    else
    {
        rc = Right(i);
        mc = rc > n ? lc : (A[lc] < A[rc]) ? rc : lc;
        child = true; //keep tracking m comes from choldren or grandchildren
        //m = greatest of children and grand children
        lclc = Left(lc);
        if (lclc <= n)
        {
            lcrc = Right(lc);
            mlc = lcrc < n ? lclc : (A[lclc] < A[lcrc]) ? lcrc : lclc;
            mc = mlc < mc ? mc : mlc;
            if (mc == mlc)
                child = false;
        }
        rclc = Left(rc);
        if (rclc <= n)
        {
            rcrc = Right(rc);
            mrc = rcrc < n ? rclc : (A[rclc] < A[rcrc]) ? rcrc : rclc;
            mc = mrc < mc ? mc : mrc;
            if (mc == mrc)
                child = false;
        }
        m = mc;
        if (!child)//m is one of the child of i
        {
            if (A[m] > A[i])
            {
                swap(&A[m], &A[i]);
                if (A[m] < A[Parent(m)])
                    swap(&A[m], &A[Parent(m)]);
                TrickleDownMax(A, i, m);
            }
        }
        else      //m is child of i
        {
            if (A[m] > A[i])
                swap(&A[i], &A[m]);
        }

    }
}

void Print(int* a, int n)
{
    int i;
    for (i = 1; i < n + 1; i++)
    {
        printf("%d  ", a[i]);
    }

}

int DeleteMin(int* A, int n)
{
    int min_index = 1;
    int min = A[1];
    swap(&A[min_index], &A[n]);
    n--;
    TrickleDown(A, n);
    return min;
}

int DeleteMax(int* A, int n)
{
    int max_index, max;
    max_index = n < 3 ? 2 : (A[2] > A[3]) ? 2 : 3;
    max = A[max_index];
    swap(&A[max_index], &A[n]);
    n--;
    TrickleDown(A, n);
    return max;
}

【讨论】:

  • 对不起,必须 -1 这个。仅前 3 行 #define 是未定义的行为(为什么要重新定义 booltruefalse?),以下几行是不好的样式(使用函数),并且您无缘无故地重写了 swap(顺便说一句,这是一个不正确的实现;考虑a == b)。很适合提供一个实现,但我个人认为不好的实现总比没有好。
【解决方案3】:

不确定这是否正是您要寻找的,但这是我在大学时代创建的 MinMax 堆。这是一个更大项目的一部分,因此在衡量性能的“秒表”类上有一个无关的参考。我不包括这门课,因为它不是我的工作。去掉它并不难,所以我保留对它的引用。

snipplr上的代码

要使用,只需使用您想要使用的任何类型创建一个新的堆实例。 (注意,自定义类型需要重载比较运算符)。创建该类型的数组,然后将其传递给构造函数并指定当前数组大小和最大值。此实现仅在传递的数组之上工作,因为这是我唯一需要的,但您拥有实现推送和弹出方法所需的一切。

【讨论】:

    【解决方案4】:

    我找不到任何好的实现,但由于其他人也找不到,我猜你会自己写,在这种情况下,我有一些方便的参考资料供你参考。

    一篇似乎没人提到的论文是 Min-Max-Heaps 的原始命题:

    http://www.cs.otago.ac.nz/staffpriv/mike/Papers/MinMaxHeaps/MinMaxHeaps.pdf

    我已经两次实现了本文中的最小-最大堆(不是在 C 中),发现它相当简单。

    我从未实施过的改进是 Min-Max-Fine-Heap。我在普通的旧精细堆上找不到任何好的论文或参考资料,但我确实在 min-max-fine-heap 上找到了一篇,它显然性能更好:

    http://arxiv.org/ftp/cs/papers/0007/0007043.pdf

    【讨论】:

    • 我实际上发现了一种不需要 MinMax 堆的不同(和改进)方法来解决我的问题。我只是悬赏这个问题,希望能从中得到一些有用的东西。顺便说一句,我已经找到了您链接的那篇论文,但是您将其添加到此处以供参考是一件好事。
    • +1 表示用一些有用的伪代码编写的第一个文档。
    【解决方案5】:

    您是否有不能使用std::set 的原因?听起来像这样,以及一些用于访问和删除 set::begin()--set::end() 的包装器将解决问题。我想很难找到通常比 set 的默认实现更快地执行 MinMax Heap 的东西。

    【讨论】:

    • 按照您的建议使用 std::set 是一种解决方法。可能没问题,但 MinMax 堆将是我的问题的最佳解决方案,这就是我特别要求它的原因。但是记住 std::set 或多或少做同样的事情是很有用的,所以感谢您指出这一点。
    • 可能缺少唯一键?它排序的集合中的键必须是唯一的,但不能在优先级队列或双端优先级队列中。
    • @Slauma 还有std::multiset
    • 但是对于大型堆/大量操作,集合实现将比最小最大堆慢...最小最大堆与普通堆具有相同的时间复杂度,即 O(1) 查找- min / find-max 操作,而对于一般集合实现 find-min 和 find-max 通常是 O(log n)。
    • 我赞成 antti.huimas 的评论,但这可能是过早的优化。也许可以抽象出队列,以便在真正需要时可以换出实现。或者不要——如果需要的话,重构以换掉它可能不会太多。另外,我相信在 std::set 中找到端点实际上是 O(1) - set 对象实际上包含指向最小和最大节点以及根的指针,因此 begin() 和 end( ) 得到有效处理。
    猜你喜欢
    • 1970-01-01
    • 2011-05-03
    • 2019-08-02
    • 2011-07-15
    • 2021-07-14
    • 2018-02-14
    • 2011-07-27
    • 2016-08-16
    • 2015-04-12
    相关资源
    最近更新 更多