STL中haep的具体实现

写在前面

heap并不属于STL的容器,但是由它实现了priority_queue,那么如果我们想要了解priority_queue的话,我们就必须要了解heap是如何实现的

heap在没有比较函数约束的情况下,默认是max-heap,也就是我们所说的大顶堆

简单的说这就是一个在用树来维护的数组,但是这里的树一定是一棵完全二叉树,完全二叉树:对于深度为K的,有n个结点的二叉树,当且仅当其每一个结点都与深度为K的满二叉树中编号从1至n的结点一一对应时称之为完全二叉树。

程序

首先我们先看一下heap的push_heap操作,对于大顶堆的push插入操作,我们首先应将新插入的元素放到数组的末尾(相对应在树中的位置,也就是最后一个叶子节点的右边),然后将这个元素不断的向上回溯,直到到达合适的位置(大于其子节点,下于其父亲节点)

template <class RandomAccessIterator, class Distance, class T>                          //push_heap的具体实现
void __push_heap(RandomAccessIterator first, Distance holeIndex,
                 Distance topIndex, T value) {
  Distance parent = (holeIndex - 1) / 2;                                               //找出父亲节点
  while (holeIndex > topIndex && *(first + parent) < value) {                          //如果尚未到达顶点,并且父亲节点数值小于新值
    *(first + holeIndex) = *(first + parent);                                          //令洞的值为父值
    holeIndex = parent;                                                                //更新洞的位置     
    parent = (holeIndex - 1) / 2;                                                      //重新求的新的洞的位置
  }     
  *(first + holeIndex) = value;                                                        //最终值填到合适的洞中
}

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)));
}

template <class RandomAccessIterator>
inline void push_heap(RandomAccessIterator first, RandomAccessIterator last) {   
  __push_heap_aux(first, last, distance_type(first), value_type(first));              //后两个参数是得到距离以及数据的类型
}

这里十分形象的将我们移动的位置称为holeIndex,开始的洞节点是新插入节点的位置(数组的末尾的位置),只要没有到达根节点并且value大于其父亲节点,那么就要将洞节点和当前洞节点的父亲节点交换,使洞节点上移,直到到达根节点或者父亲节点大于value的数值的时候,我们将value放到对应洞的位置即结束。

pop_heap操作

 pop_heap操作,我们拿出来的一定是根节点,因为这里面最大的就是跟节点,这里就会空出一个节点,我们首先将数组最后一个数放到根节点的位置,后面我们称这个节点为node,首先遍历整棵树,将左右子节点中较大的数值先上升的父亲节点位置,不断地将node节点向下移动,直到移动到叶子节点,那么这里就要判断一下最后是否会出现只有左叶子节点而没有右叶子节点的情况,然后执行一遍向上回溯的过程,将value填入到相应的位置即可。

这里要说明一点,在vector实现的heap中,pop_heap操作并没有将数据删除,而是将最大的元素插入到了树的最后,我们可以通过vector提供的back()函数查看其数值,如果要删除数据需要使用vector提供的pop_back()操作函数

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) {                                                        //while这里只是不断地和子节点中较大的值互换,并没有将value的数值放入洞中                                        
    if (*(first + secondChild) < *(first + (secondChild - 1)))                       //比较洞节点之左右两个子值,然后以secondChild代表较大节点
      secondChild--;
    *(first + holeIndex) = *(first + secondChild);
    holeIndex = secondChild;                                                         //计算新的洞值
    secondChild = 2 * (secondChild + 1);                                             //计算新洞值的右子节点
  }
  if (secondChild == len) {                                                          //如果得到的子节点到达了之前交换的节点,没有右子节点,存在左子节点
    *(first + holeIndex) = *(first + (secondChild - 1));                             //令左子值为洞值,再令洞号下移至左子节点处
    holeIndex = secondChild - 1;
  }
  __push_heap(first, holeIndex, topIndex, value);                                    //1、将数值放入洞中  2、上面的操作中顺序可能出现错误,纠正错误(只是简单地将大的数值向上回溯,并没有考虑value,这里进行考虑)
}

template <class RandomAccessIterator, class T, class Distance>
inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last,
                       RandomAccessIterator result, T value, Distance*) {
  *result = *first;
  __adjust_heap(first, Distance(0), Distance(last - first), value);
}

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>
inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last) {                        //pop_heap算法
  __pop_heap_aux(first, last, value_type(first));                                                    //pop_heap算法一定是将底部容器的第一个元素,那么首先设定欲调整的值为尾值
                                                                                                     //然后将首值调整至尾节点
}

 sort_heap操作

sort_heap操作,在没有默认的情况下,得到的顺序是由小到大的顺序,在上面的pop_heap操作中,将最大的元素放到了数组的最后,那么,完全可以通过不断地调整尾部迭代器的位置,不断地将这个区间中的最大数值存放到当前区间的最后,那么我们就可以得到循序序列了

template <class RandomAccessIterator>
void sort_heap(RandomAccessIterator first, RandomAccessIterator last) {
  while (last - first > 1) pop_heap(first, last--);                      //不断地将开始的最大元素pop掉,并且不断缩短操作区间的长度,直到剩下最后一个,那么就会生成由小到大的排序实现
}

template <class RandomAccessIterator, class Compare>
void sort_heap(RandomAccessIterator first, RandomAccessIterator last,
               Compare comp) {
  while (last - first > 1) pop_heap(first, last--, comp);
}

make_heap操作

开始,我们输入的数组就是一个完全二叉树,那么我们只需要调整这个二叉树得到满足大顶堆条件的二叉树即可

 我们只需要将前半部分数组执行一遍pop_heap中的__adjust_heap操作就可得到得到相应的堆

template <class RandomAccessIterator, class Compare, class T, class Distance>
void __make_heap(RandomAccessIterator first, RandomAccessIterator last,
                 Compare comp, T*, Distance*) {
  if (last - first < 2) return;
  Distance len = last - first;
  Distance parent = (len - 2)/2;
    
  while (true) {
    __adjust_heap(first, parent, len, T(*(first + parent)), comp);
    if (parent == 0) return;
    parent--;
  }
}

template <class RandomAccessIterator, class Compare>
inline void make_heap(RandomAccessIterator first, RandomAccessIterator last,
                      Compare comp) {
  __make_heap(first, last, comp, value_type(first), distance_type(first));
}

使用数组模拟出来的heap实现

#include <iostream>
#include <list>
#include <stack>
#include <vector>

using namespace std;

void push_heap(int a[], int first, int holeIndex, int value)
{
	int parent = (holeIndex - 1) / 2;
	while (holeIndex > first && a[parent] < value)
	{
		a[holeIndex] = a[parent];
		holeIndex = parent;
		parent = (holeIndex - 1) / 2;
	}
	a[holeIndex] = value;
}
void __adjust_heap(int a[], int first, int holeIndex, int len, int value)
{
	int secondChild = 2 * (holeIndex + 1);
	while (secondChild < len)
	{
		if (a[secondChild - 1] > a[secondChild])
			secondChild--;
		a[holeIndex] = a[secondChild];
		holeIndex = secondChild;
		secondChild = 2 * (holeIndex + 1);
	}
	if (secondChild == len)
	{
		a[holeIndex] = a[secondChild - 1];
		holeIndex = secondChild - 1;
	}
	push_heap(a, first, holeIndex, value);
}

void pop_heap(int a[], int first, int last)
{
	int value = a[last - 1];
	a[last - 1] = a[first];                                    //将元素放到最后的位置,然后将最后位置上的数值重新插入到树中,并且树的长度-1       
	__adjust_heap(a, first, first, last - 1 - first, value);
}

void sort_heap(int a[], int first, int last)
{
	while (last - first > 1)
		pop_heap(a, first, last--);
}
void make_heap(int a[], int first, int last)
{
	if (last - first < 2) return;
	int len = last - first;
	int holeIndex = (len - 2) / 2;
	for (; holeIndex >= 0; holeIndex--)
		__adjust_heap(a, first, holeIndex, len, a[holeIndex]);

}
int main()
{
	int a[10] = { 1,2,3,4,5,6 };
	make_heap(a, 0, 5);
	for (int i = 0; i < 5; i++)
		cout << a[i] << " ";
	cout << endl;

	sort_heap(a, 0, 5);
	for (int i = 0; i < 5; i++)
		cout << a[i] << " ";
	cout << endl;

	
	system("pause");

}

参考完档

百度百科:完全二叉树

《STL源码剖析》侯捷著

猜你喜欢

转载自blog.csdn.net/li1615882553/article/details/83277847