堆的定义
堆(英语:heap)是计算机科学中一类特殊的数据结构的统称。堆通常是一个可以被看做一棵树的数组对象。堆总是满足下列性质:
-
堆中某个节点的值总是不大于或不小于其父节点的值;
-
堆总是一棵完全二叉树。
将根节点最大的堆叫做最大堆或大根堆,根节点最小的堆叫做最小堆或小根堆。
堆的本质
vector+向下调整算法/向上调整算法
注意:这棵树是完全二叉树
向下调整算法:
已知条件:从一个节点开始向下调整,已知这个节点的左右子树已经是大堆或小堆
所以需要从第一个不是叶子节点的节点开始调整,而这个节点正好是最后一个节点的父节点。即i=(_v.size()-2)/2; i即是最后一个不是叶子节点的节点。
代码如下:
#include<iostream>
using namespace std;
#include<vector>
template<class T>
struct Greater
{
bool operator()(const T& x, const T& y)
{
return x > y;
}
};
template<class T>
struct Less
{
bool operator()(const T&x, const T& y)
{
return x < y;
}
};
template<class T,class Compare = Grater<T>>
class Heap
{
public:
Heap()
{}
Heap(const T* a,size_t n)
{
//vector(动态数组)
//vector reserve() 改变Capacity 配套用push_back 建议使用这个
//vector resize() 改变size大小 a[i] = array[i]
_v.reserve(n);
for (size_t i = 0; i < n; i++)
{
_v.push_back(a[i]);
}
//建堆
for (int i = ((int)_v.size()-2)/2; i >=0 ; i--)
{
AdjustDown(i);
}
//向下调正算法给的第一个就是父节点 向上给的第一个是叶子节点
//向下调正算法 假设左右都是大堆 往下换 叶子节点不用算,从最后一个非叶子节点算起(倒着算)
}
void Push(const T& data)
{
_v.push_back(data);
//向上调正算法 只在push()这里用,把新填的叶子节不断向上换 直到符合大/小堆
AdjustUp(_v.size() - 1);
}
void Pop()
{
//1.先将堆顶元素与堆的最后一个元素交换
swap(_v[_v.size() - 1], _v[0]);
//2.删除最后一个节点
_v.pop_back();
//3.向下调整
AdjustDown(0);
}
T& Top()
{
return _v[0];
}
protected:
void AdjustDown(int root)
{
int parent = root;
int child = root * 2 + 1;
while (child<(int)_v.size())
{
if (child + 1 < (int)_v.size() && Compare()(_v[child+1], _v[child]))
{
child++;
}
if (Compare()(_v[child], _v[parent]))
{
swap(_v[parent], _v[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void AdjustUp(int child)
{
int parent = (child - 1) / 2;
//注意这里是child>0 不是parent>0
while (child>0)
{
if (Compare()(_v[parent], _v[child]))
{
break;
}
else
{
swap(_v[child], _v[parent]);
child = parent;
parent = (child - 1) / 2;
}
}
}
private:
vector<T> _v;
};
void TestHeap()
{
int array[3] = {5,4,3};
//小堆
Heap<int, Less<int>> h(array,sizeof(array)/sizeof(array[0]));
cout<<h.Top()<<endl;
h.Push(2);
cout << h.Top() << endl;
h.Pop();
cout << h.Top() << endl;
}
运行结果:
总结:对于向上和向下调整算法首要的前提是朝上或者朝下已经是大小堆了,而且这两种算法操作的都是直接的父节点或者直接的子节点
向下调整算法要注意的是 ,要将最值数据放到根节点,所以两个孩子节点需要比较,并且如果已经是符合条件,记得break,否则会陷入死循环
向上调整算法注意的是,一般此算法多用于Push操作,也就是传入的是新加入的孩子节点,这个就直接和根节点比较,(只需要看是不是
需要上调),所以如果不成立就直接break。
建堆必须用向下调正算法,因为一开始并不知道大小堆情况,所以从最后一个父节点开始依次调整,如果用向上算法,根本没有办法保证已经是
大小堆
堆的应用:
1.TopK问题(海量数据处理问题)
问题:需要从十亿个数据里面找出最大的第K个数字
分析思路:
首先不能使用排序,数据太多了,计算机压根就存不下,我们可以先从十亿个数据里面拿出K个数据建立一个小堆,剩余的数据每次和Top比较,如果比Top大,就互换,然后用向下调整,保持是一个小堆,以此类推,处理完所有的数据。这里可以很明显的知道最大的前K个数据一定会进堆,而Top里面存的就是第K大的数据,这里不可以建立大堆,因为建立大堆,每次取出Top和其余数据比较,之后保持大堆,最后Top里面存的就是最大的那个数字。
代码如下:
第一种解法:
void adjustdown(vector<int>& a, int parent,int k)
{
int child = parent*2+1;
while (child < k)
{
if (child + 1 < k&&a[child + 1] > a[child])
child++;
if (a[parent] < a[child])
{
swap(a[parent], a[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
//如果上述条件不成立 立马退出来 要不死循环 因为上述条件不成立 没有改变parent 和child
break;
}
}
}
void HeapSort(vector<int>& a,int k,int size)
{
//建立大堆
for (int i = (k-2)/2; i >= 0; i--)
adjustdown(a, i,k);
for (int i = k; i < size; i++)
{
if (a[i] < a[0])
{
swap(a[i], a[0]);
adjustdown(a, 0,k);
}
}
}
第二种解法
void TopK()
{
int array1[3] = { 1, 2, 3 };
Heap<int, Less<int>> h(array1, sizeof(array1) / sizeof(array1[0]));
int array2[7] = { 4, 5, 6, 7, 8, 9, 10 };
for (size_t i = 0; i <sizeof(array2) / sizeof(array2[0]); i++)
{
if (array2[i]>h.Top())
{
h.Pop();
h.Push(array2[i]);
}
}
cout << h.Top() << endl;
}
运行结果:
2.堆排序
升序(需要建大堆)
将0号下标的数据和最后一个数据交换,堆中数据个数减1,然后再向下调整,使得0号下标的数据是最大值,然后继续和堆中最后一个数据交换,继续将堆中元素个数减一。同理降序(小堆)。
代码如下:
void AdjustDown(int* heap,int n ,int root)
{
int parent = root;
int child = root * 2 + 1;
while (child<n)
{
if (child + 1 < n && heap[child + 1]>heap[child])
{
child++;
}
if (heap[child] >heap[parent])
{
swap(heap[parent], heap[child]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
void HeapSort(int* a, int n)
{
//建了个大堆
for (int i = (n-2)/2; i >=0; i--)
{
AdjustDown(a, n, i);
}
while (n>1)
{
swap(a[0], a[n - 1]);
--n;
AdjustDown(a, n, 0);
}
}
int main()
{
int a[3] = { 3, 2, 1 };
HeapSort(a,sizeof(a)/sizeof(a[0]));
for (size_t i = 0; i < 3; i++)
{
cout << a[i] << " ";
}
cout << endl;
system("pause");
return 0;
}
运行结果: