数据结构与算法分析 六、优先队列(堆)

模型

操作

●Insert:插入
●DeleteMin:找出、返回和删除优先队列中最小的元素

一些简单实现

简单链表

效率
 在表头以O(1)执行插入操作,遍历链表删除最小元,花费O(n)时间。

缺点
 时间复杂度高

二叉查找树

效率
 O(logN)操作插入,O(logN)操作删除最小元。

缺点
 反复删除左子树 的节点损害了树的平衡,使得右子树加重。

二叉堆

结构性质

完全二叉树
 堆是一颗被完全填满的二叉树(即完全二叉树)。

数组表示
 完全二叉树很有规律,可以用数组来实现。
 对于数组中任一位置i上的元素,左儿子在2i上,右儿子在左儿子后的单元2i+1中,父亲在i/2上。
 唯一的问题在于堆的大小需要事先估计。

堆序性质

父子关系
 每一个节点X的父亲的值<=X的值

最小值
 最小值总可以在根处找到

基本的堆操作

插入
 在下一个空闲位置创建空穴,保证完全树的性质。然后空穴上滤,新元素在堆中上滤直到找出正确的位置。

上滤:如果X在空穴中不破坏堆的序,插入完成。否则,空穴的父节点上的元素移入空穴。继续该过程知道X能被放入空穴中位置。
在这里插入图片描述
删除最小值
 删除最小元后,根节点处产生了一个空穴,堆中最后一个元素需要移动到某个地方,空穴需要下滤。

下滤:将空穴的两个儿子的较小者移入空穴,这样把空穴向下推了一层。重复该步骤直到X可以被放入空穴中。
在这里插入图片描述
降低值
 降低P处的值后需要上滤对堆进行调整。

增加值
 增加P处的值后需要下滤对堆进行调整。

删除
 删除P节点可以先将P的值减到最小,然后通过删除最小值来实现。

构建堆
●n次insert
●n/2次下滤:先将N个关键字以任意顺序放入树中,保持结构特性。然后

for(i = N / 2; i > 0; i--)
	PercolateDown(i);

C++实现

#include <iostream>
#include<vector>
using namespace std;

const int vec_size = 100;

class min_heap {
public:
	min_heap():vec(vector<int>(vec_size)), capacity(vec_size), size(0){}
	void insert(int val);
	int delete_min();
	bool isempty() { return size == 0; }
	vector<int>vec;
private:
	void percolate_up(int index);

	int capacity;
	int size;
};

void min_heap::insert(int val) {
	if (size == capacity) {
		cerr << "已满" << endl;
		exit(1);
	}
	vec[size] = val;
	percolate_up(size);
	size++;
}

int min_heap::delete_min() {
	if (size == 0) {
		cerr << "为空" << endl;
		exit(1);
	}
	int min_val = vec[0];
	int last_val = vec[--size];
	int i = 0, child = 0;
	//至少有一个子节点时,将子节点的较小者放到此处
	while(i * 2 + 1 <size) {
		child = i * 2 + 1;
		if (child != (size - 1) && vec[child + 1] < vec[child])
			child++;
		//如果当前的最小子节点都大于最后一个数,则结束循环,该位置放last_val
		if (vec[child] >= last_val)
			break;
		vec[i] = vec[child];
		i = child;
	}
	vec[i] = last_val;
	return min_val;
}

//上滤,找到合适的地方存放index处的值
//删除值的下滤和增加某位置值的下滤不一样,一个是给最后一个元素找到合适的地方,一个是给当前增加的值找到合适的地方,就不统一实现了
void min_heap::percolate_up(int index) {
	//取出刚放入最后的值,此时size还没++
	int tmp = vec[index];
	int cur = index;
	int parent = (cur - 1) / 2;
	//从0开始,i节点的父亲为(i - 1) / 2,i的左儿子为2i + 1
	while (cur > 0)
	{
		if (vec[parent] <= tmp)
			break;
		vec[cur] = vec[parent];
		cur = parent;
		parent = (parent - 1) / 2;
	}
	vec[cur] = tmp;
}

int main() {
	min_heap min_heap;
	min_heap.insert(8);
	min_heap.insert(2);
	min_heap.insert(3);
	min_heap.insert(5);
	min_heap.insert(6);
	min_heap.insert(7);
	//vec应该为private,这里为了查看内部排列是否正确,置为了public
	cout << "此时vec内部:" << " ";
	for (int i = 0; i < 6; i++)
		cout << min_heap.vec[i] << " ";
	
	cout << endl <<"弹出(即排序):" << " ";
	while (!min_heap.isempty()) {
		cout << min_heap.delete_min() << " ";
	}
	cout << endl;
}

应用

100亿个数中找出最大的前k个数(海量数据topk问题)

 先拿10000个数建堆,然后一次添加剩余元素,如果大于堆顶的数(10000中最小的),将这个数替换堆顶,并调整结构使之仍然是一个最小堆,这样,遍历完后,堆中的10000个数就是所需的最大的10000个。建堆时间复杂度是O(mlogm),算法的时间复杂度为O(nmlogm)(n为10亿,m为10000)。

 优化的方法:可以把所有10亿个数据分组存放,比如分别放在1000个文件中。这样处理就可以分别在每个文件的10^6个数据中找出最大的10000个数,合并到一起在再找出最终的结果。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值