堆
堆数据结构是一种数组对象,它可以被视为一棵完全二叉树结构。
堆结构的二叉树存储是
最大堆:每个父节点的都大于孩子节点。
最小堆:每个父节点的都小于孩子节点。
堆的特点:堆顶元素为最值(大堆最大,小堆最小)。
具体实现
实现一个堆涉及两个算法,分别是向下调整算法AdjustDown(root),和向上调整算法AdjustUp(chaild)。下面开始介绍这两个算法
1、AdjustDown(root)
作用是将以下标root为根结点的树调整成符合要求的堆(大堆或小堆)。但是要求root结点的左右子树必须已经是堆。
实现思路
以实现大堆为例
在左右子树中找出最大值
因为左右子树本身就已经是堆,所以比较堆顶元素就可以了。
将该最大值和父亲结点做比较
- 若该值大于父亲结点,则交换这两个值,将较大值给父亲结点所在的空间,原父亲的元素做孩子,在从该子树开始继续向下调整
- 若该值小与父亲节点,不用调整
应用:
- 利用AdjustDown(root)算法建堆
前面已经说过堆的存储结构其实是一个数组,可以被试为完全二叉树,所以建堆的时候,可以找到最后一个非叶子结点,因为从这个结点开始到树的根结点(数组首元素)都可以看作是一个子树,所以可以从该节点开始向上的每个元素都进行一次向下调整算法,就可以建成一个堆。
- 为什么要找最后一个非叶子结点?
因为要用向下调整算法,所以开始调整的结点的左右子树必须是堆,叶子结点都可以看成是堆(无孩子,自然可以看做是堆),所以找倒数第一个叶子结点开始倒着向上依次让每个结点做向下调整算法。
- 为什么要倒着向上让每个结点做一次向下调整算法?
因为要用向下调整算法,所以开始调整的结点的左右子树必须是堆,下面的结点是上面结点的孩子,所以只有当下面的结点所在的子树满足堆的条件,上面的结点才能用向下调整算法。
- 怎么找到最后一个非叶子结点?
堆是用数组实现的,数组可以用size()函数求出元素个数,就知道了最后一个叶子结点的下标i(i = size()-1),最后一个叶子结点的父亲就是最后一个非叶子结点,可用(i-1)/2求出该非叶子结点下标。
- 实现pop()(删除堆顶元素)
- 堆顶元素与最后一个元素交换
- 然后pop_back()
- 在从堆顶开始向下调整
2、 AdjustUp(int child)
作用是从当前结点开始,一次向祖先结点调整。
以大堆为例
- 如果child结点的值大于,他的父亲结点,则需要向上调整,将child结点的值与父亲交换,在从原来父亲结点的位置开始,继续向上调整。
- 若child的值不大于父亲结点的值,则停止调整。
应用:
往堆内增加一个结点的时候(push_back),可以从最后一个位置开始做向上调整算法。
#pragma once
#include<iostream>
#include <vector>
#include <functional>
#include<assert.h>
using namespace std;
//仿函数,让代码可以建大堆也可以建小堆(代码只有些符号不同)
template<class T>
struct Less
{
bool operator ()(const T& l, const T& r)//小于返回ture
{
return l < r;
}
};
template<class T>
struct Greater
{
bool operator ()(const T& l, const T& r)//大于返回ture
{
return l > r;
}
};
template<class T, class Compare>//第二个模板参数为仿函数
class Heap
{
public:
Heap()
{}
Heap(T* a,size_t n)
{
_a.reserve(n);//给_a重新分配n个空间
for (size_t i = 0; i < n; i++)
{
_a.push_back(a[i]);
}
//建堆
for (int i = (_a.size() - 2 / 2); i >= 0;--i)
{
AdjustDown(i);//参数i为下标
}
}
//向下调整算法
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 + 1] , _a[child]))
{
++child;
}
if (com(_a[child],_a[parent]))//大的孩子大于根节点,交换后继续调整
{
swap(_a[child], _a[parent]);
parent = child;
child = parent * 2 + 1;
}
else
{
break;
}
}
}
//无仿函数版本(建大堆)
void AdjustDown(int* a, size_t n, int root)
{
int parent = root;//下标
int child = parent * 2 + 1;
while (child < n)
{
//找最大孩子,与父亲结点做比较,看是否需要交换
if (child + 1 < n
&&a[child + 1] > a[child])
{
++child;
}
if (a[child]>a[parent])
{
sawp(a[child], a[parent]);
parent = child;//交换后继续向下调整
child = parent * 2 + 1;
}
else
break;
}
}
//向上调整算法
void AdjustUp(int child)//最后一个结点下标
{
Compare com;
int parent = (child - 1) / 2;
while (child>0)
{
//if (_a[child] > _a[parent])
if (com(_a[child], _a[parent]))
{
swap(_a[child], _a[parent]);
child = parent;
parent = (child - 1) / 2;
}
else
{
break;
}
}
}
//往内堆增加结点
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);
}
//返回堆顶元素
T& Top()
{
assert(!_a.empty());
return _a[0];
}
size_t Size()
{
return _a.size();
}
void HeapSort(int* a, size_t n)
{
//建堆
for (int i = (n - 2) / 2; i >= 0; --i)//第一个非叶子节点开始,向下调整算法
{
//AdiustDown(a, n, i);
AdiustDown(i);
}
//排序
size_t end = n - 1;
while (end > 0)
{
swap(a[0], a[end]);
AdiustDown(a, end, 0);
--end;
}
}
private:
vector<T> _a;
};