STL源码剖析——heap算法

文章目录

  • push_heap
  • pop_heap
  • sort_heap
  • make_heap
  • 测试

但是heap并不属于STL容器组件,其帮助于priority queue.

binary heap就是一种完全二叉树。也就是整颗树除了最底层的叶节点之外

heap的所有元素遵循完全二叉树的排序规则,不提供遍历功能,也不提供迭代器。

STL源码剖析——heap算法_第1张图片

array的缺点是无法动态改变大小,而heap却需要,可用vector代替array

heap分为max-heap和min-heap

  • max-heap:每个结点的值都大于或等于其左右孩子结点的值,称为大顶堆
  • min-heap:每个结点的值都小于或等于其左右孩子结点的值,称为小顶堆。

STL提供的是max-heap,对max-heap进行分析。

  • 堆排序算法heapsort
  • make_heap、

push_heap

为了满足完全二叉树的条件,新加入的元素一定要放在最下一层作为叶节点,并填补在由左至右的第一个空格,也就是插入在vector的end()处。然后为了满足max-heap的条件,也就是每个节点大于等于其子节点的键值,执行一个上溯(percolate up)程序:将新节点与父节点比较,若比父节点大,就对换位置,如此一致上溯,知道不需对换或者根节点为止。

template <class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first, RandomAccessIterator last) {
	// 注意,调用该函数时候,新元素位于最后一个位置(last-1)。
	__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)));
	// (last-first)–1代表新元素的索引,0是堆首的索引,*(last - 1)是新加入的值
}
 
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; // percolate up:调整洞号,向上提升至父节点。
		parent = (holeIndex - 1) / 2;	// 新洞的父节点
	}    // 持续至顶端,或满足 heap 的次序特性为止。
	*(first + holeIndex) = value;	// 令洞值为新值。
}

pop_heap

max-heap最大值在根节点,pop操作取走根节点(vector的尾端节点)后,为了满足complete binary tree的条件后,必须割舍最下层右边的叶节点,并将其值重新安插至max-heap。

执行下溯程序:将空间节点与其较大子节点对调,并持续下放直到叶节点为止,然后将割舍的元素值设给这个已到达叶层的空洞节点,再执行一次上溯程序,即可。

pop_heap会把键值最大的元素放在底部容器的最尾端。

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));
	// pop动作的結果为底层容器的第一個元素。因此,首先设定欲调整值为尾值,然后將首值調至 
	// 尾节点(所以以上將迭代器result设为last-1)。然后重整 [first, last-1),
	// 使之重新成一個合格的 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。
}
 
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;
	}
 
	// 將欲调整值填入目前的洞号內。注意,此時肯定滿足次序特性。
	// 依侯捷之见,下面直接改為 *(first + holeIndex) = value; 应该可以。
	__push_heap(first, holeIndex, topIndex, value);
}

sort_heap

sort_heap不断地对heap进行pop_heap,使heap变成一个递增序列。

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

make_heap

将一段数据转为一个heap,其主要依据就是文章一开始提到的complete binary tree的隐式表述(implicit representation)

// 将 [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 Distance>
void __make_heap(RandomAccessIterator first,RandomAccessIterator last, T*,Distance*) 
{
    if (last - first < 2) 
        return; //如果长度为0或1,不必重新排列
 
    Distance len = last - first;
    // 找出第一个需要重排的子树头部,以parent标示出。由于任何叶节点都不需执行
    // perlocate 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--; // (即将重排之子树的)头部向前一个节点
    }
}

测试

make_heap(_First, _Last)//默认建大根堆,其中_First, _Last为指针
make_heap(_First,_Last,greater<int>())//建小根堆
make_heap(_First,_Last,less<int>())//建大根堆
push_heap (_First, _Last)//要现在vector中添加数据,其实相当于重新排序成大根堆
push_heap (_First, _Last,greater<int >())//注意比较函数一定要与make_heap建堆时一致
push_heap (_First, _Last,less<int >())//在大根堆中加入元素
pop_heap(_First, _Last)//弹出堆顶元素,实际是放到容器的最后,配合vector.pop_back()使用
pop_heap(_First, _Last,greater<int >())//同样要注意大小堆的问题
pop_heap(_First, _Last,greater<int >())
sort_heap(_First, _Last,greater<int >()) //堆排序,小根堆只能降序
sort_heap(_First, _Last,less<int >())//大根堆只能升序
#include 
#include 
#include 
using namespace std;
 
int main()
{
 
	int arr[9] = { 0,1,2,3,4,8,9,3,5 };
	vector<int> ivec(arr, arr + 9);
 
    //构造一个最大堆
	make_heap(ivec.begin(), ivec.end());
	for (int i = 0; i < ivec.size(); ++i)
		cout << ivec[i] << " ";     //9 5 8 3 4 0 2 3 1
	cout << std::endl;
 
	ivec.push_back(7); //将7添加到尾部
	for (int i = 0; i < ivec.size(); ++i)
		cout << ivec[i] << " ";     //9 5 8 3 4 0 2 3 1 7
	cout << std::endl;
 
	/*
	上面将7添加到尾部,破坏了完全二叉树的结构,因此需要调用此函数
	显式说明尾部有个元素需要插入到完全二叉树中,因此下面打印的为重新调整后的元素顺序
	*/
	push_heap(ivec.begin(), ivec.end()); 
	for (int i = 0; i < ivec.size(); ++i)
		cout << ivec[i] << " ";     //9 7 8 3 5 0 2 3 1 4
	cout << std::endl;
 
	pop_heap(ivec.begin(), ivec.end());//只是将根节点移到容器尾部,但是还没有删除
	cout << ivec.back() << std::endl;  //打印9
	ivec.pop_back();                        //将最后的尾节点9移除
 
	for (int i = 0; i < ivec.size(); ++i)
		cout << ivec[i] << " ";     //8 7 4 3 5 0 2 3 1
	cout << std::endl;
 
 
	sort_heap(ivec.begin(), ivec.end()); //对vector进行堆排序
	for (int i = 0; i < ivec.size(); ++i)
		cout << ivec[i] << " ";     //0 1 2 3 3 4 5 7 8
	cout << std::endl;
 
	return 0;
}

你可能感兴趣的:(STL源码剖析)