STL heap

注意:heap并不是STL的容器或者配接器,但却是priority queue的底层实现。并且heap运用的是常用的最大堆以及堆排序的方法,所以值得一看

一、最大堆和隐式表示法

  • heap的数据结构是一个最大堆,最大堆就是一个完全二叉树,并且每个父节点都大于它的子节点(左右子节点的大小没有限制,不需要大的子节点一定要放哪边)
  • 隐式表示法就是一个二叉树,可以用一个数组来表示,如图所示
    在这里插入图片描述

任意一个节点的父子节点可以计算得到:
设某个节点索引值为index,则节点的左子节点索引为: 2*index+1
右子节点索引为: 2*index+2
父节点索引为: (index-1)/2

heap中总共有四个函数,push_heap,pop_heap,sort_heap,make_heap,下面依次介绍

二、push_heap

作用:将数组中最后一个数据插入到最大堆中
代码示例

int a[] = { 68, 31, 65, 21, 24, 32, 26, 19, 16, 13 };
vector<int> b(a, a + 10);//创建一个最大堆,并放入数组中
b.push_back(50);//把新加入的元素放在数组末尾
push_heap(b.begin(), b.end());//调用push_heap将数组末尾的元素放到合适的位置
for (int i = 0; i < (int)b.size(); i++)
	cout << b[i] << "  ";

结果如下:
在这里插入图片描述
过程图解
将50依次和自己的父节点比较,如果比父节点大,就和父节点交换位置,如果比父节点小,就结束这个过程。push_heap就是执行这个过程。
在这里插入图片描述

源码解析

template <class _RandomAccessIterator, class _Distance, class _Tp>
void 
__push_heap(_RandomAccessIterator __first,
            _Distance __holeIndex, _Distance __topIndex, _Tp __value)
            //这是真正算法执行的地方了,__holeIndex表示需要插入的值的当前位置,value就是需要插入最大堆的值了
{
  _Distance __parent = (__holeIndex - 1) / 2;//找到父节点
  while (__holeIndex > __topIndex && *(__first + __parent) < __value) {
  //如果没有到堆顶或者需要插入的值大于父节点值的时候,循环都不会停止
    *(__first + __holeIndex) = *(__first + __parent);//因为执行到这一步,肯定是value>*(__first + __parent),所以就把父节点的值下移到子节点
    __holeIndex = __parent;//需要插入的值此时移到原父节点处
    __parent = (__holeIndex - 1) / 2;//找到新位置的父节点
  }    
  *(__first + __holeIndex) = __value;//将新值填入到最后的找到的合适位置,相当于__value是新值的临时存放位置,__holeIndex是新值索引的临时存放位置
}

template <class _RandomAccessIterator, class _Distance, class _Tp>
inline void 
__push_heap_aux(_RandomAccessIterator __first,
                _RandomAccessIterator __last, _Distance*, _Tp*)
{
  __push_heap(__first, _Distance((__last - __first) - 1), _Distance(0), 
              _Tp(*(__last - 1)));
              //一般_Distance都是ptrdiff_t,这里就是把(__last - __first) - 1强转成ptrdiff_t类型。
              //(__last - __first) - 1表示的是最后一个元素到堆顶的距离,也就是数组中最后一个元素的下标了
              //_Distance(0)就是堆顶的下标了
}

template <class _RandomAccessIterator>
inline void 
push_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
//传入首尾迭代器,会认为__first~__last-2都是已经排好的最大堆,而__last-1就是等待插入的元素
{
  __push_heap_aux(__first, __last,
                  __DISTANCE_TYPE(__first), __VALUE_TYPE(__first));
}

三、pop_heap

作用:将最大堆中的根节点移出最大堆,移到数组的最后,所以相当于数组的尺寸没变,但是最大堆尺寸减一
代码示例

int a[] = { 68, 31, 65, 21, 24, 32, 26, 19, 16, 13 };
vector<int> b(a, a + 10);
b.push_back(50);
push_heap(b.begin(), b.end());
pop_heap(b.begin(), b.end());
for (int i = 0; i < (int)b.size(); i++)
	cout << b[i] << "  ";

结果如图所示:
在这里插入图片描述
过程图解
先将根节点放到数组最后一个位置,然后将该位置原元素,也就是24先临时保存起来
然后从根节点开始,比较它的子节点和24这三个数值,哪个更大,如果是其中一个子节点更大,那就把该子节点放到空着的根节点位置,这时就空出来一个子节点的位置(如步骤三中所示),就将24继续和空出来的节点的子节点比较,如果还是子节点更大,继续执行之前的流程,如果是24更大,就将24填入空着的节点。
在这里插入图片描述
源码解析

template <class _RandomAccessIterator, class _Distance, class _Tp>
void 
__adjust_heap(_RandomAccessIterator __first, _Distance __holeIndex,
              _Distance __len, _Tp __value)
             //__holeIndex索引所在位置表示为空缺值,先让子节点不断上提弥补空缺值,然后将value加入到重新排列过的最大堆中。
{
  _Distance __topIndex = __holeIndex;//__topIndex是根节点的索引
  _Distance __secondChild = 2 * __holeIndex + 2;//找到__holeIndex的右子节点
  while (__secondChild < __len) {//直到从根节点循环到最后一层才结束
  	//找到左右子节点中较大的那个值,将其索引值存入__secondChild
    if (*(__first + __secondChild) < *(__first + (__secondChild - 1)))
      __secondChild--;
      //将__holeIndex的左右子节点中较大值填入__holeIndex位置
    *(__first + __holeIndex) = *(__first + __secondChild);
    __holeIndex = __secondChild;//__holeIndex向下移动到空着的子节点位置
    __secondChild = 2 * (__secondChild + 1);//重新寻找右子节点
  }
  if (__secondChild == __len) {//这种情况下是右子节点不在堆的范围中,但是左子节点在,所以将左子节点上提
    *(__first + __holeIndex) = *(__first + (__secondChild - 1));
    __holeIndex = __secondChild - 1;
  }
  //将__value重新加入到最大堆中去
  //并且此时初始位置是__holeIndex,这个位置的元素刚好被提到父节点去了
  __push_heap(__first, __holeIndex, __topIndex, __value);
}

template <class _RandomAccessIterator, class _Tp, class _Distance>
inline void 
__pop_heap(_RandomAccessIterator __first, _RandomAccessIterator __last,
           _RandomAccessIterator __result, _Tp __value, _Distance*)
           //__result是指向数组中的最后一个元素,用来存储根节点
           //__value是原数组最后一个元素的值
{
  *__result = *__first;
  __adjust_heap(__first, _Distance(0), _Distance(__last - __first), __value);
  //由于第三个参数是len,最大堆的尺寸,所以不需要-1,如果是最后一个元素的索引,就需要-1了
}

template <class _RandomAccessIterator, class _Tp>
inline void 
__pop_heap_aux(_RandomAccessIterator __first, _RandomAccessIterator __last,
               _Tp*)
{
  __pop_heap(__first, __last - 1, __last - 1, 
             _Tp(*(__last - 1)), __DISTANCE_TYPE(__first));
             //注意这边传入的参数把最大堆尺寸由原来的__last - __first - 1变为了__last - __first - 2
             //相当于缩小了最大堆的尺寸
}

template <class _RandomAccessIterator>
inline void pop_heap(_RandomAccessIterator __first, 
                     _RandomAccessIterator __last)//传入首尾迭代器
                     //将__first指向的元素放到__last-1,并重新调整最大堆的位置。
{
  __pop_heap_aux(__first, __last, __VALUE_TYPE(__first));
}

四、sort_heap

作用:因为最大堆是存储在数组中的,所以利用最大堆把数组中的元素排序
代码示例

int a[] = { 68, 31, 65, 21, 24, 32, 26, 19, 16, 13 };
vector<int> b(a, a + 10);
b.push_back(50);
push_heap(b.begin(), b.end());
sort_heap(b.begin(), b.end());
for (int i = 0; i < (int)b.size(); i++)
	cout << b[i] << "  ";

结果如图所示:
在这里插入图片描述
过程图解
就是不断使用pop_heap,每次都把堆中最大的值放到堆得末尾,然后找到剩下元素中最大的值放到根节点
在这里插入图片描述
在这里插入图片描述
源码解析

void sort_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
  while (__last - __first > 1)//每执行一次,last都会减一,最大堆的规模都会减小一,所以知道最大堆上只有一个元素时,说明已经排序完毕。
    pop_heap(__first, __last--);
}

五、make_heap

作用:就是将一个无序的数组排列成最大堆的形式
代码示例

int a[] = { 24, 32, 31, 65, 26, 68, 19, 16, 21, 13 };//这里是无序的
vector<int> b(a, a + 10);
make_heap(b.begin(), b.end());
for (int i = 0; i < (int)b.size(); i++)
	cout << b[i] << "  ";

结果如图所示:
在这里插入图片描述
可以看到经过make_heap,就排列成最大堆的模式了。
源码解析

template <class _RandomAccessIterator, class _Tp, class _Distance>
void 
__make_heap(_RandomAccessIterator __first,
            _RandomAccessIterator __last, _Tp*, _Distance*)
{
  if (__last - __first < 2) return;//如果元素个数为1或者0就可以
  _Distance __len = __last - __first;
  _Distance __parent = (__len - 2)/2;//计算出最后一个元素__len - 2的父节点,然后从__parent 节点往前,就都是由子节点的了。
    
  while (true) {
    __adjust_heap(__first, __parent, __len, _Tp(*(__first + __parent)));//从最后一个父节点开始,每次把父
    //节点下沉,相当于保证了每个父节点都比自己最相邻的那两个子节点大,然后依次往顶层循环,这样保证所有的父节
    //点都比自己的子节点大了。
    if (__parent == 0) return;
    __parent--;
  }
}

template <class _RandomAccessIterator>
inline void 
make_heap(_RandomAccessIterator __first, _RandomAccessIterator __last)
{
  __make_heap(__first, __last,
              __VALUE_TYPE(__first), __DISTANCE_TYPE(__first));
}
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值