1. 程式人生 > >STL之heap實現詳解(內部使用)

STL之heap實現詳解(內部使用)

堆無非就是分為最大堆(父節點大於等於子節點)和最小堆(父節點小於等於子節點)。STL裡面實現的都是最大堆。還有就是堆一般通過陣列實現。那麼問題就有兩個。假如不用陣列索引0的位置,那麼演算法會很簡單,那麼N的父節點索引就是N/2;N的左右子節點分別是2N和2N+1。假如使用了索引0位置,那麼N的父節點索引就是(N-1)/2;N的左右子節點分別是2N+1和2(N+1),STL實現使用了索引0位置,並且僅僅實現了最大堆。

插入元素後上浮操作

當元素已經插入到了vector之後,通過呼叫push_heap可以將元素上浮到vector合適的位置,使其滿足最大堆屬性,下面是上浮操作,基本和libevent和演算法第四版裡面實現一樣。

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)));//holeIndex尾部,插入值為尾部元素,從0處開始,所以這裡在vector元素插入之後,在呼叫push_heap。 } //這是堆實現的標準實現,libevent裡面也是這樣實現的。 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) {//尚未到頂端,並且父節點小於新值 *(first + holeIndex) = *(first + parent);//將洞值令為父值 holeIndex = parent;//更新holeindex,繼續向上層比較 parent = (holeIndex - 1) / 2;//更新父節點 } *(first + holeIndex) = value;//令洞值為新值,完成插入操作。直到存放新值的位置即可,不必每次交換位置 }

下沉操作後獲取最大元素

pop_push將最大堆中的最大元素放置到底部vector容器最尾端,尚未取走。可以使用底部容器提供的back讀取數值,也可以使用pop_back移除。這裡的實現操作也和前面敘述一樣。

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

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

//下沉,這裡的寫法沒有libevent裡面簡單,將最小值移到尾端
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) {

    if (*(first + secondChild) < *(first + (secondChild - 1)))//左子節點比右子節點大
      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);
}

堆排序操作

vector已經滿足最大堆屬性,那麼通過呼叫sort_heap可以將堆排序,過程就是不斷將最大的元素移至最尾端即可。

template <class RandomAccessIterator>
void sort_heap(RandomAccessIterator first, RandomAccessIterator last) {
  while (last - first > 1) pop_heap(first, last--);
}//堆排序,依次將最大元素放置尾端,即可實現堆排序了

構造堆

構造堆意思就是將vector容器裡面的已有資料,構造成滿足堆屬性的資料組合。構造過程在演算法上面說的很清楚,可以看前面的參考連結。就是通過下沉階段完成的。

//採用下沉操作,構造堆,和演算法書上面說的方法一樣。
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);//parent下沉到合適的位置
    if (parent == 0) return;//構造完成
    parent--;
  }
}

使用例子

#include <iostream>
#include <list>
#include <algorithm>
#include <iterator>
#include <deque>
#include <algorithm>
#include<stdio.h>
using namespace std;
int main()
{
    int a[9] = {0,1,2,3,4,8,9,3,5};
    make_heap(a , a+9 );//將a以堆排放,[a,a+9)左閉合右開
    sort_heap(a , a+9 );//進行堆排序
    for(int i = 0 ; i < 9 ; i++)
        printf("%d  " , a[i]);
    printf("\n");
    make_heap(a , a+9);
    pop_heap(a , a+9);//將最大元素放到尾端
    printf("%d\n",a[8]);

    vector<int> ivec(a ,a+9);//通過a初始化ivec
    make_heap(ivec.begin() , ivec.end());//在vector構造堆,使vector成堆有序
    for(int i = 0 ; i < 9 ; i++)
        printf("%d  " , ivec[i]);
    printf("\n");
    ivec.push_back(7);//先插入,然後通過pop_heap將尾部元素進行上浮操作
    push_heap(ivec.begin() , ivec.end());
    for(int i = 0 ; i < ivec.size() ; i++)
        printf("%d  " , ivec[i]);
    printf("\n");
    pop_heap(ivec.begin() , ivec.end());//將堆頂元素移到尾端
    std::cout << ivec.back() << endl;//讀取元素
    ivec.pop_back();//彈出最後元素

    for(int i = 0 ; i < ivec.size() ; i++)//列印堆有序vector
        printf("%d  " , ivec[i]);
    std::cout << endl;

    sort_heap(ivec.begin() , ivec.end());//堆有序的vector進行堆排序
    for(int i = 0 ; i < ivec.size() ; i++)//列印排序後的vector
        printf("%d  " , ivec[i]);
    std::cout << endl;
 }

這裡寫圖片描述
要點:堆是通過陣列或者底層list、vector、deque容器實現。pop_heap、push_heap、make_heap、sort_heap都是在已存在的陣列或者底層容器物件上面操作。特別對於push_heap操作得現在尾部插入元素,才可以進行。make_heap是將堆頂元素移至尾端。通過看原始碼,可以看得比較清楚,很簡單的。