STL笔记之heap

heap并不归属于STL容器组件,作为priority queue的底层机制。

priority queue允许用户以任何次序将任何元素推入容器内,但取出时一定是从优先权最高(也就是数值最高)的元素开始取。

以array表述tree:将array的0号元素保留(或设为无限大值或无限小值),那么当完全二叉树中的某个节点位于array的i处时,其左子节点必位于array的2i处,其右子节点必位于2i+1处,其父节点必位于int(i/2)处,通过简单的位置规则,array可以轻易实现完全二叉树。这种表述方法称为隐式表述法。

这样构成一个priority queue需要的工具就很简单:一个vector(因可动态改变大小)和一组heap算法(用来插入元素、删除元素、取极值,将一整组数据排列成一个heap)。

heap算法

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*) {
	__push_heap(first, Distance((last - first) - 1), Distance(0), T(*(last - 1)));
	//根据隐式表述heap的结构特性,新值必置于底部容器的最尾端,即第一个洞号(last-first)-1
}

//以下这组push_heap()不允许指定“大小比较标准”
template<class RandomAccessIterator,class Distance,class T>
void __push_heap(RandomAccessIterator first, Distance holeIndex,
	Distance topIndex, T value) {
	Distance parent = (holeIndex - 1) / 2;//找出父节点
	while (holeIndex > topIndex&&*(first + parent) < value) {
		//尚未到达顶端,且父节点小于新值
		//由于以上使用operator<,可知STL heap是一种max-heap
		*(first + holeIndex) = *(first + parent);//令洞值为父值
		holeIndex = parent;//调整洞号,向上提升至父节点
		parent = (holeIndex - 1) / 2;//新洞的父节点
	}//持续至顶端,或满足heap的次序特性为止
	*(first + holeIndex) = value;//令洞值为新值,完成插入操作
}
pop_heap算法

pop操作取走根节点(其实是设至底部容器vector的尾端节点)后,为满足完全二叉树的条件,必须割舍最下层最右边的叶节点,并将其值重新安插至max-heap(因此有必要重新调整heap结构)。

为满足max-heap次序特性(每个节点的键值都大于或等于其较大子节点的键值),执行percolate down程序:将空间节点和其较大子节点对调,并持续下方,直至叶节点为止。然后将前述被割舍之元素值设给这个已到达叶层的空洞节点,再对它执行一次percolate up程序。

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));
	//以上根据隐式表述heap的次序特性,pop操作的结果应为底部容器的第一个元素。
	//因此,首先欲调整值为尾值,然后将首值调至尾节点(所以以上将迭代器result
	//设为last-1).然后重整[first,last-1),使之重新成一个合格的heap
}

//以下这组__pop_heap()不允许指定“大小比较标准”
template<class RandomAccessIterator,class T,class Distance>
inline void __pop_heap(RandomAccessIterator first,
	RandomAccessIterator last,
	RandomAccessIterator result,
	T value, Distance*) {
	*result = *first;//设定尾值为首值,于是尾值即为欲求结果,可以底层容器之pop_back()取出尾值
	__adjust_heap(first, Distance(0), Distance(last - first), value);
	//以上欲重新调整heap,洞号为0(即树根处),欲调整值为value(原尾值)
}

//以下这组__adjust_heap()不允许指定“大小比较标准”
template <class RandomAccessIterator,class Distance,class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
	Distance len, T value) {
	Distance topIndex = holeIndex;
	Distance secondChild = 2 * holeIndex + 2;//洞节点之右子节点
	while (secondChild < len) {
		//比较洞节点之左右两个子值,然后以secondChild代表较大子节点
		if (*(first + secondChild) < *(first + (secondChild - 1))
			secondChild--;
		//percolate down:令较大子值为洞值,再令洞号下移至较大子节点处
		*(first + holeIndex) = *(first + secondChild);
		holeIndex = secondChild;
		//找出新洞节点的右子节点
		secondChild = 2 * (secondChild + 1);
	}
	if (secondChild == len)
	{
		//没有右子节点,只有左子节点
		//percolate down:令左子值为洞值,再令洞号下移至左子节点处
		*(first + holeIndex) = *(first + (secondChild - 1));
		holeIndex = secondChild - 1;
	}
	//此时可能尚未满足percolate up操作
	__push_heap(first, holeIndex, topIndex, value);
}

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

sort_heap算法

既然每次pop_heap可获得heap中键值最大的元素,如果持续对整个heap做pop_heap操作,每次将操作范围从后向前缩减一个元素,当整个程序执行完毕,便可获得一个递增序列。

sort_heap()接受两个迭代器,用来表现一个heap底部容器的头尾。如果不符合这个条件,sort_heap执行结果未知。排序后,原来的heap就不是一个合法的heap了。

//以下这个sort_heap()不允许指定“大小比较标准”
template<class RandomAccessIterator>
void sort_heap(RandomAccessIterator first,
	RandomAccessIterator last) {
	//以下,每执行一次pop_heap(),极值即被放在尾端
	//扣除尾端再执行一次pop_heap(),次极值又被放在新尾端,一直下去,最后得排序结果
	while (last - first > 1)
		pop_heap(first, last--);
}
make_heap算法

这个算法用来将一段现有的数据转化为一个heap,主要依据完全二叉树的隐式表述。

//将[first,last)排列为一个heap
template<class RandomAccessIterator>
inline void make_heap(RandomAccessIterator first,
	RandomAccessIterator last) {
	__make_heap(first, last, value_type(first), distance_type(first));
}

//以下这组make_heap()不允许指定“大小比较标准”
template<class RandomAccessIterator,class T,class distace>
void __make_heap(RandomAccessIterator first,
	RandomAccessIterator last, T*, Distance*) {
	if (last - first < 2)return;//如果长度为0或1,不必重新排列
	Distance len = last - first;
	//找出第一个需要重排的子树头部,以parent标示出,由于任何叶节点都不需执行
	//percolate down,所以有以下计算,parent命名不佳,名为holeIndex更好
	Distance parent = (len - 2) / 2;

	while (true) {
		//重排parent为首的子树,len是为了让__adjust_heap()判断操作范围
		__adjust_heap(first, parent, len, T(*(first + parent)));
		if (parent == 0)return;//走完根节点就结束
		parent--;//(已重排之子树的)头部向前一个节点
	}
}

迭代器

heap的所有元素都必须遵循特别的(完全二叉树)排列规则,所以heap不提供遍历功能,也不提供迭代器。


猜你喜欢

转载自blog.csdn.net/s_hit/article/details/79519247