📜  K-ary堆

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

先决条件 – 二叉堆

K元堆是二叉堆(K=2)的推广,其中每个节点有K个子节点而不是2个。就像二叉堆一样,它遵循两个属性:

1) 近乎完备的二叉树,除最后一层外,所有层都有最大节点数,从左到右填充。

2) 和二叉堆一样,它可以分为两类: (a) 最大 k-ary 堆(根处的键大于所有后代,并且对所有节点递归如此)。 (b) 最小 k-ary 堆(根的键小于所有后代,并且对所有节点递归地相同)

例子:

3-ary max heap - root node is maximum
                 of all nodes
         10
     /    |   \
   7      9     8
 / | \   /
4  6  5 7


3-ary min heap -root node is minimum 
                of all nodes
         10
      /   |  \
    12    11  13
  / | \
14 15 18 

具有 n 个节点的完整 k-ary 树的高度由 log k n 给出。

K元堆的应用

  • 与二进制堆相比,K-ary 堆在优先队列的实现中使用时允许更快地减少密钥操作(O(log 2 n)) 用于二进制堆,而 O(log k n) 用于 K-ary 堆)。然而,与使用二进制堆作为优先级队列时的复杂度 O(log 2 n) 相比,它会导致 extractMin() 操作的复杂度增加到 O(k log k n)。这使得 K-ary 堆在降低优先级操作比 extractMin() 操作更常见的算法中更有效。 示例:Dijkstra 的单源最短路径算法和 Prim 的最小生成树算法
  • K-ary 堆比二进制堆具有更好的内存缓存行为,这使得它们在实践中运行得更快,尽管它具有更大的 extractMin() 和 delete() 操作的最坏情况运行时间(两者都是 O(k log k) n) )。

执行
假设数组的索引基于 0,数组表示 K 元堆,因此对于我们考虑的任何节点:

  • 索引 i 处节点的父节点(根节点除外)位于索引 (i-1)/k
  • 索引 i 处节点的子节点位于索引 (k*i)+1 , (k*i)+2 …。 (k*i)+k
  • 大小为 n 的堆的最后一个非叶节点位于索引 (n-2)/k

buildHeap() :从输入数组构建堆。
该函数从最后一个非叶节点开始运行一个循环,一直到根节点,为每个索引调用一个函数restoreDown(也称为 maHeapify),通过移动节点将传递的索引恢复到堆的正确位置在 K-ary 堆中以自下而上的方式构建它。
为什么我们从最后一个非叶子节点开始循环?
因为之后的所有节点都是叶节点,它们将简单地满足堆属性,因为它们没有任何子节点,因此已经是 K-ary 最大堆的根。

restoreDown()(或 maxHeapify) :用于维护堆属性。
它运行一个循环,在其中找到所有节点的子节点的最大值,将其与自己的值进行比较,如果最大值(所有子节点的值)>(节点处的值)进行交换。它重复此步骤,直到节点恢复到其在堆中的原始位置。

extractMax() :提取根节点。
k-ary 最大堆在其根中存储最大元素。它返回根节点,将最后一个节点复制到第一个节点,在第一个节点上调用还原,从而保持堆属性。

insert() :向堆中插入一个节点
这可以通过在最后一个位置插入节点并在给定索引上调用 restoreUp() 以将节点恢复到堆中的适当位置来实现。 restoreUp() 迭代地将给定节点与其父节点进行比较,因为在最大堆中,父节点始终大于或等于其子节点,因此仅当其键大于父节点时,节点才与其父节点交换。

综合以上,下面是K元堆的C++实现。

// C++ program to demonstrate all operations of
// k-ary Heap
#include
  
using namespace std;
  
// function to heapify (or restore the max- heap
// property). This is used to build a k-ary heap
// and in extractMin()
// att[] -- Array that stores heap
// len   -- Size of array
// index -- index of element to be restored
//          (or heapified)
void restoreDown(int arr[], int len, int index,
                                        int k)
{
    // child array to store indexes of all
    // the children of given node
    int child[k+1];
  
    while (1)
    {
        // child[i]=-1 if the node is a leaf
        // children (no children)
        for (int i=1; i<=k; i++)
            child[i] = ((k*index + i) < len) ?
                    (k*index + i) : -1;
  
        // max_child stores the maximum child and
        // max_child_index holds its index
        int max_child = -1, max_child_index ;
  
        // loop to find the maximum of all
        // the children of a given node
        for (int i=1; i<=k; i++)
        {
            if (child[i] != -1 &&
                arr[child[i]] > max_child)
            {
                max_child_index = child[i];
                max_child = arr[child[i]];
            }
        }
  
        // leaf node
        if (max_child == -1)
            break;
  
        // swap only if the key of max_child_index
        // is greater than the key of node
        if (arr[index] < arr[max_child_index])
            swap(arr[index], arr[max_child_index]);
  
        index = max_child_index;
    }
}
  
// Restores a given node up in the heap. This is used
// in decreaseKey() and insert()
void restoreUp(int arr[], int index, int k)
{
    // parent stores the index of the parent variable
    // of the node
    int parent = (index-1)/k;
  
    // Loop should only run till root node in case the
    // element inserted is the maximum restore up will
    // send it to the root node
    while (parent>=0)
    {
        if (arr[index] > arr[parent])
        {
            swap(arr[index], arr[parent]);
            index = parent;
            parent = (index -1)/k;
        }
  
        // node has been restored at the correct position
        else
            break;
    }
}
  
// Function to build a heap of arr[0..n-1] and alue of k.
void buildHeap(int arr[], int n, int k)
{
    // Heapify all internal nodes starting from last
    // non-leaf node all the way upto the root node
    // and calling restore down on each
    for (int i= (n-1)/k; i>=0; i--)
        restoreDown(arr, n, i, k);
}
  
// Function to insert a value in a heap. Parameters are
// the array, size of heap, value k and the element to
// be inserted
void insert(int arr[], int* n, int k, int elem)
{
    // Put the new element in the last position
    arr[*n] = elem;
  
    // Increase heap size by 1
    *n = *n+1;
  
    // Call restoreUp on the last index
    restoreUp(arr, *n-1, k);
}
  
// Function that returns the key of root node of
// the heap and then restores the heap property
// of the remaining nodes
int extractMax(int arr[], int* n, int k)
{
    // Stores the key of root node to be returned
    int max = arr[0];
  
    // Copy the last node's key to the root node
    arr[0] = arr[*n-1];
  
    // Decrease heap size by 1
    *n = *n-1;
  
    // Call restoreDown on the root node to restore
    // it to the correct position in the heap
    restoreDown(arr, *n, 0, k);
  
    return max;
}
  
// Driver program
int main()
{
    const int capacity = 100;
    int arr[capacity] = {4, 5, 6, 7, 8, 9, 10};
    int n = 7;
    int k = 3;
  
    buildHeap(arr, n, k);
  
    printf("Built Heap : \n");
    for (int i=0; i

输出

Built Heap : 
10 9 6 7 8 4 5 

Heap after insertion of 3: 
10 9 6 7 8 4 5 3 

Extracted max is 10

Heap after extract max: 
9 8 6 7 3 4 5 

时间复杂度分析

  • 对于 k-ary 堆,有 n 个节点,给定堆的最大高度将为 log k n。因此,restoreUp() 最多运行 log k n 次(因为在每次迭代中,在 restoreUp() 的情况下节点向上移动一级或在 restoreDown 的情况下向下移动一级)。
  • restoreDown() 为 k 个孩子递归调用自身。所以这个函数的时间复杂度是 O(k log k n)。
  • Insert 和 reduceKey() 操作调用 restoreUp() 一次。所以复杂度是 O(log k n)。
  • 由于extractMax() 调用restoreDown() 一次,其复杂度为O(k log k n)
  • 构建堆的时间复杂度为O(n)(分析类似于二叉堆)

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