關於C++中Hash的應用

本文只介紹我們在C++中如何使用Hash這種數據結構達到我們編程的目的,有關Hash的概念和實現不做詳談。

 

C++11新增了一類散列容器包括unordered_set, unordered_map, unordered_multiset, unordered_multimap, 即之前熟悉的hash_set, hash_map等。

這類容器底層以哈希表實現之,通過unordered_map介紹下這類容器的使用。

unordered_map 是一個模板類,需要我們提供5個魔板參數。依次爲:key值的類型, value值的類型,hash函數, 等價函數, 容器分配器。其中後三個有默認參數,那我們是不是隻需要提供前2個模板參數就可以使用了呢? 不一定。當我們使用的key爲內置類型時(如int, double, float, string等),後面三個默認模板參數在STL內有其特化版本,故可以直接進行使用。可一旦你的類爲自定義類型, 其中的hash和equal就得由你自己提供。其實也不難理解, 假設你的對象是一塊石頭,石頭怎麼進行hash, 石頭怎麼怎麼比大小呢?編譯器當然不知道,這就需要你告訴編譯器。下面我們對這2種情況分別舉例說明。

(一)、當key爲內置類型:

unordered_map<string, int> m_map;

當key爲內置類型, 僅需提供key與value的類型便可運用。 其中hash<string> 與 equal <int> 均有特化版本,分配器對整個容器進行內存管理,這三個參數均爲默認參數。

(二)、當key爲自定義類型:

比如我們簡單定義一個package類,裏面僅有名字,電話2項數據。

class package
{
public:
    string getName() const { return name; }
    long long getPhone() const { return phone; }

    package(string m_name = 0, long long m_pNum = 0);

    bool operator== (const package& p) const
    {    return name == p.name &&
               phone == p.phone; 
    }

private:
    string name;        
    long long phone;        
};

然後將原生hash包裝使用下:

namespace std
{
    template<>
    struct hash<package>
    {
        size_t operator() (const package& s) const noexcept
        {
            return  hash<decltype(s.getName())>()(s.getName()) +
                    hash<decltype(s.getPhone())>()(s.getPhone());
        }
    }; // 間接調用原生Hash.
}

或者可以藉助藉助boost庫的hash_value:

 namespace std
 {
     template<>
     struct hash<package>
     {
         size_t operator() (const package& s) const noexcept
         {
             auto t = make_tuple(s.getName(), s.getPhone());                                            
             size_t value = boost::hash_value(t);         
             return value;     // make_tuple(s.getName(), s.getPhone()) 等價於 tuple<string, long long>()(s.getName(), s.getPhone())
          }
     }; // 間接調用原生Hash.
  }

當我們把Hash函數(package的特化版本)和 等價函數 (操作符==重載)提供後, 便可使用自定義版本的unordered_map了:

unordered_map<package, int> m_map;

下面給出測試代碼:

測試環境: VS2017

#include <iostream>
#include <unordered_map>
#include <string>
#include <algorithm>
//#include <boost/functional/hash.hpp>   // 根據安裝路徑選擇hash.hpp
#include <tuple>

using namespace std;


class package
{
public:
    string getName() const { return name; }
    long long getPhone() const { return phone; }

    package(string m_name = 0, long long m_pNum = 0);

    bool operator== (const package& p) const
    {    return name == p.name &&
               phone == p.phone; 
    }

private:
    string name;        
    long long phone;        
};

package::package(string m_name, long long m_pNum)
    : name(m_name), phone(m_pNum) { }

namespace std
{
    template<>
    struct hash<package>
    {
        size_t operator() (const package& s) const noexcept
        {
            return  hash<decltype(s.getName())>()(s.getName()) +
                    hash<decltype(s.getPhone())>()(s.getPhone());
            
            //auto t = make_tuple(s.getName(), s.getPhone());                                            
            //size_t value = boost::hash_value(t);         
            //return value;     // make_tuple(s.getName(), s.getPhone()) 等價於 tuple<string, long long>()(s.getName(), s.getPhone())
        }
    }; // 間接調用原生Hash.
}

int main()
{
    unordered_map<package, int> m_map;

    package p1{ "Wang", 13399996666};
    package p2{ "Li", 13399993333};
    package p3{ "Zhang", 13399992222};
    package p4{ "Zhou", 13399991111 };
    package p5{ "Wang", 13399996666};
    package p6{ "Wang", 13366669999 };

    m_map[p1]++;
    m_map[p2]++;
    m_map[p3]++;
    m_map[p4]++;
    m_map[p5]++;
    m_map[p6]++;

    cout << m_map.bucket(p1) << " ";
    cout << m_map.bucket(p2) << " ";
    cout << m_map.bucket(p3) << " ";
    cout << m_map.bucket(p4) << " ";
    cout << m_map.bucket(p5) << " ";
    cout << m_map.bucket(p6) << " " << endl;

    return 0;
}

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章