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));
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章