簡體   English   中英

在 C++ 中使用 HashMap 的最佳方法是什么?

[英]What is the best way to use a HashMap in C++?

我知道 STL 有一個 HashMap API,但我找不到任何好的和詳盡的文檔以及關於此的好例子。

任何好的例子將不勝感激。

標准庫包括有序和無序映射( std::mapstd::unordered_map )容器。 在有序映射中,元素按鍵排序,插入和訪問在O(log n)中 通常,標准庫內部使用紅黑樹作為有序映射。 但這只是一個實現細節。 在無序映射中,插入和訪問在O(1)中。 它只是哈希表的另一個名稱。

(有序) std::map的示例:

#include <map>
#include <iostream>
#include <cassert>

int main(int argc, char **argv)
{
  std::map<std::string, int> m;
  m["hello"] = 23;
  // check if key is present
  if (m.find("world") != m.end())
    std::cout << "map contains key world!\n";
  // retrieve
  std::cout << m["hello"] << '\n';
  std::map<std::string, int>::iterator i = m.find("hello");
  assert(i != m.end());
  std::cout << "Key: " << i->first << " Value: " << i->second << '\n';
  return 0;
}

輸出:

23
Key: hello Value: 23

如果您需要在容器中進行排序並且可以使用O(log n)運行時,那么只需使用std::map

否則,如果你真的需要一個哈希表(O(1)insert / access),請查看std::unordered_map ,它與std::map API類似(例如在上面的例子中你只需要搜索和替換使用unordered_map map )。

unordered_map容器是隨C ++ 11標准版本引入的。 因此,根據您的編譯器,您必須啟用C ++ 11功能(例如,在使用GCC 4.8時,您必須將-std=c++11添加到CXXFLAGS)。

甚至在C ++ 11發行版GCC支持unordered_map - 在命名空間std::tr1 因此,對於舊的GCC編譯器,您可以嘗試使用它:

#include <tr1/unordered_map>

std::tr1::unordered_map<std::string, int> m;

它也是boost的一部分,即你可以使用相應的boost-header來獲得更好的可移植性。

hash_map是用於標准化目的的較舊的非標准化版本,稱為unordered_map (最初在TR1中,並且自C ++ 11以來包含在標准中)。 顧名思義,它與std::map的不同之處主要在於無序 - 例如,如果你遍歷從begin()end()的映射,你可以按鍵1順序獲取項目,但是如果你迭代通過從begin()end()unordered_map ,您可以獲得或多或少的任意順序的項目。

通常期望unordered_map具有恆定的復雜性。 也就是說,插入,查找等通常基本上花費固定的時間量,而不管表中有多少項。 std::map復雜性與存儲的項目數量呈對數關系 - 這意味着插入或檢索項目的時間會增長,但隨着地圖變大, 速度會變慢 例如,如果查找100萬個項目中的一個需要1微秒,那么您可以預期查找200萬個項目中的一個需要大約2微秒,400萬個項目中的一個項目需要3微秒,800萬個項目中的一個項目需要4微秒物品等

從實際的角度來看,這並非真正的整個故事。 本質上,簡單的哈希表具有固定的大小。 使其適應通用容器的可變大小要求有點不重要。 結果,(可能)增長表(例如,插入)的操作可能相對較慢(即,大多數相當快,但周期性地會慢得多)。 查找不能改變表的大小,通常要快得多。 因此,與插入次數相比,當您執行大量查找時,大多數基於散列的表往往處於最佳狀態。 對於插入大量數據的情況,然后遍歷表一次以檢索結果(例如,計算文件中唯一單詞的數量), std::map可能同樣快,甚至可能甚至更快(但同樣,計算復雜性不同,因此也可以取決於文件中唯一字的數量)。


1創建地圖時,順序由第三個模板參數定義,默認情況下為std::less<T>

這是一個更完整,更靈活的示例,不會忽略生成編譯錯誤所必需的包含:

#include <iostream>
#include <unordered_map>

class Hashtable {
    std::unordered_map<const void *, const void *> htmap;

public:
    void put(const void *key, const void *value) {
            htmap[key] = value;
    }

    const void *get(const void *key) {
            return htmap[key];
    }

};

int main() {
    Hashtable ht;
    ht.put("Bob", "Dylan");
    int one = 1;
    ht.put("one", &one);
    std::cout << (char *)ht.get("Bob") << "; " << *(int *)ht.get("one");
}

對於鍵仍然沒有特別有用,除非它們被預定義為指針,因為匹配值不會做! (但是,由於我通常使用字符串作為鍵,因此在鍵的聲明中將“string”替換為“const void *”應解決此問題。)

有證據表明std::unordered_map在GCC stdlibc ++ 6.4中使用了哈希映射

這在以下網址提到: https//stackoverflow.com/a/3578247/895245但是在下面的答案中: 在C ++中的std :: map里面有什么數據結構? 我通過以下方式為GCC stdlibc ++ 6.4實現提供了進一步的證據:

  • GDB步驟調試進入類
  • 性能特征分析

以下是該答案中描述的性能特征圖的預覽:

在此輸入圖像描述

如何使用unordered_map自定義類和哈希函數

這個答案指出: C ++ unordered_map使用自定義類類型作為鍵

摘錄:平等:

struct Key
{
  std::string first;
  std::string second;
  int         third;

  bool operator==(const Key &other) const
  { return (first == other.first
            && second == other.second
            && third == other.third);
  }
};

哈希函數:

namespace std {

  template <>
  struct hash<Key>
  {
    std::size_t operator()(const Key& k) const
    {
      using std::size_t;
      using std::hash;
      using std::string;

      // Compute individual hash values for first,
      // second and third and combine them using XOR
      // and bit shifting:

      return ((hash<string>()(k.first)
               ^ (hash<string>()(k.second) << 1)) >> 1)
               ^ (hash<int>()(k.third) << 1);
    }
  };

}

對於我們這些試圖弄清楚如何 hash 我們自己的類同時仍然使用標准模板的人來說,有一個簡單的解決方案:

  1. 在您的 class 中,您需要定義一個相等運算符重載== 如果您不知道如何操作,GeeksforGeeks 有一個很棒的教程https://www.geeksforgeeks.org/operator-overloading-c/

  2. 在標准命名空間下,聲明一個名為 hash 的模板結構,並將您的類名作為類型(見下文)。 我發現了一篇很棒的博文,其中還顯示了使用異或和位移計算哈希的示例,但這不在這個問題的 scope 范圍內,但它還包含有關如何使用 hash 函數以及https://prateekvjoshi.com/完成的詳細說明2014/06/05/使用-hash-function-in-c-for-user-defined-classes/

namespace std {

  template<>
  struct hash<my_type> {
    size_t operator()(const my_type& k) {
      // Do your hash function here
      ...
    }
  };

}
  1. 因此,要使用新的 hash function 實現哈希表,您只需創建一個std::mapstd::unordered_map就像您通常做的那樣並使用my_type作為鍵,標准庫將自動使用 hash function you之前(在第 2 步中)定義到 hash 您的密鑰。
#include <unordered_map>

int main() {
  std::unordered_map<my_type, other_type> my_map;
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM