淺析STL算法中的堆排序

堆結構簡述面試


    瞭解過數據結構的人,應該對堆結構不陌生,堆的底層是使用數組來實現的,但卻保持了二叉樹的特性。堆分爲兩種,最大堆和最小堆,以最大堆爲例,最大堆保持了根結點大於兩個左右兩個孩子,同時全部子樹一次類推。因爲堆底層是數組結構,這裏從跟結點開始,按照層序依次走到最後一個結點,結點下標分貝爲0~N-1。結構以下圖:
算法

wKiom1hBX3-AjFBGAABcjjuvTHY285.png

    上圖中,紫色表示的是該元素在數組中的下標,能夠看到,每一個結點的值老是大於它的左右孩子,這裏並無規定左右孩子的大小關係,也沒有規定不是同一棵樹之間結點的大小關係。這就是最大堆。同時這裏能夠注意到,若是是大堆,根節點必定是樹中最大的結點,一樣,若是是小堆,根節點必定是樹中最小的結點。數組

    堆結構在排序領域,佔據着必定的低位,可是STL中並無直接給出堆結構,而是把堆的相關算法,寫在了 algorithm 裏面。在這裏我不會過多的去模擬實現一個堆,今天要說的是關於STL中給出的堆算法如何使用。
數據結構


algorithm 中的堆算法dom

    

    在STL中,關於堆,給出了一下幾種算法。
ide

    ★make_heap    
函數

    ★push_heapui

    ★pop_heapspa

    ★sort_heap指針

    這裏,首先給出一個利用STL中堆算法的實例


#include <algorithm>
#include <vector>
void test()
{
    int arr[] = {1,2,3,4,5,6,7};
    vector<int> vec(arr, arr+7);         // 左開右閉類型
    make_heap(vec.begin(), vec.end());   // 默認建大堆
    pop_heap(v1.begin(), v1.end());      
    v1.pop_back();                       
    sort_heap(vec.begin(), vec.end());   // 堆排序
    for(size_t i = 0; i < vec.size(); i++)
        cout<<vec[i]<<" ";
    cout<<endl;
}

    上面代碼,首先用一個數組構建了一個向量,以後對向量vec建堆,pop出調整以後的向量中第一個元素,並進行調整,而後進行堆排序,最後將結果打印出來,打印結果以下:

wKioL1hBZBfhxxc4AAASiXstCFs740.png

    看完了heap算法的基本使用,如今,咱們瞭解一下,STL中是如何提供這些算法接口的。

一、make_heap

default (1)
template <class RandomAccessIterator>
  void make_heap (RandomAccessIterator first, RandomAccessIterator last);
custom (2)
template <class RandomAccessIterator, class Compare>
  void make_heap (RandomAccessIterator first, RandomAccessIterator last,
                  Compare comp );

    前面提到過,堆分爲大堆和小堆,咱們創建堆的時候就須要肯定,但剛剛例子中,咱們並無去指定大小堆。STL中規定,沒有指定的話,默認大堆結構。從上面關於make_heap的兩套接口能夠看到,第一種是默認的,沒有提供指定大小堆的接口,第二種這裏有實現。咱們可經過仿函數的結構,實現這裏的傳參。

    對剛剛給出的例子,咱們如今能夠解釋另一個問題,爲何咱們要進行堆排序,首先要構建vector呢?由於堆的底層實現就是經過數組的形式,而vector是堆數組的高級封裝,這些庫中實現好的容器給出了不少實用的接口和迭代器,使用起來的確能夠方便很多。make_heap給出的接口中,前兩個是任意類型的迭代器(固然,這裏也能夠直接傳遞數組的指針),不管是make_heap仍是其餘三個函數,這裏的迭代器區間老是左閉右開的,這是個須要注意的地方。

    接下來咱們來介紹仿函數這裏的實現。仍是上面的例子,如何讓上面剪子一個最小堆呢?

//仿函數結構:

struct Compare
{
   bool operator()(int num1, int num2)
          {
      return num1 > num2;
   }
};

// 建堆時,參數傳遞改成

make_heap(vec.begin(), vec.end() , Compare());   // 建小堆

    注意,上面我寫的是num1 > num2,這樣建出來頂點是小堆。關於make_heap就說到這裏。


二、push_heap  pop_heap 

    push表示的是向vector中插入一個元素,但這裏它並非直接插入元素,準確的說,它的功能只是作調整,在push_heap以前,首先須要調用vec.push_back(x),向vector尾部插入一個元素,而後在調用push_heap函數進行調整,使得整個樹從新知足堆結構。

    pop_heappush_heap相似,一樣沒有直接改變vector中元素個數的能力。對於堆而言,pop要作的是將vector的第一個元素扔出去,爲了避免直接破壞樹的結構,這裏先調用pop_heap函數,將堆中的最大值或最小值放到vector的尾部,同時進行一次調整,使得堆結構依然成立,而後調用vec.pop+back()函數,將最後一個元素從vector中剔除。

    這就是插入和刪除的整個過程。

三、sort_heap

    顧名思義,sort_heap就是進行堆排序的意思,對堆結構進行排序,獲得的結果就是vector中的元素變得有序。這裏,構建大堆,排序結果是升序的,構建小堆,獲得的排序結果是降序的

    

    上面就是關於堆排序的基本算法,關於C++11新引入的兩個函數,這裏不作討論。

    關於STL中的堆結構,這裏有幾點仍是須要注意的:

    一、由於堆結構,是創建在vector上的結構,因此若是要進行堆排序,整個過程當中至少一次建堆(make_heap)的過程。

    二、當建堆完成以後,若是再向vector中插入元素,須要調用 push_heap(v1.begin(), v1.end()) 進行一次向上調整;若是從vector中Pop出一個元素,須要調用 pop_heap(v1.begin(), v1.end()) 進行一次向下調整。

    若是沒有調整,當調用 sort_heap(v1.begin(), v1.end()) 函數的過程當中,會出現因爲堆不合法引發的斷言錯誤。

    三、不可讓vector屢次尾插,以後再屢次向上調整,會形成堆混亂,排序時也會出現斷言錯誤。固然,屢次插入或刪除以後,能夠再次進行從新建堆,只不過消耗的時間代價會比較大

 

堆結構實際應用

   

接下來,看一道面試題:

CVTE:統計公司員工最喜歡吃的前K種水果

   有過上面的基礎,我這裏直接給出demo

#include <algorithm>
#include <map>
#include <string>
#include <vector>

struct Min
{
            bool operator()(pair<string, int> p1, pair<string, int> p2)
            {
                        return p1.second > p2.second;
            }
};
void HeapSort()
{
            vector<string> v1 = { "蘋果", "香蕉", "蘋果"
                        , "蘋果", "蘋果", "香蕉"
                        , "蘋果", "獼猴桃", "草莓" };
            map<string, int> fruit;
            
            //用map統計次數
            for (size_t i = 0; i < v1.size(); i++)
            {
                        fruit[v1[i]]++;
            }

            // 用heap排序
            vector<pair<string, int>> vec;
            map<string, int>::iterator it = fruit.begin();
            //while (it != fruit.end())
            //{
            //         vec.push_back(pair<string, int>(it->first, it->second));
            //         it++;
            //}
            vec.insert(vec.begin(), fruit.begin(), fruit.end());
            make_heap(vec.begin(), vec.end(), Min());
            sort_heap(vec.begin(), vec.end(), Min());
            int K = 3;
            for (size_t i = 0; (i < K) && (i < vec.size()); i++)
            {
                        cout << vec[i].first <<"--"<<vec[i].second<< endl;
            }
}
int main()
{
            HeapSort();
            system("pause");
            return 0;
}


    堆排序是數據結構中一塊很重要的內容,若是是在實際開發中,更加推薦的是熟悉STL中給出的算法,正如上文講到的內容。若是是對於初學者,這裏仍是推薦對堆結構和算法的底層實現有一個更加深入的認識,對於堆的調整算法,不能否認,是數據結構中較爲重要的一部分。下一次,會對堆結構進行模擬實現,更加深刻地瞭解底層結構。

相關文章
相關標籤/搜索