堆的实现代码详解

heap概述

严格意义来说,heap不属于stl容器组件,它是priority queue的助手。这里要讲解的heap是binary heap,其实就是一种完全二叉树。

我们以vector对tree进行表述,这种方法称为隐式表述法。

push_heap算法

template<class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first, RandomAccessIterator last) {
	__push_heap_aux(first, last, distance_type(first), value_type(first));
}
template<class RandomAccessIterator,class Distance,class T>
inline void __push_heap_aux(RandomAccessIterator first, RandomAccessIterator last, Distance*, T*) {
	//迭代器是左开右闭原则,所以最后一个元素的地址是last-1
	//也正是因为上述原因,vector首尾元素之间的距离为(last-first)-1
	__push_heap(first, Distance((last - first) - 1), Distance(0), T(*(last - 1)));
}

 

新插入的元素置于底部容器的最尾端,因为这并不是最终的位置,所以先置为空洞。

template<class RandomAccessIterator,class Distance,class T>
void __push_heap(RandomAccessIterator first, Distance holeIndex, Distance topIndex, T value) {
	/*
	最开始很困惑为什么父节点是(holeIndex-1)/2,而不是holeIndex-1,
	你要注意的是这不是指父节点,而是说父节点到first指向的起始元素
	的距离,包括这里的holeIndex也不是说空洞的坐标,不是此时的空洞
	与first指向的起始元素的距离,通过上面的那张图就能看出为什么要
	减一了
	*/
	Distance parent = (holeIndex - 1) / 2;
	/*
	此处的topIndex是0,代表首元素到首元素的距离,first + parent
	这才是真正的地址下标
	*/
	while (holeIndex > topIndex && *(first + parent) < value) {
		/*
		(first + parent) < value代表是父节点的值小于这个后代的值,
		如果成立,就将父节点的值移置空洞,然后将父节点所在位置
		设为空洞
		*/
		*(first + holeIndex) = *(first + parent);
		holeIndex = parent;//这两个都是距离
		parent = (holeIndex - 1) / 2;//新洞的父节点到起始元素的距离
	}
	//循环结束的时候新插入元素的位置即在空洞所处
	*(first + holeIndex) = value;
}

 

pop_heap算法

以大顶堆为例,最大值必然在根结点,pop操作取走根结点(其实就是设至底部容器vector的尾端节点)后,割舍最下层右边的叶结点,先调整此时的堆,然后再将割舍掉的元素值插入。

template<class RandomAccessIterator>
inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last) {
	__pop_heap_aux(first, last, value_type(first));
}
template<class RandomAccessIterator,class T>
inline void __pop_heap_aux(RandomAccessIterator first, RandomAccessIterator last, T*) {
	__pop_heap(first, last - 1, last - 1, T(*(last - 1)),distance_type(first));
}
template<class RandomAccessIterator,class T,class Distance>
inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last,
	RandomAccessIterator result, T value, Distance*) {
	*result = *first;//设定尾值为首值(最大元素),然后取出
	//取走以后调整heap,空洞为0(0代表空洞距树根的距离),欲调整值为value(也就是原尾值)
	__adjust_heap(first, Distance(0), Distance(last - fiirst), value);
}
template<class RandomAccessIterator,class Distance,class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
	Distance len, T value) {
	//topIndex也就是据树根距离的最小值(0)
	Distance topIndex = holeIndex;
	//secondChild代表空洞节点的右孩子节点到树根的距离,要注意初始的holeIndex为2
	Distance secondChild = 2 * holeIndex + 2;
	//len代表至树根的最远距离
	while (secondChild < len) {
		/*
		*(first + secondChild)是右孩子的位置
		*(first + (secondChild - 1))是左孩子的位置
		找到它俩中的较大者移至空洞所在位置
		然后将较大者的原有位置设为空洞
		*/
		if (*(first + secondChild) < *(first + (secondChild - 1)))
			secondChild--;
		*(first + holeIndex) = *(first + secondChild);
		holeIndex = secondChild;
		/*
		跟新空洞所在处的右孩子
		因为secondChild代表的是距离,所以
		不要想成secondChild=2*secondChild+1,看上图
		*/
		secondChild = 2 * (secondChild + 1);
	}
	//下面这步所示情况如下图所示
	if (secondChild == len) {//没有又节点,只有左节点
		//令左孩子值为空洞值,再将空洞移至左孩子处
		*(first + holeIndex) = *(first + (secondChild - 1));
		holeIndex = secondChild - 1;
	}
	__push_heap(first, holeIndex, topIndex, value);
}

这张图描述的特殊情况就是上面的if语句,将10移至0处,然后secondChild=2*(econdChild+1)=3;而len描述的是至树根的最远距离也为3,此时就只有左孩子而没有右孩子。(注意:上述的红色角标仍旧是代表距离树根的距离)

小结

pop_heap之后,最大元素只是被置放在底部容器的最尾端,尚未被取走,如果要取其值,可使用底部容器(vector)所提供的back()操作函数,如果要移除它,可使用底部容器(vector)所提供的pop_back()操作函数。

sort_heap算法

既然每次pop_heap可获得heap中键值最大的元素,若持续对整个heap做pop_heap操作,每次将操作方位从后向前缩减一个元素(因为pop_back会把键值最大的元素放在容器的最尾端),当整个程序执行完毕的时候,便形成了一个递增序列。

template<class RandomAccessIterator>
void sort_heap(RandomAccessIterator first, RandomAccessIterator last) {
	while (last - first > 1)
		//每次执行pop_heap()一次,操作范围退缩一格
		pop_heap(first, last--);
}

 

make_heap算法

template<class RandomAccessIterator>
inline void make_heap(RandomAccessIterator first, RandomAccessIterator last) {
	__male_heap(first, last, value_type(first), distance_type(first));
}
template<class RandomAccessIterator,class T,class Distance>
void __male_heap(RandomAccessIterator first, RandomAccessIterator last,
	T*, Distance*) {
	/*
	小心,holeIndex是距离类型
	holeIndex是需要重排的子树头部,从最后一个有子树的头部开始,利用
	__adjust_heap进行调整,然后holeIndex向前,继续调整
	*/
	Distance holeIndex = (len - 2) / 2;
	while (true) {
		__adjust_heap(first, holeIndex, len, T(*(first + holeIndex)));
		if (holeIndex == 0) return;
		holeIndex--;
	}
}

通过下图可以看出为什么第一个空洞是holeIndex=(len-2)/2;

迭代器first为0,last为10,len=last-first=10;

很明显可以看出,距离为5、6、7、8、9的节点均满足大顶堆的性质,因为它们没有左右孩子,所以需要重新构建的第一个空洞就是最后一个右孩子的节点,即为距离为(10-2)/2=4的节点,然后依次向前。

 


 

实现一个堆排序

#include <iostream>
#include<algorithm>
using namespace std;

//设计时第一个元素的索引是1
void HeapAdjust(int *a, int i, int size)  //调整堆 
{
	int lchild = 2 * i;       //i的左孩子节点序号 
	int rchild = 2 * i + 1;     //i的右孩子节点序号 
	int max = i;            //临时变量 
	if (i <= size / 2)          //如果i不是叶节点就不用进行调整 
	{
		if (lchild <= size&&a[lchild] > a[max])
		{
			max = lchild;
		}
		if (rchild <= size&&a[rchild] > a[max])
		{
			max = rchild;
		}
		if (max != i)
		{
			swap(a[i], a[max]);
			HeapAdjust(a, max, size);    //避免调整之后以max为父节点的子树不是堆 
		}
	}
}

void BuildHeap(int *a, int size)    //建立堆 
{
	int i;
	for (i = size / 2; i >= 1; i--)    //非叶节点最大序号值为size/2 
	{
		HeapAdjust(a, i, size);
	}
}

void HeapSort(int *a, int size)    //堆排序 
{
	int i;
	BuildHeap(a, size);
	for (i = size; i >= 1; i--)
	{
		cout<<a[1]<<" ";
		swap(a[1], a[i]);       //交换堆顶和最后一个元素,即每次将剩余元素中的最大者放到最后面 
				     //BuildHeap(a,i-1);  将余下元素重新建立为大顶堆 
		HeapAdjust(a, 1, i - 1);      //重新调整堆顶节点成为大顶堆
	}
}
int main(void)
{
	int nums[] = { -1,3,8,5,2,4,9 };
	HeapSort(nums, 6);
	system("pause");
	return 0;
}

 

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值