进阶数据结构——二叉堆

目录

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

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

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

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

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

二、二叉堆的实现与操作

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 问题)。

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

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

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

相关推荐
我爱996!43 分钟前
LinkedList与链表
数据结构·链表
yb0os11 小时前
RPC实战和核心原理学习(一)----基础
java·开发语言·网络·数据结构·学习·计算机·rpc
hope_wisdom2 小时前
C/C++数据结构之栈基础
c语言·数据结构·c++··stack
野犬寒鸦2 小时前
力扣hot100:环形链表(快慢指针法)(141)
java·数据结构·算法·leetcode·面试·职场和发展
宁檬精5 小时前
算法练习——55.跳跃游戏
数据结构·算法·游戏
高山有多高7 小时前
顺序表:数据结构中的基础线性存储结构
数据结构
默默无名的大学生7 小时前
数据结构——链表的基本操作
数据结构·算法
_OP_CHEN7 小时前
数据结构(C语言篇):(十一)二叉树概念介绍
c语言·开发语言·数据结构·二叉树·学习笔记··
Neverfadeaway7 小时前
C语言————冒泡排序(例题2)
c语言·数据结构·算法·冒泡排序·升序排列·降序排列
散1127 小时前
01数据结构-B树
数据结构·b树