先决条件 – 二叉堆
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 现场工作专业课程和学生竞争性编程现场课程。