📜  最大堆中的第 K 个最大元素

📅  最后修改于: 2021-10-28 02:01:14             🧑  作者: Mango

给定一个大小为 n 的最大堆,找到最大堆中的第 k最大元素。

例子:

天真的方法:我们可以从最大堆中提取 k 次最大元素,提取的最后一个元素将是第 k 个最大元素。每次删除操作需要 O(log n) 时间,因此这种方法的总时间复杂度为 O(k * log n)。

下面是这个方法的实现:

// C++ program for the
// above approach
#include 
using namespace std;
  
// Structure for the heap
struct Heap {
    vector v;
    int n; // Size of the heap
  
    Heap(int i = 0)
        : n(i)
    {
        v = vector(n);
    }
};
  
// Generic function to
// swap two integers
void swap(int& a, int& b)
{
    int temp = a;
    a = b;
    b = temp;
}
  
// Returns the index of
// the parent node
inline int parent(int i)
{
    return (i - 1) / 2;
}
  
// Returns the index of
// the left child node
inline int left(int i)
{
    return 2 * i + 1;
}
  
// Returns the index of
// the right child node
inline int right(int i)
{
    return 2 * i + 2;
}
  
// Maintains the heap property
void heapify(Heap& h, int i)
{
    int l = left(i), r = right(i), m = i;
    if (l < h.n && h.v[i] < h.v[l])
        m = l;
    if (r < h.n && h.v[m] < h.v[r])
        m = r;
    if (m != i) {
        swap(h.v[m], h.v[i]);
        heapify(h, m);
    }
}
  
// Extracts the maximum element
int extractMax(Heap& h)
{
    if (!h.n)
        return -1;
    int m = h.v[0];
    h.v[0] = h.v[h.n-- - 1];
    heapify(h, 0);
    return m;
}
  
int kThGreatest(Heap &h, int k)
{
    for (int i = 1; i < k; ++i)
        extractMax(h);
    return extractMax(h);
}
  
// Driver Code
int main()
{
    Heap h(7);
    h.v = vector{ 20, 15, 18, 8, 10, 5, 17 };
    int k = 4;
  
    cout << kThGreatest(h, k);
    return 0;
}
输出:
15

时间复杂度:O(k * log n)

有效的方法:我们可以注意到一个关于最大堆的有趣观察。i 层的元素 x 有 i – 1 个祖先。根据 max-heaps 的特性,这些i-1 个祖先保证大于 x。这意味着 x 不能在堆的最大 i – 1 个元素中。使用此属性,我们可以得出结论,第 k最大元素的级别最多为 k。

我们可以减小最大堆的大小,使其只有 k 层。然后我们可以通过我们之前提取最大元素k次的策略来获得第k个最大元素。请注意,堆的大小减少到最大 2 k – 1,因此每个 heapify 操作将花费 O(log 2 k ) = O(k) 时间。总时间复杂度为 O(k 2 )。如果 n >> k,则此方法的性能优于前一种。

下面是这个方法的实现:

// C++ program for the
// above approach
#include 
using namespace std;
  
// Structure for the heap
struct Heap {
    vector v;
    int n; // Size of the heap
  
    Heap(int i = 0)
        : n(i)
    {
        v = vector(n);
    }
};
  
// Generic function to
// swap two integers
void swap(int& a, int& b)
{
    int temp = a;
    a = b;
    b = temp;
}
  
// Returns the index of
// the parent node
inline int parent(int i)
{
    return (i - 1) / 2;
}
  
// Returns the index of
// the left child node
inline int left(int i)
{
    return 2 * i + 1;
}
  
// Returns the index of
// the right child node
inline int right(int i)
{
    return 2 * i + 2;
}
  
// Maintains the heap property
void heapify(Heap& h, int i)
{
    int l = left(i), r = right(i), m = i;
    if (l < h.n && h.v[i] < h.v[l])
        m = l;
    if (r < h.n && h.v[m] < h.v[r])
        m = r;
    if (m != i) {
        swap(h.v[m], h.v[i]);
        heapify(h, m);
    }
}
  
// Extracts the maximum element
int extractMax(Heap& h)
{
    if (!h.n)
        return -1;
    int m = h.v[0];
    h.v[0] = h.v[h.n-- - 1];
    heapify(h, 0);
    return m;
}
  
int kThGreatest(Heap &h, int k)
{
    // Change size of heap
    h.n = min(h.n, int(pow(2, k) - 1));
  
    for (int i = 1; i < k; ++i)
        extractMax(h);
  
    return extractMax(h);
}
  
// Driver Code
int main()
{
    Heap h(7);
    h.v = vector{ 20, 15, 18, 8, 10, 5, 17 };
    int k = 2;
  
    cout << kThGreatest(h, k);
    return 0;
}
输出:
18

时间复杂度:O(k 2 )

更有效的方法:我们可以通过以下算法进一步提高这个问题的时间复杂度:

  1. 创建一个优先级队列P,将最大堆的根节点插入到P中。
  2. 重复这些步骤 k – 1 次:
    1. 从 P 中弹出最大的元素。
    2. 插入弹出元素的左右子元素。 (如果它们存在)。
  3. P 中的最大元素是最大堆的第 k 个最大元素。

优先级队列的初始大小为 1,并且在 k-1 步中的每一步最多增加 1。因此,优先级队列中最多有 k 个元素,pop 和 insert 操作的时间复杂度为 O(log k)。因此总时间复杂度为 O(k * log k)。

下面是上述方法的实现:

// C++ program for the
// above approach
#include 
using namespace std;
  
// Structure for the heap
struct Heap {
    vector v;
    int n; // Size of the heap
  
    Heap(int i = 0)
        : n(i)
    {
        v = vector(n);
    }
};
  
// Returns the index of
// the left child node
inline int left(int i)
{
    return 2 * i + 1;
}
  
// Returns the index of
// the right child node
inline int right(int i)
{
    return 2 * i + 2;
}
  
int kThGreatest(Heap &h, int k)
{
    priority_queue > p;
    p.push(make_pair(h.v[0], 0));
  
    for (int i = 0; i < k - 1; ++i) {
        int j = p.top().second;
        p.pop();
        int l = left(j), r = right(j);
        if (l < h.n)
            p.push(make_pair(h.v[l], l));
        if (r < h.n)
            p.push(make_pair(h.v[r], r));
    }
    return p.top().first;
}
  
// Driver Code
int main()
{
    Heap h(7);
    h.v = vector{ 20, 15, 18, 8, 10, 5, 17 };
    int k = 2;
  
    cout << kThGreatest(h, k);
    return 0;
}
输出:
18

时间复杂度:O(k * log k)

如果您希望与专家一起参加现场课程,请参阅DSA 现场工作专业课程学生竞争性编程现场课程。