1. 程式人生 > >STL原始碼分析之heap大根堆

STL原始碼分析之heap大根堆

前言

在分析本節之前你至少應該對堆排序有所瞭解, 大根堆, 小根堆等. 本節分析的heap就是堆排序, 嚴格意義上來講heap並不是一個容器, 所以他沒有實現自己的迭代器, 也就沒有遍歷操作, 它只是一種演算法. 程式碼來自stl_heap.h.

heap分析

push插入元素

插入函式是push_heap. heap只接受RandomAccessIterator型別的迭代器.

注意, 在分析heap的時候最好還是自己畫一次, 畫一個數組一個二叉樹.

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*) { // 這裡傳入的是兩個迭代器的長度, 0, 還有最後一個數據 __push_heap(first, Distance((last - first) - 1), Distance(0), T(*(last - 1))); }

push的核心程式碼

template <class RandomAccessIterator, class Distance, class T>
void __push_heap(RandomAccessIterator first, Distance holeIndex,Distance topIndex, T value)
{ // 這裡就行二分, 因為二叉樹每一行都是2的倍數 Distance parent = (holeIndex - 1) / 2; // 這裡判斷的是當前沒有達到堆頂並且傳入的值大於根節點的值, 那就將根節點下移 while (holeIndex > topIndex && *(first + parent) < value) { // 將根節點下移 *(first + holeIndex) = *(first + parent); holeIndex = parent; parent = (holeIndex - 1) / 2; } // 將陣列插入到合適的位置, 可能是根也可能是葉 *(first + holeIndex) = value; }

pop彈出元素

pop操作其實並沒有真正意義去刪除資料, 而是將資料放在最後, 只是沒有指向最後的元素而已, 這裡arrary也可以使用, 畢竟沒有對陣列的大小進行調整. pop的實現有兩種, 這裡都羅列了出來, 另一個傳入的是cmp偽函式.

template <class RandomAccessIterator, class Compare>
inline void pop_heap(RandomAccessIterator first, RandomAccessIterator last,
                     Compare comp) {
    __pop_heap_aux(first, last, value_type(first), comp);
}
template <class RandomAccessIterator, class T, class Compare>
inline void __pop_heap_aux(RandomAccessIterator first,
                           RandomAccessIterator last, T*, Compare comp) {
  __pop_heap(first, last - 1, last - 1, T(*(last - 1)), comp,
             distance_type(first));
}
template <class RandomAccessIterator, class T, class Compare, class Distance>
inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last,
                       RandomAccessIterator result, T value, Compare comp,
                       Distance*) {
  *result = *first;
  __adjust_heap(first, Distance(0), Distance(last - first), value, comp);
}
template <class RandomAccessIterator, class T, class Distance>
inline void __pop_heap(RandomAccessIterator first, RandomAccessIterator last,
                       RandomAccessIterator result, T value, Distance*) {
  *result = *first;	// 因為這裡是大根堆, 所以first的值就是最大值, 先將最大值儲存.
  __adjust_heap(first, Distance(0), Distance(last - first), value);
}

pop的核心函式. 這裡主要之分析第一個版本.

pop彈出的是二叉樹的最下一排的資料.

template <class RandomAccessIterator, class Distance, class T>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex, Distance len, T value) 
{
    // holeIndex傳入的是0
  Distance topIndex = holeIndex;
    // secondChild是右孩子的一個節點
  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);
}
// cmpare版本只將比較修改成使用者定義的函式
template <class RandomAccessIterator, class Distance, class T, class Compare>
void __adjust_heap(RandomAccessIterator first, Distance holeIndex,
                   Distance len, T value, Compare comp) {
  Distance topIndex = holeIndex;
  Distance secondChild = 2 * holeIndex + 2;
  while (secondChild < len) {
    if (comp(*(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, comp);
}

make_heap函式, 將陣列變為堆存放.

template <class RandomAccessIterator>
inline void make_heap(RandomAccessIterator first, RandomAccessIterator last) {
  __make_heap(first, last, value_type(first), distance_type(first));
}
template <class RandomAccessIterator, class T, class Distance>
void __make_heap(RandomAccessIterator first, RandomAccessIterator last, 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)));
    if (parent == 0) return;
    parent--;
  }
}

sort, 堆排序其實就是每次將第一位資料彈出從而實現排序功能.

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

總結

heap沒有自己的迭代器,只要支援RandomAccessIterator的容器都可以作為Heap容器. heap最重要的函式還是poppush的實現.