STL源码分析之大顶堆

关于大顶堆和小顶堆这里就不再介绍了,这里通过STL再次回顾一下。

heap为了适应容器大小的不断变化,底层调用vector实现

关于heap的算法(这里是大顶堆)

 

push_heap()算法

为了满足完全二叉树的条件,新加入的元素一定是放在最下一层作为叶节点,并填补在由左至右的第一个空格,即插在vector的end()处

 STL源码分析之大顶堆_第1张图片

STL源码分析之大顶堆_第2张图片

 

我们通过上溯,将新节点与其父节点进行比较,如果键值比父节点的大,就对换位置,如此一直上溯,直到不需要对换或到了根节点为止。

以下即为push_heap的源码,两个迭代器表示heap底部容器(array或vector)的头尾,并且新的元素已经插入到底部容器的最尾端了。

template 
inline voidpush_heap(RandomAccessIterator first, RandomAccessIterator last) {
  __push_heap_aux(first, last,distance_type(first), value_type(first));
}
 
template 
inline void__push_heap_aux(RandomAccessIterator first,
                           RandomAccessIterator last, Distance*, T*) {
  __push_heap(first, Distance((last - first) -1), Distance(0),
              T(*(last - 1)));
}
 
//上面为了处理
//不管上面的内容,我们只要知道holeIndex就是插入的新节点在array/vector中的下标位置
template 
void__push_heap(RandomAccessIterator first, Distance holeIndex,
                 Distance topIndex, T value) {
  Distance parent = (holeIndex - 1) / 2;                //新插入节点的父节点下标
    //循环的一个判断条件就是父节点是存在的 且 父节点的键值小于新节点的键值
  while (holeIndex > topIndex && *(first + parent)< value) {
    //这里的操作本应该是交换父节点和新节点的位置(或值),因为value被一个变量指向了,所以这里只将新节点的值改为父节点的值,且将要处理的节点改为原来的父节点
    *(first + holeIndex) = *(first + parent);
holeIndex = parent;
//另求当前处理节点的父节点下标
    parent = (holeIndex - 1) / 2;
  }   
    //最后将这个指向新址的变量交给当前处理结束的节点
  *(first + holeIndex) = value;
}
 


 

pop_heap()算法

该操作取走根节点,即将整个堆最大的元素取走(其实是将此节点与整个堆的最后一个元素交换位置),然后进行下溯操作,调整整棵树

STL源码分析之大顶堆_第3张图片

STL源码分析之大顶堆_第4张图片

//这里我们只看核心部分
template 
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 
void__adjust_heap(RandomAccessIterator first, Distance holeIndex,
                   Distance len, T value) {
  Distance topIndex = holeIndex;            //当前大顶堆就是从根部开始调整
  Distance secondChild = 2 * holeIndex + 2;     //算出第二个儿子的下标位置。为什么要求第二个儿子的下标呢?因为可能这个节点只有1个儿子,可能都没有
  while (secondChild < len) {               //右子节点存在
    //如果左子节点的键值大于右子节点的键值
if (*(first +secondChild) < *(first + (secondChild - 1))) 
//那么可能要进行交换(有资格去跟父节点键值比较的)的就是左子
//因为是完全二叉树,底部容器是vector,所以求左子就是减一操作
      secondChild--;
    //令当前父节点的值直接等于该大的子节点
    //接下来直接修改要调整的节点为该子节点
    //原以为会令父子节点进行比较,可没有,是因为这个函数最后又调用了push_heap操作来调整这个节点。。。确实出乎我的意料了
    *(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);
}



make_heap()算法

即将一个无序的数组转化为heap形式

template 
inline voidmake_heap(RandomAccessIterator first, RandomAccessIterator last) {
  __make_heap(first, last, value_type(first),distance_type(first));
}
 
template 
void__make_heap(RandomAccessIterator first, RandomAccessIterator last,
                 Compare comp, T*, Distance*) {
    //0或1个元素就不操作了
  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--;
  }
}


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