模型
操作
●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个数,合并到一起在再找出最终的结果。