堆简介
堆是一种特殊的数据结构,它通常是一个可以看做一棵树的数组对象。
堆既然是一个数组,它便通过下标以二叉树的方式来保存。
以下图二叉树模型为例
红色数字代表节点的值,黑色数字代表节点的下标。
这是一颗乱序的完全二叉树,可以观察到
父节点下标 = (左孩子下标-1)/2
左孩子下标 = (父节点下标*2)+1
根据以上规律便可以很好地存储一棵树了。
堆有两种存储结构:
最大堆:每个父节点的值都大于孩子节点。
最小堆:每个父节点的值都小于孩子节点。
基本操作
(一)建堆
Heap(const T* a,int n)
{
_a.reserve(n);
for(int i = 0;i < n;i++)//给定数值全部压入堆
{
_a.push_back(a[i]);
}
for(int i = _a.size()-2>>1;i >= 0;--i)//找到最后一个叶子节点的父亲节点
{
_AdjustDown(i);
}
}
(二)向下调整:
步骤:
(1)假设该节点的下标为parent;
(2)找到该节点的左孩子child=parent*2+1;
(3)如果右孩子存在,找到左孩子和右孩子中的较小者。
(4)比较父节点是否小于其左右孩子中的较小者,如果父节点小于较小者调整结束,否则将父节点的元素与较小孩子交换,此时有可能导致其子树不满足堆的性质,继续调整其子树直到满足堆的性质。
void _AdjustDown(int root)
{
int parent = root;
int child = parent*2 +1;
while(child<_a.size())
{
if(child+1<_a.size()&&_a[child+1]<_a[child])
{
child=child+1;
}
if(_a[child]<_a[parent])
{
swap(_a[child],_a[parent]);
child = parent;
child = parent*2 + 1;
}
else
{
return;
}
}
}
(三)向上调整法:
void _AdjustUp(int child)
{
int parent = (child - 1)>>1;
while(child > 0)
{
if(_a[child]<_a[parent])
{
swap(_a[child],_a[parent]);
child = parent;
parent = (child - 1)>>1;
}
else
{
break;
}
}
}
(四)堆的插入:
每次都插入到数组的最后,插入新元素后可能破坏堆的结构,然后针对这个节点进行一次向上调整
void Push(const T& x)
{
_a.push_back(x);
_AdjustUp(_a.size()-1);
}
(五)堆的删除:
(1)将堆中最后一个元素替代堆顶元素
(2)将堆中元素个数减少一个,相当于将堆中最后一个元素删除
(3)此时堆结构可能被破坏,再向下调整使其满足堆的性质。
void Pop()//代替删除法
{
assert(!_a.empty());
swap(_a[0],_a[_a.size()-1]);//先将头与数组最后一个值相交换
_a.pop_back();
_AdjustDown(0);//对头结点进行向下调整法
}
完整代码:
template<class T>
//仿函数,代码复用
struct less
{
bool operator() (const T&l,const T&r) const
{
return l < r;
}
};
template<class T>
struct greater
{
bool operator() (const T&l,const T&r) const
{
return l > r;
}
};
template<class T,class Compare = greater<T>>//默认建大堆
class Heap
{
public:
Heap()
{}
Heap(const T* a,int n)
{
_a.reserve(n);
for(int i = 0;i < n;i++)//给定数值全部压入
{
_a.push_back(a[i]);
}
for(int i = _a.size()-2>>1;i >= 0;--i)//找到最后一个节点的父亲
{
_AdjustDown(i);
}
}
void Push(const T& x)
{
_a.push_back(x);
_AdjustUp(_a.size()-1);
}
void Pop()//代替删除法
{
assert(!_a.empty());
swap(_a[0],_a[_a.size()-1]);//先将头与数组最后一个值相交换
_a.pop_back();
_AdjustDown(0);//对头结点进行向下调整法
}
const T& Top() const
{
assert(!_a.empty());
return _a[0];
}
size_t Size()
{
return _a.size();
}
bool Empty()
{
return _a.empty();
}
void IsHeap()
{
return _IsHeap(int root);
}
void AdjustDown(int root)
{
return _Adjustdown(int root);
}
void AdjustUp(int child)
{
return _AdjustUp(int child);
}
void Prinrt()
{
for(int i = 0;i < _a.size();i++)
{
cout<<_a[i]<<" ";
}
cout<<endl;
}
protected:
bool _IsHeap(int root)//判断是否是小堆
{
for(size_t = 0;i <= (a.size()-2)/2;i++)
{
if(_a[i] < _a[i*2+1] || ((i*2+2)<_a.size()&&_a[i] < _a[i*2+2]))
{
return false;
}
}
return true;
}
void _AdjustUp(int child)
{
Compare Com;
int parent = (child - 1)>>1;
while(child > 0)
{
if(Com(_a[parent],_a[child]))
{
swap(_a[child],_a[parent]);
child = parent;
parent = (child - 1)>>1;
}
else
{
break;
}
}
}
void _AdjustDown(int root)
{
Compare Com;
int parent = root;
int child = parent*2 + 1;
while(child < _a.size())
{
if(child+1<_a.size()&&Com(_a[child],_a[child+1]))
child++;
if(Com(_a[parent],_a[child]))
{
swap(_a[child],_a[parent]);
//让孩子节点作为父亲节点,判断是否存在子树
parent = child;
child = parent*2 + 1;
}
else
break;
}
}
private:
vector<T> _a;
};
int main()
{
int a[]={10,11,12,13,14,15,16,17,18,19};
Heap<int,less<int>> h1(a,sizeof(a)/sizeof(a[0]));//这里是建小堆
h1.Prinrt();
h1.Push(21);
h1.Prinrt();
h1.Pop();
h1.Prinrt();
}
运行结果:
堆排序
//堆排序(以调整为大堆为例)
void AdjustDown(int* a,int i,int end)
{
int parent = i;
int child = i*2+1;
while(child<=end)
{
if((child+1<end)&&(a[child]<a[child+1]))
{
child++;
}
if(a[parent]<a[child])
{
swap(a[parent],a[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,i,n-1);
}
int end = n-1;依次上调
}
int main()
{
int a[] = {10,11,13,7,16,18,15,12,14,9};
HeapSort(a,sizeof(a)/sizeof(a[0]));
for(size_t i = 0;i < sizeof(a)/sizeof(a[0]);i++)
{
cout<<a[i]<<" ";
}
cout<<endl;
}
TopK问题
问题是统计中很常见的问题:例如老师要从全校10000名学生中挑选10个最高的成绩。
思路:
(1)创建一个k大小的小堆,将数组中前K个元素push进去,进行调堆。
(2)遍历剩余的所有数据,当遇到的数据大于堆顶元素时,让此数据替换堆顶元素,再进行向下调整。
void AdjustDown(int* heap,int n,int root)
{
assert(heap);
int parent = root;
int child = parent*2 + 1;
while(child<n)
{
if(child+1<n && heap[child+1] < heap[child])
{
++child;
}
if(heap[child] < heap[parent])
{
swap(heap[child],heap[parent]);
parent = child;
child = parent*2;
}
else
{
break;
}
}
}
const size_t N = 10000;
const size_t K = 10;
void TopK()
{
int a[N] = {0};
for(size_t i = 0;i < N;++i)
{
a[i] = rand()%N;
}
int heap[K] = {0};
for(size_t i = 0;i < K;++i)
{
heap[i] = a[i];
}
//建堆
for(int i = (K-2)/2;i>=0;--i)
{
AdjustDown(heap,K,i);
}
for(size_t i = K;i < N;++i)
{
if(a[i] > heap[0])
{
heap[0] = a[i];
AdjustDown(heap,K,0);
}
}
for(size_t i = 0;i < K;++i)
{
cout<<heap[i]<<" ";
}
}
int main()
{
TopK();
return 0;
}