C ++ ile HashMap kullanmanın en iyi yolu nedir?


175

STL'nin bir HashMap API'sı olduğunu biliyorum, ancak bununla ilgili iyi örneklerle iyi ve eksiksiz bir belge bulamıyorum.

Herhangi bir iyi örnek takdir edilecektir.


C ++ 1x hash_map veya std :: map hakkında mı soruyorsunuz?
hayırsever

2
C ++ java.util.HashMap ve varsa standart bir şekilde yapmak istiyorum. Başka en iyi standart olmayan kütüphane. C ++ geliştiricileri HashMap'e ihtiyaç duyduklarında yaygın olarak ne kullanırlar?
user855

Yanıtlar:


238

Standart kütüphane sıralı ve sırasız harita ( std::mapve std::unordered_map) kaplarını içerir. Sıralı bir haritada elemanlar tuşa göre sıralanır, ekleme ve erişim O'da (log n) olur . Genellikle standart kütüphane sıralı haritalar için dahili olarak kırmızı siyah ağaçlar kullanır . Ancak bu sadece bir uygulama detayıdır. Sırasız bir harita eki ve erişimi O (1) 'de. Bir hashtable için sadece başka bir isim.

(Sıralı) ile bir örnek 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;
}

Çıktı:

23
Anahtar: merhaba Değer: 23

Konteynırınızda sipariş vermeniz gerekiyorsa ve O (log n) çalışma zamanı ile uyumluysanız, sadece kullanın std::map.

Eğer gerçekten (O (1) insert / erişimi) karma-tablo gerekir Aksi takdirde, kontrol std::unordered_mapbenzer olan, std::map(sadece arama ve değiştirmek zorunda yukarıdaki örnekte örneğin API mapile unordered_map).

unordered_mapKap ile tanıtıldı C ++ 11 standart revizyon. Bu nedenle, derleyicinize bağlı olarak, C ++ 11 özelliklerini etkinleştirmeniz gerekir (örn. GCC 4.8 kullanırken -std=c++11CXXFLAGS'a eklemeniz gerekir).

C ++ 11 sürümünden önce bile GCC desteklenir unordered_map- ad alanında std::tr1. Böylece, eski GCC derleyicileri için bunu şu şekilde kullanmayı deneyebilirsiniz:

#include <tr1/unordered_map>

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

Ayrıca güçlendirmenin bir parçasıdır, yani daha iyi taşınabilirlik için karşılık gelen yükseltme başlığını kullanabilirsiniz .


1
Birlikte standart kütüphane, bir karma tablo tabanlı kap sahip değildir, hemen hemen tüm uygulamalar dahil bir form veya başka SGI STL. hash_map
James McNellis

HashMap uygulaması için unordered_map veya hash_map öneren @JamesMcNellis
Shameel Mohamed

2
@ShameelMohamed, 2017, yani C ++ 11'den 6 yıl sonra, sağlamayan bir STL bulmak zor olmalıdır unordered_map. Dolayısıyla, standart olmayanı düşünmek için hiçbir neden yoktur hash_map.
maxschlepzig

30

A hash_map, standardizasyon amacıyla neyin daha eski, standartlaştırılmamış bir versiyonudur unordered_map(başlangıçta TR1'de ve C ++ 11'den beri standarda dahil edilmiştir). Adından da anlaşılacağı gibi, bu farklıdır std::mapöncelikle sırasız olmak - Bir harita üzerinden size iterate, eğer örneğin begin()üzere end(), anahtar tarafından sırayla öğeleri olsun 1 , ama bir yoluyla size yinelerler eğer unordered_mapgelen begin()etmek end(), sen a öğeleri olsun az ya da çok keyfi düzen.

An unordered_mapnormalde sabit bir karmaşıklığa sahip olması beklenir. Yani, bir ekleme, arama vb., Tabloda kaç öğe olduğuna bakılmaksızın, genellikle sabit bir süre alır. A std::map, depolanan öğe sayısı üzerinde logaritmik bir karmaşıklığa sahiptir - yani bir öğe ekleme veya alma zamanı artar, ancak harita büyüdükçe oldukça yavaş olur . Örneğin, 1 milyon öğeden birini aramak 1 mikrosaniye alırsa, 2 milyon öğeden birini aramak için yaklaşık 2 mikrosaniye, 4 milyon öğeden biri için 3 mikrosaniye, 8 milyondan biri için 4 mikrosaniye bekleyebilirsiniz. öğeler, vb.

Pratik bir bakış açısından, bu aslında tüm hikaye değil. Doğası gereği, basit bir karma tablosu sabit bir boyuta sahiptir. Genel amaçlı bir kap için değişken boyutlu gereksinimlere uyarlanması biraz önemsizdir. Sonuç olarak, (potansiyel olarak) tabloyu büyüten operasyonlar (örneğin, yerleştirme) potansiyel olarak nispeten yavaştır (yani, çoğu oldukça hızlıdır, ancak periyodik olarak bir tane daha yavaş olacaktır). Tablonun boyutunu değiştiremeyen aramalar genellikle çok daha hızlıdır. Sonuç olarak, ekleme sayısına kıyasla çok fazla arama yaptığınızda, karma tabanlı tabloların çoğu en iyi durumda olma eğilimindedir. Çok fazla veri eklediğiniz durumlarda, sonuçları almak için tabloyu bir kez yineleyin (örneğin, bir dosyadaki benzersiz kelimelerin sayısını saymak)std::map aynı derecede hızlı ve muhtemelen daha hızlı olacaktır (ancak yine de hesaplama karmaşıklığı farklıdır, bu nedenle dosyadaki benzersiz kelimelerin sayısına da bağlı olabilir).


1std::less<T> Varsayılan olarak , haritayı oluştururken siparişin üçüncü şablon parametresi tarafından tanımlandığı yer .


1
Cevabın yayınlanmasından 9 yıl sonra geldiğimin farkındayım ama ... düzensiz bir haritanın boyutunun küçülebileceğinden bahseden bir dokümana bağlantınız var mı? Genellikle, std koleksiyonları sadece büyür. Dahası, çok fazla veri ekler, ancak önceden kaç veya daha fazla anahtar ekleyeceğinizi biliyorsanız, temelde yeniden boyutlandırma maliyetini geçersiz kılan oluşturmadaki haritanın boyutunu belirtebilirsiniz (çünkü hiçbir şey olmayacaktır) .
Zonko

@Zonko: Üzgünüm, sorulduğunda bunu fark etmedim. Bildiğim kadarıyla, çağrıya yanıt dışında bir unordered_map küçülmez rehash. Aradığınızda rehash, tablo için bir boyut belirtirsiniz. Bu boyut, tablo için belirtilen maksimum yük faktörünü aşmadığı sürece kullanılacaktır (bu durumda, yük faktörünü sınırlar dahilinde tutmak için boyut otomatik olarak artırılacaktır).
Jerry Coffin

22

Derleme hataları oluşturmak için gerekli olmayan, daha eksiksiz ve esnek bir örnek:

#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");
}

İşaretçiler olarak önceden tanımlanmadıkları sürece tuşlar için özellikle yararlı değildir, çünkü eşleşen bir değer işe yaramaz! (Ancak, normalde anahtarlar için dizeler kullandığım için, anahtar bildirimi "const void *" yerine "dize" yerine bu sorunu çözmelidir.)


4
Söylemeliyim ki, bu örnek C ++ 'da çok kötü bir uygulamadır. Güçlü yazılmış bir dil kullanıyorsunuz ve kullanarak yok ediyorsunuz void*. Yeni başlayanlar için, unordered_mapstandardın bir parçası olduğu ve kod sürdürülebilirliğini azalttığı için sarmak için bir neden yoktur . Sonra, sarmakta ısrar ediyorsanız, kullanın templates. Onlar tam olarak bunun için.
guyarad

Güçlü yazdınız mı? Muhtemelen statik olarak yazılmış demek istediniz. Const char ptr'den sessizce boşluğa gidebilmesi, C ++ 'ı statik olarak yapar, ancak güçlü bir şekilde yazmaz. Türleri var, ancak büyük olasılıkla var olmayan bazı belirsiz bayrağı etkinleştirmezseniz derleyici bir şey söylemez.
Sahsahae

6

std::unordered_mapGCC'de karma harita kullanan kanıtlar stdlibc ++ 6.4

Bu, şu adreste belirtilmiştir: https://stackoverflow.com/a/3578247/895245 ama aşağıdaki cevapta: st ++ içinde hangi veri yapısı var: C ++ haritası? GCC stdlibc ++ 6.4 uygulaması için daha fazla kanıt verdim:

  • Sınıfa GDB adım hata ayıklama
  • performans karakteristik analizi

İşte bu cevapta açıklanan performans karakteristik grafiğinin bir önizlemesi:

resim açıklamasını buraya girin

Özel sınıf ve hash işlevi nasıl kullanılır? unordered_map

Bu yanıt onu çiviler: Anahtar olarak özel bir sınıf türü kullanarak C ++ unordered_map

Alıntı: eşitlik:

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);
  }
};

Özet fonksiyonu:

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);
    }
  };

}

0

Standart şablonu kullanırken kendi sınıflarımızı nasıl karılacağını anlamaya çalışanlarımız için basit bir çözüm var:

  1. Sınıfınızda bir eşitlik operatörü aşırı yüklemesi tanımlamanız gerekir ==. Bunu nasıl yapacağınızı bilmiyorsanız, GeeksforGeeks'in harika bir öğreticisi var https://www.geeksforgeeks.org/operator-overloading-c/

  2. Standart ad alanı altında, tür olarak sınıf adınızla hash adında bir şablon yapısı bildirin (aşağıya bakın). XOR ve bitshifting kullanarak hash hesaplamanın bir örneğini de gösteren harika bir blog yazısı buldum, ancak bu sorunun kapsamı dışında, ancak aynı zamanda hash işlevlerini nasıl kullanacağınız hakkında ayrıntılı talimatlar da içeriyor https://prateekvjoshi.com/ 2014/06/05 / kullanarak karması fonksiyonlu-in-c-için kullanıcı tanımlı-sınıfları /

namespace std {

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

}
  1. Yani ardından yeni karma işlevi kullanılarak bir hashtable uygulamak, sadece bir oluşturmak zorunda std::mapya std::unordered_maptıpkı normalde yapacağını ve kullanım my_typeanahtarı olarak, standart kütüphane otomatik karma Sizin (2. adımda) önce tanımlanmış karma işlevi kullanacak senin anahtarların.
#include <unordered_map>

int main() {
  std::unordered_map<my_type, other_type> my_map;
}
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.