最小堆二叉树
最小堆二叉树是二叉树,其中根节点在树中具有最小密钥。
上面的定义对于树中的所有子树都适用。
这称为"最小堆"属性。
除了最后两层以外,几乎每个节点都必须有两个子节点。
也就是说,除了最后两层外,这几乎是完整的二叉树。
由于上面的两个属性成立,因此下面的树是最小堆二叉树的示例。
最小堆树
既然我们已经介绍了最小堆树,那么让我们看一下如何表示它。
最小堆树的表示
最小堆二叉树通常表示为数组,并根据以下格式进行索引:
Current Node | arr[i] |
Parent Node | arr[(i-1)/2] |
Left Child | arr[(2*i) + 1] |
Right Child | arr[(2*i )+ 2] |
整个树的根在arr [0]
。
我们将使用下图所示的索引。
您可以在此处找到与上表相匹配的模式,这一点并不难。
最小堆二叉树索引
该索引遵循二进制树的级别顺序遍历,因此二进制堆数组是使用级别顺序遍历的二进制树。
最小堆数组
上图显示了最小堆树的数组表示形式。
现在,我们已经涵盖了这些概念,让我们继续使用C来实现它!
实施最小堆树
我们将使用数组表示法来构建树。
让我们开始编写Min Heap的结构。
typedef struct MinHeap MinHeap; struct MinHeap { int* arr; //Current Size of the Heap int size; //Maximum capacity of the heap int capacity; };
我们将有一个元素数组和一个大小,该大小会随着元素的插入或者删除而更新。
该阵列还具有容量,该容量指示阵列的最大大小。
我们需要编写一些函数来表示我们正在代表最小堆树,例如查找父级和子级。
int parent(int i) { //Get the index of the parent return (i - 1)/2; } int left_child(int i) { return (2*i + 1); } int right_child(int i) { return (2*i + 2); } int get_min(MinHeap* heap) { //Return the root node element, //since that's the minimum, by the min-heap //property return heap->arr[0]; }
我们将编写函数来初始化和释放堆。
MinHeap* init_minheap(int capacity) { MinHeap* minheap = (MinHeap*) calloc (1, sizeof(MinHeap)); minheap->arr = (int*) calloc (capacity, sizeof(int)); minheap->capacity = capacity; minheap->size = 0; return minheap; } void free_minheap(MinHeap* heap) { if (!heap) return; free(heap->arr); free(heap); }
讲完这些后,我们现在继续介绍如何插入元素!
插入最小堆
插入算法很简单。
这会将元素插入树中。
分解算法:
首先,始终将其插入树的底部。
插入元素的初始位置在最后一层。现在,我们将需要更新此元素的位置,以便满足min-heap属性。
由于每个子树的根节点必须最小,因此请检查其直接父级的子树。
如果父级大于此插入的元素,则需要通过交换来更新其位置。
但是我们还没有完成,因为min-heap属性可能会违反更新后的节点的子树!
我们需要继续交换,直到到达根节点为止。
要了解此过程,请举一个例子。
考虑下面的树,它只有一个元素。
最小堆一元素
让我们插入元素40。
由于只有一个元素,所以它插入到底部,并且我们观察到min-heap属性满足,因为10 <40。
因此不需要交换。
最小堆两个元素
接下来,我们将插入50。
执行类似的步骤。
最小堆三要素
接下来,我们将插入5。
因此,现在,我们首先在树的底部插入索引3。
最小堆状态
对于子树1-3,因此对于整个树,都违反了min heap属性。
因此,我们必须继续与父交换,直到到达根为止。
交换
因此,我们需要再进行一次交换,因为同样,对于以节点0为根的子树,min-heap属性被违反了。
交换后最小堆
好的。
现在我们已经可视化了,让我们写下来吧!
MinHeap* insert_minheap(MinHeap* heap, int element) { //Inserts an element to the min heap //We first add it to the bottom (last level) //of the tree, and keep swapping with it's parent //if it is lesser than it. We keep doing that until //we reach the root node. So, we will have inserted the //element in it's proper position to preserve the min heap property if (heap->size == heap->capacity) { fprintf(stderr, "Cannot insert %d. Heap is already full!\n", element); return heap; } //We can add it. Increase the size and add it to the end heap->size++; heap->arr[heap->size - 1] = element; //Keep swapping until we reach the root int curr = heap->size - 1; //As long as you aren't in the root node, and while the //parent of the last element is greater than it while (curr > 0 && heap->arr[parent(curr)] > heap->arr[curr]) { //Swap int temp = heap->arr[parent(curr)]; heap->arr[parent(curr)] = heap->arr[curr]; heap->arr[curr] = temp; //Update the current index of element curr = parent(curr); } return heap; }
现在,我们将实现删除方法。
从最小堆中删除
在考虑删除任何索引元素之前,由于min-heap与根紧密相关,因此我们将首先考虑删除根。
要删除最小元素(即根),我们将执行以下操作:
将根更新为数组(树)的最后一个元素
现在,我们将删除底部的最后一个元素。
这类似于最后交换和删除!只是因为我们不再关心根值,我们才更新它。再次出现的问题是,我们需要维护min-heap属性。
因此,我们必须确保整个树都保留此属性。
我们将使用一个名为" heapify()"的函数为我们完成此任务。
因此,我们知道删除方法也将在完成heapify()
方法之后完成。
MinHeap* delete_minimum(MinHeap* heap) { //Deletes the minimum element, at the root if (!heap || heap->size == 0) return heap; int size = heap->size; int last_element = heap->arr[size-1]; //Update root value with the last element heap->arr[0] = last_element; //Now remove the last element, by decreasing the size heap->size--; size--; //We need to call heapify(), to maintain the min-heap //property heap = heapify(heap, 0); return heap; }
heapify()过程
该函数通过交换其直接子树的最小元素来获取元素索引" index",并维护最小堆属性。
生成的树将满足min-heap属性。
这涉及找到子树的最小元素,并与当前元素进行交换。
此后,我们仍然需要确保整个树都满足此要求。
因此,我们需要递归地调用最小元素上的过程,直到到达根为止!
MinHeap* heapify(MinHeap* heap, int index) { //Rearranges the heap as to maintain //the min-heap property if (heap->size <= 1) return heap; int left = left_child(index); int right = right_child(index); //Variable to get the smallest element of the subtree //of an element an index int smallest = index; //If the left child is smaller than this element, it is //the smallest if (left < heap->size && heap->arr[left] < heap->arr[index]) smallest = left; //Similarly for the right, but we are updating the smallest element //so that it will definitely give the least element of the subtree if (right < heap->size && heap->arr[right] < heap->arr[smallest]) smallest = right; //Now if the current element is not the smallest, //swap with the current element. The min heap property //is now satisfied for this subtree. We now need to //recursively keep doing this until we reach the root node, //the point at which there will be no change! if (smallest != index) { int temp = heap->arr[index]; heap->arr[index] = heap->arr[smallest]; heap->arr[smallest] = temp; heap = heapify(heap, smallest); } return heap; }
现在我们可以扩展这个delete_minimum()
函数,以删除任何元素。
删除任意元素
这仅涉及将所需元素设置为可能的最小值,即get_min()-1,因为它必须小于当前的最小值。
现在,我们将继续交换直到更新位置,以使新的根成为该元素。
现在,我们回到原来的delete_minimum()
函数!我们可以简单地删除新的根目录!
这样,我们的整个删除过程将如下所示:
MinHeap* delete_element(MinHeap* heap, int index) { //Deletes an element, indexed by index //Ensure that it's lesser than the current root heap->arr[index] = get_min(heap) - 1; //Now keep swapping, until we update the tree int curr = index; while (curr > 0 && heap->arr[parent(curr)] > heap->arr[curr]) { int temp = heap->arr[parent(curr)]; heap->arr[parent(curr)] = heap->arr[curr]; heap->arr[curr] = temp; curr = parent(curr); } //Now simply delete the minimum element heap = delete_minimum(heap); return heap; }
!我们终于完成了。
现在,我将向您展示整个代码以及" print()"函数,以可视化该树。
完整代码
#include <stdio.h> #include <stdlib.h> typedef struct MinHeap MinHeap; struct MinHeap { int* arr; //Current Size of the Heap int size; //Maximum capacity of the heap int capacity; }; int parent(int i) { //Get the index of the parent return (i - 1)/2; } int left_child(int i) { return (2*i + 1); } int right_child(int i) { return (2*i + 2); } int get_min(MinHeap* heap) { //Return the root node element, //since that's the minimum return heap->arr[0]; } MinHeap* init_minheap(int capacity) { MinHeap* minheap = (MinHeap*) calloc (1, sizeof(MinHeap)); minheap->arr = (int*) calloc (capacity, sizeof(int)); minheap->capacity = capacity; minheap->size = 0; return minheap; } MinHeap* insert_minheap(MinHeap* heap, int element) { //Inserts an element to the min heap //We first add it to the bottom (last level) //of the tree, and keep swapping with it's parent //if it is lesser than it. We keep doing that until //we reach the root node. So, we will have inserted the //element in it's proper position to preserve the min heap property if (heap->size == heap->capacity) { fprintf(stderr, "Cannot insert %d. Heap is already full!\n", element); return heap; } //We can add it. Increase the size and add it to the end heap->size++; heap->arr[heap->size - 1] = element; //Keep swapping until we reach the root int curr = heap->size - 1; //As long as you aren't in the root node, and while the //parent of the last element is greater than it while (curr > 0 && heap->arr[parent(curr)] > heap->arr[curr]) { //Swap int temp = heap->arr[parent(curr)]; heap->arr[parent(curr)] = heap->arr[curr]; heap->arr[curr] = temp; //Update the current index of element curr = parent(curr); } return heap; } MinHeap* heapify(MinHeap* heap, int index) { //Rearranges the heap as to maintain //the min-heap property if (heap->size <= 1) return heap; int left = left_child(index); int right = right_child(index); //Variable to get the smallest element of the subtree //of an element an index int smallest = index; //If the left child is smaller than this element, it is //the smallest if (left < heap->size && heap->arr[left] < heap->arr[index]) smallest = left; //Similarly for the right, but we are updating the smallest element //so that it will definitely give the least element of the subtree if (right < heap->size && heap->arr[right] < heap->arr[smallest]) smallest = right; //Now if the current element is not the smallest, //swap with the current element. The min heap property //is now satisfied for this subtree. We now need to //recursively keep doing this until we reach the root node, //the point at which there will be no change! if (smallest != index) { int temp = heap->arr[index]; heap->arr[index] = heap->arr[smallest]; heap->arr[smallest] = temp; heap = heapify(heap, smallest); } return heap; } MinHeap* delete_minimum(MinHeap* heap) { //Deletes the minimum element, at the root if (!heap || heap->size == 0) return heap; int size = heap->size; int last_element = heap->arr[size-1]; //Update root value with the last element heap->arr[0] = last_element; //Now remove the last element, by decreasing the size heap->size--; size--; //We need to call heapify(), to maintain the min-heap //property heap = heapify(heap, 0); return heap; } MinHeap* delete_element(MinHeap* heap, int index) { //Deletes an element, indexed by index //Ensure that it's lesser than the current root heap->arr[index] = get_min(heap) - 1; //Now keep swapping, until we update the tree int curr = index; while (curr > 0 && heap->arr[parent(curr)] > heap->arr[curr]) { int temp = heap->arr[parent(curr)]; heap->arr[parent(curr)] = heap->arr[curr]; heap->arr[curr] = temp; curr = parent(curr); } //Now simply delete the minimum element heap = delete_minimum(heap); return heap; } void print_heap(MinHeap* heap) { //Simply print the array. This is an //inorder traversal of the tree printf("Min Heap:\n"); for (int i=0; i<heap->size; i++) { printf("%d -> ", heap->arr[i]); } printf("\n"); } void free_minheap(MinHeap* heap) { if (!heap) return; free(heap->arr); free(heap); } int main() { //Capacity of 10 elements MinHeap* heap = init_minheap(10); insert_minheap(heap, 40); insert_minheap(heap, 50); insert_minheap(heap, 5); print_heap(heap); //Delete the heap->arr[1] (50) delete_element(heap, 1); print_heap(heap); free_minheap(heap); return 0; }
输出
Min Heap: 5 -> 50 -> 40 -> Min Heap: 5 -> 40 ->
实施的时间复杂度
上述过程的时间复杂度如下:
Function | Time Complexity |
get_min() | O(1) |
insert_minheap() | O(logN) |
delete_minimum() | Same as insert – O(logN) |
heapify() | O(logN) |
delete_element() | O(logN) |