一、概念
数据以数组的形式存储。
如果有一个关键码的集合K = {k0,k1, k2,…,kn-1},把它的所有元素按完全二叉树的顺序存储方式存储在一个一维数组中,并满足:Ki <= K2*i+1 且 Ki<= K2*i+2(Ki >= K2*i+1 且 Ki >= K2*i+2) i = 0,1,2…,则称这个堆为最小堆(或最大堆)。
二、形式
最小堆:任一结点的关键码均小于等于它的左右孩子的关键码,位于堆顶结点的关键码最小。
最大堆:任一结点的关键码均大于等于它的左右孩子的关键码,位于堆顶结点的关键码最大。
三、特性
在堆中,所有的记录具有称之为堆序的关系。
1、具有最小堆序的结点(除没有孩子的结点)之间存在小于或等于关系,从根节点到每个结点的路径上数组元素组成的序列都是递增的。
2、具有最大堆序的结点(除没有孩子的结点)之间存在大于或等于关系,从根节点到每个结点的路径上数组元素组成的序列都是递减的。
四、性质
堆存储在下标为0开始计数的数组中,因此在堆中给定下标为i的结点时:
1、如果i=0,结点i是根节点,没有双亲节点;否则结点i的双亲结点为结点(i-1)/2
2、如果2*i+1>n-1,则结点i无左孩子,否则结点i的左孩子为结点2*i+1
3、如果2*i+2>n-1,则结点i无右孩子,否则结点i的右孩子为结点2*i+2
五、堆的创建
1、将所有的数据存储在vector中
2、放完元素之后,则可能导致结构不符合,则需要进行调整(此处先默认为小堆)
找到倒数第一个非叶子结点,标记其为双亲结点,默认其左孩子结点最大,如若另一个结点存在,则进行比较,从而找到最小的孩子,标记此孩子结点,让其与双亲进行比较,若其比双亲小,则进行交换,一直调整,直到其调整的元素超过其size的大小,则为终止。——>此处所用为向下调整的思路
代码实现如下所示:
void AdjustDown(size_t parent)
{
//标记左孩子,且默认左孩子为最小的孩子
size_t child = parent * 2 + 1;
size_t size = _array.size();
//找最小的孩子
while (child < size)
{
//Compare com;
if (child + 1 <size && Compare()(_array[child + 1], _array[child]))//在此处,因为默认左孩子最小,因此要返回true,只能是这样
{
child = child + 1;//右孩子为最小
}
//比较最小的孩子与双亲的大小----值域
if (Compare()(_array[child], _array[parent]))
{
swap(_array[parent], _array[child]);
//交换之后,可能导致孩子结点不为堆
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
六、堆中插入元素
1、在已知上面的堆中(已经调整好的),插入一个元素,因为数据是存放在vector中(类似数组),因此在元素的末尾进行插入(默认小堆)
2、插入之后,则可能导致部分结构不符合要求,因此需要进行调整
3、让其插入元素与双亲进行比较,如果其比双亲小,则进行交换,交换之后,则可能导致以此结点为孩子结点的双亲又不符合,因此,一直向上调整,一直到调整到根部则调整结束。——>此处为向上调整的思路
代码实现如下所示:
void AdjustUp(size_t child)//此处的child为下标
{
size_t parent = (child - 1) >> 1;
while (child > 0)
{
if (Compare()(_array[child] , _array[parent]))
{
swap(_array[parent], _array[child]);
child = parent;
parent = (child - 1) >> 1;
}
else
return;
}
}
七、堆中删除元素
在堆中,一般删除的元素为堆顶元素
1、如果堆为空,则不能删除,直接返回
2、堆中只有一个元素,直接删除,不需要调整
3、堆中有多个元素,让其堆顶元素与最后一个元素进行交换,然后直接删除最后一个元素,在利用向下调整的思路,进行调整即可。
代码如下所示:
void Pop()
{
if (_array.size() == 0)
return;
else if (_array.size() == 1)
{
_array.pop_back();
return;
}
//多个元素
else
{
swap(_array[0], _array[_array.size() - 1]);
_array.pop_back();//删除数组中最后一个元素
AdjustDown(0);
}
}
八、获取堆顶元素
注意:此处的返回值不能是引用,防止堆顶元素被修改
代码如下所示:
T Top()const
{
assert(_array.size());
return _array[0];
}
九、获取堆中的元素个数与判空
代码实现如下:
直接利用vector
bool Empty()const
{
return _array.empty();
}
size_t Size()const
{
return _array.size();
}
十、注意
此处需要创建一个任意堆,则需要定制比较器,在此处,我们选择仿函数,从而实现我们的应用,仿函数的实现具体如下:
即重载函数调用符,从而实现我们的思路
template<class T>
class Less//小堆
{
public:
bool operator()(const T&left, const T&right)
{
return left < right;
}
};
template<class T>
class Great//大堆
{
public:
bool operator()(const T&left, const T&right)
{
return left > right;
}
};
十一、整体代码
//模板参数列表,创建一个任意堆
#if 1
template<class T>
class Less//小堆
{
public:
bool operator()(const T&left, const T&right)
{
return left < right;
}
};
template<class T>
class Great//大堆
{
public:
bool operator()(const T&left, const T&right)
{
return left > right;
}
};
template<class T,class Compare=Less<T>>//缺省时为小堆
class Heap
{
public:
Heap()
{}
Heap(const T*array, size_t size)
{
//开辟空间
_array.resize(size);
//拷贝元素
for (int i = 0; i < size; ++i)
{
_array[i] = array[i];
}
//调整元素
int root = (size - 1 - 1) >> 1;
for (; root >= 0; --root)
{
AdjustDown(root);
}
}
void Push(const T&data)
{
_array.push_back(data);//将元素放在vector中,即数组的最后
//元素放入之后,个数大于1,则需要对齐进行调整
if (_array.size() > 1)
{
//向上调整
AdjustUp(_array.size() - 1);//此处传的是下标
}
}
void Pop()
{
if (_array.size() == 0)
return;
else if (_array.size() == 1)
{
_array.pop_back();
return;
}
//多个元素
else
{
swap(_array[0], _array[_array.size() - 1]);
_array.pop_back();//删除数组中最后一个元素
AdjustDown(0);
}
}
bool Empty()const
{
return _array.empty();
}
size_t Size()const
{
return _array.size();
}
//获取堆顶元素
T Top()const
{
assert(_array.size());
return _array[0];
}
private:
//创建小堆-----向下调整
void AdjustDown(size_t parent)
{
//标记左孩子,且默认左孩子为最小的孩子
size_t child = parent * 2 + 1;
size_t size = _array.size();
//找最小的孩子
while (child < size)
{
//Compare com;
if (child + 1 <size && Compare()(_array[child + 1], _array[child]))//在此处,因为默认左孩子最小,因此要返回true,只能是这样
{
child = child + 1;//右孩子为最小
}
//比较最小的孩子与双亲的大小----值域
if (Compare()(_array[child], _array[parent]))
{
swap(_array[parent], _array[child]);
//交换之后,可能导致孩子结点不为堆
parent = child;
child = parent * 2 + 1;
}
else
break;
}
}
void AdjustUp(size_t child)//此处的child为下标
{
size_t parent = (child - 1) >> 1;
while (child > 0)
{
if (Compare()(_array[child] , _array[parent]))
{
swap(_array[parent], _array[child]);
child = parent;
parent = (child - 1) >> 1;
}
else
return;
}
}
private:
vector<T> _array;
};
测试函数如下所示:
void Test()
{
int array[] = { 53, 17, 78, 9, 45, 65, 87, 23 };
Heap<int> hp(array,sizeof(array)/sizeof(*array));//此处默认为小堆
cout << hp.Top() << endl;
hp.Push(98);
cout << hp.Top() << endl;
cout << hp.Size() << endl;
/*hp.Pop();
cout << hp.Top() << endl;
hp.Pop();
cout << hp.Top() << endl;
hp.Pop();
cout << hp.Top() << endl;
hp.Pop();
cout << hp.Top() << endl;
hp.Pop();
cout << hp.Top() << endl;
hp.Pop();
cout << hp.Top() << endl;*/
}
注意:在测试删除函数之时,一直测试到空堆,从而验证代码的正确性。
有关堆的基础,就这么多了,下次将实现一些对堆的的英语,从而提升自己的技能。
只有不停的奔跑,才能不停留在原地。