C++中堆的模拟实现详解
在数据结构中,堆(Heap)是一种特殊的完全二叉树,常用于实现优先队列。堆通常分为最大堆和最小堆两种,它们的主要区别在于节点的值与其子节点的关系:最大堆的父节点值大于或等于子节点的值,最小堆的父节点值小于或等于子节点的值。堆的核心操作包括插入、删除最大(最小)元素、堆化等。
本篇文章将通过C++来实现堆的模拟,讲解堆的基本概念及其常见操作。
一、堆的基本结构
堆是一个完全二叉树,可以通过数组来高效地表示。假设数组的下标从0开始,对于任意元素 arr[i]
,其左子节点的下标为 2*i + 1
,右子节点的下标为 2*i + 2
,父节点的下标为 (i-1)/2
。
例如,以下是一个最大堆的例子:
10
/ \
8 5
/ \ / \
4 3 2 1
这个堆在数组中的表示是:[10, 8, 5, 4, 3, 2, 1]
。
二、堆的常见操作
插入元素(Insert)
- 在堆中插入一个新元素时,需要将其添加到堆的末尾,然后通过“上浮”操作将其调整到正确的位置。
- 上浮操作的基本思想是比较当前节点与父节点的大小,如果当前节点较大(在最大堆中),则交换它们,直到满足堆的性质。
删除最大(最小)元素(Delete)
- 删除堆顶元素后,通常将堆的最后一个元素移到堆顶,并通过“下沉”操作调整堆结构。
- 下沉操作的基本思想是将当前节点与其子节点中较大的一个进行交换,直到堆的性质得到恢复。
堆化(Heapify)
- 堆化是将一个无序的数组转化为一个堆的过程。通过对每个非叶子节点执行下沉操作来完成。
三、C++中的堆模拟实现
下面是一个简单的最大堆的C++实现,包含了插入、删除、堆化等基本操作。
#include <iostream>
#include <vector>
using namespace std;
class MaxHeap {
public:
MaxHeap() {}
// 插入操作
void insert(int val) {
heap.push_back(val); // 将新元素添加到末尾
int index = heap.size() - 1; // 获取新元素的下标
// 上浮操作
while (index > 0) {
int parent = (index - 1) / 2;
if (heap[index] > heap[parent]) {
swap(heap[index], heap[parent]);
index = parent;
} else {
break;
}
}
}
// 删除堆顶元素
void remove() {
if (heap.size() == 0) return;
swap(heap[0], heap[heap.size() - 1]); // 将最后一个元素交换到堆顶
heap.pop_back(); // 删除堆顶元素
heapify(0); // 堆化操作
}
// 堆化操作(下沉)
void heapify(int index) {
int left = 2 * index + 1;
int right = 2 * index + 2;
int largest = index;
if (left < heap.size() && heap[left] > heap[largest]) {
largest = left;
}
if (right < heap.size() && heap[right] > heap[largest]) {
largest = right;
}
if (largest != index) {
swap(heap[index], heap[largest]);
heapify(largest); // 递归下沉
}
}
// 打印堆
void print() {
for (int val : heap) {
cout << val << " ";
}
cout << endl;
}
private:
vector<int> heap; // 使用vector存储堆
};
int main() {
MaxHeap maxHeap;
maxHeap.insert(10);
maxHeap.insert(20);
maxHeap.insert(5);
maxHeap.insert(8);
maxHeap.insert(30);
cout << "堆的内容: ";
maxHeap.print(); // 输出堆的内容
maxHeap.remove();
cout << "删除堆顶元素后的堆: ";
maxHeap.print(); // 删除堆顶元素后的堆
return 0;
}
四、代码讲解
插入操作(insert)
- 使用
push_back
将元素添加到堆的末尾。 - 然后通过
while
循环进行“上浮”操作,直到堆的性质得到恢复。
- 使用
删除操作(remove)
- 使用
swap
将堆顶元素与最后一个元素交换。 - 然后使用
pop_back
移除最后一个元素,并调用heapify
函数进行堆化。
- 使用
堆化操作(heapify)
- 堆化操作从一个节点开始,检查该节点的左子节点和右子节点,找到最大的子节点并与当前节点交换。递归地对交换后的子树继续进行堆化。
打印操作(print)
- 遍历堆数组,打印堆中的所有元素。
五、堆的时间复杂度分析
- 插入操作:最坏情况下,需要上浮到根节点,因此时间复杂度为O(log n)。
- 删除堆顶元素:最坏情况下,需要下沉到叶子节点,因此时间复杂度为O(log n)。
- 堆化操作:在构建堆时,堆化每个节点的时间复杂度为O(log n),因此整个堆化操作的时间复杂度为O(n)。
- 查找最大元素:最大堆的堆顶元素就是最大元素,因此查找最大元素的时间复杂度为O(1)。
六、总结
堆是一种非常重要的完全二叉树数据结构,广泛应用于实现优先队列、排序算法(堆排序)等场景。通过C++模拟堆的实现,可以帮助我们更好地理解堆的基本操作和时间复杂度。最大堆和最小堆的实现方式相似,区别在于插入和删除元素时的比较操作。
通过这个实现,我们掌握了堆的基础操作,并且能够在实际项目中灵活使用堆解决各种问题。
工作流程图
graph TD;
A[插入元素] --> B[将元素添加到堆末尾];
B --> C[上浮操作:比较并交换];
C --> D[堆的性质恢复];
A --> E[删除堆顶元素];
E --> F[将堆顶元素与最后一个元素交换];
F --> G[下沉操作:调整堆结构];
G --> D[堆的性质恢复];
通过这一流程图,我们可以清楚地看到堆的操作步骤及其相互之间的关系,帮助进一步理解堆的实现与应用。