1.堆的概念
将所有元素按照完全二叉树的顺序存储方式存储在一维数组中,并且满足任意一根节点的左右子树的关键字的值小于等于根节点的值。堆实际上是一个一维数组,不过是将这个一维数组当成完全二叉树,并且认为其满足完全二叉树的双亲结点的序号和孩子结点序号的关系。
2.堆的分类
堆分为大堆和小堆。大堆,即堆顶元素是所有元素中最大的,将完全二叉树沿某条路径遍历,则得到的序列是递减的;
小堆,即堆顶元素是所有元素中最小的,将完全二叉树沿某条路径遍历,则得到的序列是递增的。
3.堆的创建
算法思想:将一维数组看作与之对应的完全二叉树,看此完全二叉树是否满足堆的性质。若满足,则就是堆;若不满足,则需要自顶向下调整堆。
所谓的自顶向下调整是指:比较左右子树的值,选取较大(较小)的一个;比较双亲结点和较大(较小)的左右子树的结点,若双亲结点较小(较大),则交换左右子树和双亲结点。
我们以创建大堆为例,先找到倒数第一个非叶子结点,即最后一个元素的序号减去1除以2。不断自顶向下调整堆,直到根节点。
为了实现高效的堆,我们利用模板类实现堆的多适用性,建立比较器实现任意大堆或者小堆。
template <class T>
class Greater
{
public:
bool operator()(const T&left,const T&right)
{
return left > right;
}
};
template <class T>
class Less
{
public:
bool operator()(const T&left, const T&right)
{
return left < right;
}
};
Heap(int* arr, int sz)
{
_arr.resize(sz);
for (int i = 0; i < sz; i++)
{
_arr[i] = arr[i];
}
//计算倒数第一个非叶子结点
int parent = (sz - 2) >> 1;
for (; parent >= 0; parent--)
{
AjustDown(parent);
}
}
void AjustDown(int parent)
{
int child = 2 * parent + 1;
int size = _arr.size();
while (child<size)
{
if (child+1 < size && Compare<T>()(_arr[child+1] ,_arr[child]))
child = child + 1;
if (Compare<T>()(_arr[child],_arr[parent]))
{
swap(_arr[child], _arr[parent]);
parent = child;
child = 2 * parent + 1;
}
else
break;
}
}
5.堆的插入
堆的插入每次都是在已经建好的堆的后面插入元素,但在插入之后,可能会改变堆的结构,需要对堆自下向上进行调整。
所谓的自下向上调整是☞:先找到双亲结点,比较双亲和孩子结点的值,若需要交换,则交换双亲和孩子结点的值。
注:向上调整不需要比较左右孩子结点的值。
void InsertHeap(int key)
{
int sz = _arr.size();
_arr.resize(sz + 1);
int newsize = _arr.size();
_arr[newsize-1] = key;
int parent = newsize - 1;
AjustUp(parent);
}
//自下向上调整
void AjustUp(int child)
{
int parent = (child - 1) >> 1;
while (child>0)
{
if (Compare<T>()(_arr[child] , _arr[parent]))
{
swap(_arr[child], _arr[parent]);
child = parent;
parent = (child - 1) >> 1;
}
else
break;
}
}
6.堆的应用——堆排序
思想:
在进行堆排序之前,我们需要先建好堆。遵循升序排列建立大堆,降序排列建立小堆。将堆顶元素和堆的最后一个元素进行交换,但在交换之后,可能破坏堆的结构,我们需要自顶向下调整堆。
适用情况:
在数据量较大时。
时间复杂度:O(n*Log2n);
空间复杂度:O(n);
稳定性:不稳定;
void CreateHeap(DataType* arr, int sz)
{
size_t root = (sz - 2) >> 1;
for (; root >= 0; root--)
{
Adjust(arr, sz, root);
}
//堆排序
size_t end = sz - 1;
while (end)
{
swap(arr[0], arr[end]);
Adjust(arr, end, 0);
end--;
}
}
void Adjust(int*arr, int sz, size_t parent)
{
size_t Child = parent * 2 + 1;
while (Child < sz)
{
if (Child + 1 < sz&&arr[Child + 1] > arr[Child])
Child = Child + 1;
if (arr[Child]>arr[parent])
{
swap(arr[Child], arr[parent]);
parent = Child;
Child = parent * 2 + 1;
}
else
break;
}
}