typedef struct HeapStruct *PriorityQueue;
int MinPQSize = 2;
struct HeapStruct
{
int Capacity;
int Size;
int *Elements;
};
首先,定义一个结构体HeapStruct,表示堆的数据结构,其中包括堆的容量(Capacity)、当前大小(Size)以及一个整型数组Elements,用于存储堆的元素。然后,通过typedef定义了一个指向HeapStruct的指针类型PriorityQueue,用于引用和操作堆,并且规定了堆的最小元素个数MinPQSize是2。
PriorityQueue Initialize(int MaxElements)
{
PriorityQueue H;
if(MaxElements < MinPQSize)
{
cout << "Priority queue size is too small" << endl;
return H;
}
H = new(HeapStruct);
if(H == nullptr)
{
cout << "Out of space" << endl;
return H;
}
H->Elements = new int[MaxElements+1];
if(H->Elements == nullptr)
{
cout << "Out of space" << endl;
return H;
}
H->Capacity = MaxElements;
H->Size = 0;
H->Elements[0] = 0;
return H;
}
函数Initialize用于初始化一个最小堆优先队列。函数接受一个整型参数MaxElements,表示堆的最大容量。在函数内部,首先检查最小堆的容量是否小于最小要求,如果是,则输出错误信息并返回空指针。然后,分配堆结构体和元素数组的内存空间,并进行相应的初始化赋值操作。最后,返回指向堆的指针。
int IsFull(PriorityQueue H)
{
return H->Size == H->Capacity;
}
函数IsFull用于判断堆是否已满。如果堆的大小等于容量,则返回 1;否则返回 0。
void Insert(int x, PriorityQueue H)
{
int i;
if(IsFull(H))
{
cout << "Priority queue is full" << endl;
}
for(i = ++H->Size; H->Elements[i / 2] > x; i /= 2)
{
H->Elements[i] = H->Elements[i / 2];
}
H->Elements[i] = x;
}
当我们向最小堆中插入一个新元素时,需要确保该元素被正确放置在合适的位置,以满足最小堆的性质。最小堆的性质是父节点的值小于或等于其子节点的值。
在上述代码中,通过上滤操作实现了将新元素插入到最小堆中的过程。下面是更详细的解释:
-
i = ++H->Size
:首先将堆的大小加1,并将其赋值给变量i
。这是因为在插入元素之前,堆的大小会增加,i
表示新元素在数组中的索引。 -
H->Elements[i / 2] > x
:该条件检查当前节点的父节点是否比要插入的元素x
大。如果是,则需要将当前节点的父节点下移,为新元素腾出位置。 -
i /= 2
:将当前节点的索引除以2,即向上移动到父节点的位置。这是因为在最小堆中,父节点的索引是当前节点索引的一半。 -
H->Elements[i] = H->Elements[i / 2]
:将当前节点的父节点的值复制给当前节点。这样,父节点的值就被下移到了当前节点的位置。 -
继续循环执行步骤2和步骤3,直到插入的元素
x
找到了合适的位置,或者到达堆的根节点。在每一次循环中,我们比较当前节点的父节点与要插入的元素x
的大小关系,如果父节点较大,则将父节点的值下移。 -
H->Elements[i] = x
:最后,将要插入的元素x
放置在找到的合适位置上,完成上滤操作。
通过上述上滤过程,新元素会逐步上移,直到找到合适的位置,以保持最小堆的性质。最小堆的根节点即为最小的元素。
上滤操作的时间复杂度为 O(log n),其中 n 是堆的大小,因为在最坏情况下,新元素可能需要上移到堆的顶部。
这样的上滤操作保证了在插入新元素后,最小堆仍然保持了其性质。这样我们可以高效地插入和删除最小元素,以及查找最小元素。
int DeleteMin(PriorityQueue H)
{
int i, child;
if(H->Size == 0)
{
cout << "Priority queue is empty" << endl;
return -1;
}
int minElement = H->Elements[1];
int lastElement = H->Elements[H->Size--];
for(i = 1; i*2 <= H->Size; i = child)
{
child = i*2;
if(child != H->Size && H->Elements[child + 1] < H->Elements[child])
{
child++;
}
if(lastElement > H->Elements[child])
{
H->Elements[i] = H->Elements[child];
}
else
{
break;
}
}
H->Elements[i] = lastElement;
return minElement;
}
函数DeleteMin实现了从小根堆中删除最小元素的功能,并返回删除的最小的元素的值。
-
if(H->Size == 0)
:首先检查堆是否为空。如果堆的大小为0,表示堆为空,输出错误信息并返回一个特定的错误值(-1)。 -
int minElement = H->Elements[1]
:将堆的根节点的值(最小元素)保存到变量minElement
中。 -
int lastElement = H->Elements[H->Size--]
:将堆的最后一个元素的值保存到变量lastElement
中,并将堆的大小减1。这样做是为了方便后面的下滤操作。 -
for(i = 1; i*2 <= H->Size; i = child)
:开始进行下滤操作。循环条件是当前节点的左子节点的索引不超过堆的大小。 -
child = i*2
:将变量child
设置为当前节点的左子节点的索引。 -
if(child != H->Size && H->Elements[child + 1] < H->Elements[child])
:检查当前节点是否有右子节点,并且右子节点的值是否比左子节点的值小。如果是,则将child
设置为右子节点的索引。 -
if(lastElement > H->Elements[child])
:如果要删除的元素(lastElement
)比子节点的值大,说明需要将子节点的值上移。 -
H->Elements[i] = H->Elements[child]
:将子节点的值复制给当前节点,即进行上移操作。 -
else
:如果要删除的元素比子节点的值小或相等,则退出循环,结束下滤操作。 -
H->Elements[i] = lastElement
:将要删除的元素放置在找到的合适位置上,完成下滤操作。 -
return minElement
:返回被删除的最小元素的值。
通过上述下滤过程,堆的最后一个元素会逐步下移,直到找到合适的位置,以保持最小堆的性质。这样我们就删除了最小元素,并保持了最小堆的性质。
下滤操作的时间复杂度为 O(log n),其中 n 是堆的大小。在每次循环中,我们比较当前节点与其子节点的值,最坏情况下,下滤操作需要将元素下移到堆的叶子节点。
int FindMin(PriorityQueue H)
{
if(H->Size == 0)
{
cout << "Priority queue is empty" << endl;
return -1;
}
return H->Elements[1];
}
函数FindMin用于查找堆中的最小元素,并返回它的值。如果堆为空,则输出错误信息并返回一个特定的错误值。函数直接返回堆的根节点的值。