堆的简单操作实现
程序员文章站
2024-02-11 21:25:22
...
1、堆的基本概念
关键码的集合按完全二叉树的顺序存储方式存储在一维数组中,并满足:对于所有节点它的父节点关键码都大于子节点(或都小于它的子节点),这样的堆称为最大堆(或最小堆)。
2、堆的特性
(1)父结点的键值总是大于或等于(小于或等于)任何一个子节点的键值。
(2)每个结点的左子树和右子树都是一个二叉堆(都是最大堆或最小堆)。
(3) 当父结点的键值总是大于或等于任何一个子节点的键值时为最大堆。当父结点的键值总是小于或等于 任何一个子节点的键值时为最小堆。 最大堆和最小堆是堆数据结构的重点。堆排序中使用特别的多。
给出一个数组建立一个堆:
很显然这个堆是一个无意义的堆,它既不满足大堆也不满足小堆性质,所以我们要对它进行堆调整:
堆调整—向下调整:首先我们要明确调整堆的目的就是让整棵树中的双亲节点都大于孩子节点(这里以最大堆为例),所以我们要从叶子结点开始调整,直到调整到根节点结束,可能调整好这棵树后,子树又不符合最大堆规则,转而调整子树,所以我们把这种方式叫下调(AdjustDown)
向下调整函数:
void _AdjustDown(size_t parent)
{
//1 默认左孩子为左右孩子中最小的孩子--child
int child = 2 * parent + 1;
int size = _array.size();
while (child < size)
{
//找左右孩子中最小的孩子
if ((child+1<size)&&Compare()(_array[child+1] , _array[child]))
child = child + 1;
if (Compare()(_array[child], _array[parent]))
{
swap(_array[child], _array[parent]);
parent = child;
child = 2 * parent + 1;
}
else
break;
}
3、堆的基本操作实现
//1. 实现堆的以下接口
#include<iostream>
using namespace std;
#include<vector>
template<class T>
class Less//比较器--小
{
public:
bool operator()(const T&left, const T&right)
{
return left < right;
}
};
template<class T>
class greater//比较器--大
{
public:
bool operator()(const T&left, const T&right)
{
return left > right;
}
};
template <class T, class Compare>
class Heap
{
public:
Heap()
{}
Heap(const T* array, size_t size)
{
//1 将数组中的元素存起来
_array.resize(size);
for (size_t i = 0; i < size; ++i)
_array[i] = array[i];
//调整堆
//先找二叉树中倒数第一个非叶子结点
int root = (size - 2) >> 1;
for (; root >= 0; --root)
_AdjustDown(root);
}
void Push(const T& data)
{
//先将元素插入到完全二叉树中
_array.push_back(data);
int size = _array.size();
int child = size - 1;
//然后对堆进行调整--向上调整
_AdjustUp(child);
}
void Pop()
{
int size = _array.size();
if (size == 0)
return;
else if (size == 1)
{
_array.pop_back();
}
if (size > 1)
{
int Last = size - 1;
swap(_array[0], _array[Last]);
_array.pop_back();
_AdjustDown(0);
}
}
T Top()const
{
return _array[0];
}
bool Empty()const
{
return _array.empty();
}
size_t Size()const
{
return _array.size();
}
private:
void _AdjustDown(size_t parent)
{
//1 默认左孩子为左右孩子中最小的孩子--child
int child = 2 * parent + 1;
int size = _array.size();
while (child < size)
{
//找左右孩子中最小的孩子
if ((child+1<size)&&Compare()(_array[child+1] , _array[child]))
child = child + 1;
if (Compare()(_array[child], _array[parent]))
{
swap(_array[child], _array[parent]);
parent = child;
child = 2 * parent + 1;
}
else
break;
}
}
void _AdjustUp(size_t child)
{
//先找到双亲节点,以便进行比较
int parent = (child - 1) >> 1;
//调整--向上调整--与双亲节点进行较
while (child > 0)
{
if (_array[child] < _array[parent])
{
swap(_array[child], _array[parent]);
//更新child、parent继续调整
child = parent;
parent = (child - 1) >> 1;
}
else
break;
}
}
private:
std::vector<int> _array;
};
void HeapTest()
{
int arr[] = { 53, 17, 78, 9, 45, 65, 87, 23 };
Heap<int, Less<int>> hp(arr, sizeof(arr) / sizeof(arr[0]));
cout<<hp.Size()<<endl;
cout << hp.Top() << endl;
hp.Pop();
cout << hp.Top() << endl;
hp.Push(55);
}
4、堆排序
升序—建大堆
降序—建小堆
堆排序的基本思想:(以升序为例):
堆排序实质上是一种选择排序,选出最大的与尾部数据交换,然后堆元素个数–,重新建堆,相当于没把刚刚选出的最大的数算在重新建的堆中,如此循环,直到end为0。
代码实现:
void HeapSort(T* a, size_t n)//升序建大堆 降序降序建小堆
{
_a.reserve(n);
int end = n - 1;
Heap(a, n);//建堆
while (end > 0)
{
swap(_a[0], _a[end]);
end--;
AdjustDown(0);
//堆排序(升序),每次把堆顶数据放到数组后面,再利用前end个元素重新建大堆
}
}