进阶数据结构——二叉堆

目录

一、二叉堆的核心思想与本质

核心思想:二叉堆是一种完全二叉树,满足堆性质(父节点的值总是大于或小于子节点的值)。

最大堆:父节点的值大于或等于子节点的值。

最小堆:父节点的值小于或等于子节点的值。

本质:通过维护堆性质,实现高效的插入、删除和最值查询操作。

二、二叉堆的实现与操作

1. 存储结构

数组存储:利用完全二叉树的性质,将堆存储在一维数组中。

对于下标为 i 的节点:

父节点:(i - 1) / 2

左子节点:2 * i + 1

右子节点:2 * i + 2

2. 基本操作

插入(Insert)

将新元素插入数组末尾。

从新元素开始向上调整(上浮),直到满足堆性质。

删除堆顶(Extract-Max/Min)

将堆顶元素与数组末尾元素交换。

删除末尾元素。

从堆顶开始向下调整(下沉),直到满足堆性质。

建堆(Heapify)

从最后一个非叶子节点开始,依次向下调整。

三、二叉堆的应用场景

1. 优先队列

实现优先级调度:每次取出优先级最高的元素。

示例:Dijkstra 算法中用于选择最短路径。

2. Top-K 问题

问题描述:从大量数据中找出前 K 个最大或最小的元素。

解法:使用最小堆维护前 K 个最大元素,或使用最大堆维护前 K 个最小元素。

3. 堆排序

步骤

建堆。

依次将堆顶元素与末尾元素交换,并调整堆。

时间复杂度

O(nlogn)。

4. 中位数维护

问题描述:动态维护数据流的中位数。

解法:使用一个最大堆和一个最小堆,分别存储较小的一半和较大的一半。

四、二叉堆的复杂度分析

时间复杂度

插入:

O(logn)

删除堆顶:

O(logn)

建堆:

O(n)(通过数学证明,建堆的摊还复杂度为 O(n))

堆排序:

O(nlogn)

空间复杂度

存储堆:

O(n)

五、二叉堆的变种与高阶应用

1. 二项堆

特点:由多个二项树组成,支持高效的合并操作。

应用:图算法中的优先级队列。

2. 斐波那契堆

特点:支持更高效的插入、合并和减小键值操作。

应用:Dijkstra 算法和 Prim 算法。

3. 左偏堆

特点:支持高效的合并操作,适合需要频繁合并堆的场景。

六、常见陷阱与调试技巧

1. 边界条件处理

空堆检查:在删除堆顶前需判断堆是否为空。

数组越界:在调整堆时注意下标范围。

2. 调试方法

打印堆状态:在每次操作后输出堆的内容。

可视化堆结构:手动画出堆的树形结构,检查堆性质。

七、代码模版(c++)

cpp 复制代码
#include<iostream>
using namespace std;

#define maxn 100001
#define lson(idx)(idx*2+1)
#define rson(idx)(idx*2+2)
#define parent(idx)((idx-1)/2)

template<typename T>
struct Small{
	bool operator()(const T& left, const T& right) const {
		return left < right;
	}
};

template<typename T>
struct Big {
	bool operator()(const T& left, const T& right) const {
		return left > right;
	}
};

template<class T,typename cmp>
class Heap {
private:
	void shiftUp(int curr);
	void shiftDown(int curr);
public:
	Heap(int cap = maxn);
	~Heap();
	void push(const T& data);
	void pop();
	T top() const;
	bool empty() const;
	void clear();
private:
	T* m_data;
	int m_size;
	int m_capacity;
	cmp m_cmp;
};

template<class T, typename cmp>
void Heap<T, cmp>::shiftUp(int curr){
	if (curr == 0)return;
	int par = parent(curr);
	if (m_cmp(m_data[curr], m_data[par])) {
		swap(m_data[curr], m_data[par]);
		shiftUp(par);
	}

}

template<class T, typename cmp>
void Heap<T, cmp>::shiftDown(int curr){
	int lsonId = lson(curr);
	int rsonId = rson(curr);
	int optId = curr;
	if (lsonId < m_size && m_cmp(m_data[lsonId], m_data[optId])) {
		optId = lsonId;
	}
	if (rsonId < m_size && m_cmp(m_data[rsonId], m_data[optId])) {
		optId = rsonId;
	}
	if (curr != optId) {
		swap(m_data[optId], m_data[curr]);
		shiftDown(optId);
	}
}

template<class T, typename cmp>
Heap<T, cmp>::Heap(int cap){
	m_data = new T[cap];
	m_capacity = cap;
	m_size = 0;
}

template<class T, typename cmp>
Heap<T, cmp>::~Heap(){
	delete[] m_data;
	m_data = NULL;
}

template<class T, typename cmp>
void Heap<T, cmp>::push(const T& data){
	m_data[m_size++] = data;
	shiftUp(m_size - 1);
}

template<class T, typename cmp>
void Heap<T, cmp>::pop(){
	swap(m_data[0], m_data[m_size - 1]);
	m_size--;
	shiftDown(0);
}

template<class T, typename cmp>
T Heap<T, cmp>::top() const{
	return m_data[0];
}

template<class T, typename cmp>
bool Heap<T, cmp>::empty() const{
	return m_size == 0;
}

template<class T, typename cmp>
void Heap<T, cmp>::clear(){
	m_size = 0;
}

int main() {
	Heap<int, Big<int>> h;
	for (int i = 0; i < 5; i++) {
		int x = rand() % 30;
		int y = rand() % 30;
		h.push(x), h.push(y);
		cout << "塞入两个数:" << x << ' ' << y << endl;
		cout << "目前最大的数是" << h.top() << endl;
		h.pop();
		cout << "弹出最大的那个数" << endl;
	}
	return 0;
}

八、经典例题

小蓝的最大集合

cpp 复制代码
#include<iostream>
using namespace std;

#define maxn 200001
#define lson(idx)(idx*2+1)
#define rson(idx)(idx*2+2)
#define parent(idx)((idx-1)/2)

template<typename T>
struct Small {
    bool operator()(const T& left, const T& right) const {
        return left < right;
    }
};

template<typename T>
struct Big {
    bool operator()(const T& left, const T& right) const {
        return left > right;
    }
};

template<class T,typename cmp>
class Heap {
private:
    void shiftUp(int curr);
    void shiftDown(int curr);

    T* m_data;
    int m_size;
    int m_capacity;
    cmp m_cmp;
public:
    Heap(int cap=maxn);
    ~Heap();
    void push(const T& data);
    void pop();
    T top() const;
    void clear();
    bool empty() const;
    int size();
    T* getData();
};

template<class T, typename cmp>
void Heap<T, cmp>::shiftUp(int curr){
    if (curr == 0)return;
    int par = parent(curr);
    if (m_cmp(m_data[curr], m_data[par])) {
        swap(m_data[par], m_data[curr]);
        shiftUp(par);
    }
}

template<class T, typename cmp>
void Heap<T, cmp>::shiftDown(int curr) {
    int lsonId = lson(curr);
    int rsonId = rson(curr);
    int optId = curr;
    if (lsonId < m_size && m_cmp(m_data[lsonId], m_data[optId])) {
        optId = lsonId;
    }
    if (rsonId < m_size && m_cmp(m_data[rsonId], m_data[optId])) {
        optId = rsonId;
    }
    if (curr != optId) {
        swap(m_data[curr], m_data[optId]);
        shiftDown(optId);
    }
}

template<class T, typename cmp>
Heap<T, cmp>::Heap(int cap) {
    m_size = 0;
    m_data = new T[cap];
    m_capacity = cap;
}

template<class T, typename cmp>
Heap<T, cmp>::~Heap() {
    m_size = 0;
    delete[] m_data;
    m_data = NULL;
}

template<class T, typename cmp>
void Heap<T, cmp>::push(const T& data) {
    m_data[m_size++] = data;
    shiftUp(m_size - 1);
}

template<class T, typename cmp>
void Heap<T, cmp>::pop() {
    swap(m_data[0], m_data[m_size - 1]);
    m_size--;
    shiftDown(0);
}

template<class T, typename cmp>
T Heap<T, cmp>::top() const {
    return m_data[0];
}

template<class T, typename cmp>
void Heap<T, cmp>::clear() {
    m_size = 0;
}

template<class T, typename cmp>
bool Heap<T, cmp>::empty() const {
    return m_size == 0;
}

template<class T, typename cmp>
int Heap<T, cmp>::size() {
    return m_size;
}

template<class T, typename cmp>
T* Heap<T, cmp>::getData() {
    return m_data;
}

int main() {
    Heap<int, Big<int>> h;
    int n, k;
    cin >> n >> k;
    h.push(k);
    while (n--) {
        int opt;
        cin >> opt;
        if (opt == 1) {
            int x;
            cin >> x;
            h.push(x);
        }
        else if (opt == 2) {
            int x;
            cin >> x;
            int* data = h.getData();
            for (int i = 0; i < h.size(); i++) {
                data[i] += x;
            }
        }
        else if (opt == 3) {
            int x;
            cin >> x;
            int* data = h.getData();
            for (int i = 0; i < h.size(); i++) {
                data[i] -= x;
            }
        }
        else {
            cout << h.top() << endl;
            h.pop();
        }
    }
    cout << endl;

    return 0;
}

还有一种方法

cpp 复制代码
Heap<int, Big<int>> h;
int n, k;
cin >> n >> k;
h.push(k);
int sum = 0;		//用于记录数字串中被加减后的值
while (n--) {
	int opt;
	cin >> opt;
	if (opt == 4) {
		cout << h.top() + sum << endl;
		h.pop();
	}
	else {
		int x;
		cin >> x;
		if (opt == 1) {
			h.push(x - sum);
		}
		else if (opt == 2) {
			sum += x;
		}
		else if (opt == 3) {
			sum -= x;
		}
	}
}
cout << endl;

第k大的数

cpp 复制代码
#include<iostream>
using namespace std;

#define maxn 200001
#define lson(idx)(idx*2+1)
#define rson(idx)(idx*2+2)
#define parent(idx)((idx-1)/2)

template<typename T>
struct Small {
	bool operator()(const T& left, const T& right) const {
		return left < right;
	}
};

template<typename T>
struct Big {
	bool operator()(const T& left, const T& right) const {
		return left > right;
	}
};

template<class T,typename cmp>
class Heap {
private:
	void shiftUp(int curr);
	void shiftDown(int curr);

	T* m_data;
	int m_size;
	int m_capacity;
	cmp m_cmp;
public:
	Heap(int cap=maxn);
	~Heap();
	void push(const T& data);
	void pop();
	T top() const;
	void clear();
	bool empty() const;
	int size();
	T* getData();
};

template<class T, typename cmp>
void Heap<T, cmp>::shiftUp(int curr){
	if (curr == 0)return;
	int par = parent(curr);
	if (m_cmp(m_data[curr], m_data[par])) {
		swap(m_data[par], m_data[curr]);
		shiftUp(par);
	}
}

template<class T, typename cmp>
void Heap<T, cmp>::shiftDown(int curr) {
	int lsonId = lson(curr);
	int rsonId = rson(curr);
	int optId = curr;
	if (lsonId < m_size && m_cmp(m_data[lsonId], m_data[optId])) {
		optId = lsonId;
	}
	if (rsonId < m_size && m_cmp(m_data[rsonId], m_data[optId])) {
		optId = rsonId;
	}
	if (curr != optId) {
		swap(m_data[curr], m_data[optId]);
		shiftDown(optId);
	}
}

template<class T, typename cmp>
Heap<T, cmp>::Heap(int cap) {
	m_size = 0;
	m_data = new T[cap];
	m_capacity = cap;
}

template<class T, typename cmp>
Heap<T, cmp>::~Heap() {
	m_size = 0;
	delete[] m_data;
	m_data = NULL;
}

template<class T, typename cmp>
void Heap<T, cmp>::push(const T& data) {
	m_data[m_size++] = data;
	shiftUp(m_size - 1);
}

template<class T, typename cmp>
void Heap<T, cmp>::pop() {
	swap(m_data[0], m_data[m_size - 1]);
	m_size--;
	shiftDown(0);
}

template<class T, typename cmp>
T Heap<T, cmp>::top() const {
	return m_data[0];
}

template<class T, typename cmp>
void Heap<T, cmp>::clear() {
	m_size = 0;
}

template<class T, typename cmp>
bool Heap<T, cmp>::empty() const {
	return m_size == 0;
}

template<class T, typename cmp>
int Heap<T, cmp>::size() {
	return m_size;
}

template<class T, typename cmp>
T* Heap<T, cmp>::getData() {
	return m_data;
}

int main() {
	Heap<int, Small<int>> h;
	int n, k;
	cin >> n >> k;
	for (int i = 0; i < n; i++) {
		int a;
		cin >> a;
		h.push(a);
	}
	while (k--) {
		int x;
		cin >> x;
		if (x >= h.top()) {
			h.pop();
			h.push(x);
		}
		cout << h.top() << ' ';
	}
	
	cout << endl;

	return 0;
}

一道简单的取模问题

cpp 复制代码
#include<iostream>
using namespace std;

#define maxn 200001
#define lson(idx)(idx*2+1)
#define rson(idx)(idx*2+2)
#define parent(idx)((idx-1)/2)

template<typename T>
struct Small {
	bool operator()(const T& left, const T& right) const {
		return left < right;
	}
};

template<typename T>
struct Big {
	bool operator()(const T& left, const T& right) const {
		return left > right;
	}
};

template<class T,typename cmp>
class Heap {
private:
	void shiftUp(int curr);
	void shiftDown(int curr);

	T* m_data;
	int m_size;
	int m_capacity;
	cmp m_cmp;
public:
	Heap(int cap=maxn);
	~Heap();
	void push(const T& data);
	void pop();
	T top() const;
	void clear();
	bool empty() const;
	int size();
	T* getData();
};

template<class T, typename cmp>
void Heap<T, cmp>::shiftUp(int curr){
	if (curr == 0)return;
	int par = parent(curr);
	if (m_cmp(m_data[curr], m_data[par])) {
		swap(m_data[par], m_data[curr]);
		shiftUp(par);
	}
}

template<class T, typename cmp>
void Heap<T, cmp>::shiftDown(int curr) {
	int lsonId = lson(curr);
	int rsonId = rson(curr);
	int optId = curr;
	if (lsonId < m_size && m_cmp(m_data[lsonId], m_data[optId])) {
		optId = lsonId;
	}
	if (rsonId < m_size && m_cmp(m_data[rsonId], m_data[optId])) {
		optId = rsonId;
	}
	if (curr != optId) {
		swap(m_data[curr], m_data[optId]);
		shiftDown(optId);
	}
}

template<class T, typename cmp>
Heap<T, cmp>::Heap(int cap) {
	m_size = 0;
	m_data = new T[cap];
	m_capacity = cap;
}

template<class T, typename cmp>
Heap<T, cmp>::~Heap() {
	m_size = 0;
	delete[] m_data;
	m_data = NULL;
}

template<class T, typename cmp>
void Heap<T, cmp>::push(const T& data) {
	m_data[m_size++] = data;
	shiftUp(m_size - 1);
}

template<class T, typename cmp>
void Heap<T, cmp>::pop() {
	swap(m_data[0], m_data[m_size - 1]);
	m_size--;
	shiftDown(0);
}

template<class T, typename cmp>
T Heap<T, cmp>::top() const {
	return m_data[0];
}

template<class T, typename cmp>
void Heap<T, cmp>::clear() {
	m_size = 0;
}

template<class T, typename cmp>
bool Heap<T, cmp>::empty() const {
	return m_size == 0;
}

template<class T, typename cmp>
int Heap<T, cmp>::size() {
	return m_size;
}

template<class T, typename cmp>
T* Heap<T, cmp>::getData() {
	return m_data;
}

int main() {
	Heap<int, Big<int>> h;
	int n, q;
	cin >> n >> q;
	long long sum = 0;
	for (int i = 0; i < n; i++) {
		int a;
		cin >> a;
		sum += a;
		h.push(a);
	}
	while (q--) {
		int x;
		cin >> x;
		while (!h.empty()) {
			if (h.top() >= x) {
				sum -= h.top();
				int y = h.top() % x;
				sum += y;
				h.pop();
				h.push(y);
			}
			else break;
		}
		cout << sum << ' ';
		
	}
	
	cout << endl;

	return 0;
}

九、总结与学习建议

1. 核心总结

二叉堆的核心是利用完全二叉树和堆性质实现高效的最值操作。

高阶问题的突破口通常在于堆的变种(如二项堆、斐波那契堆)或堆的应用场景(如优先队列、Top-K 问题)。

2. 学习建议

分类刷题:按问题类型集中练习(如优先队列、堆排序、Top-K 问题)。

对比其他数据结构:理解二叉堆与平衡二叉树的异同。

手写模拟过程:在纸上画出堆的调整过程,加深直观理解。

希望大家可以一键三连,后续我们一起学习,谢谢大家!!!

相关推荐
砂糖はいかがですか。28 分钟前
用数组模拟循环队列
数据结构·算法
旧物有情34 分钟前
数组模拟邻接表 #图论
数据结构·图论
宋发元2 小时前
Java 中 LinkedList 的底层数据结构及相关分析
java·开发语言·数据结构
开压路机2 小时前
list及其模拟实现
数据结构·list
曦月逸霜2 小时前
第十三次CCF-CSP认证(含C++源码)
数据结构·c++·算法·ccp-csp
W流沙W2 小时前
算法与数据结构(初识)
数据结构·算法
Dust-Chasing2 小时前
数据结构之链表(双链表)
数据结构·链表
小猪写代码3 小时前
论冒泡和快排
数据结构·算法·排序算法
愚戏师4 小时前
C++: vector
数据结构·c++·算法
想成为高手4994 小时前
C++红黑树的深入解析:从理论到实践
数据结构·c++·算法