วิธีที่ดีที่สุดในการใช้ HashMap ใน C ++ คืออะไร


174

ฉันรู้ว่า STL มี HashMap API แต่ฉันไม่สามารถหาเอกสารที่ดีและละเอียดพร้อมตัวอย่างที่ดีเกี่ยวกับเรื่องนี้

ตัวอย่างที่ดีใด ๆ ที่จะได้รับการชื่นชม


คุณถามเกี่ยวกับ C ++ 1x hash_map หรือเกี่ยวกับ std :: map?
philant

2
ฉันต้องการบางสิ่งบางอย่างเช่น java.util.HashMap ใน C ++ และวิธีแยกต่างหากหากมี เป็นห้องสมุดมาตรฐานที่ดีที่สุด นักพัฒนา C ++ ใช้อะไรเมื่อพวกเขาต้องการ HashMap?
user855

คำตอบ:


238

ไลบรารีมาตรฐานประกอบด้วยคอนเทนเนอร์ที่สั่งซื้อและแผนที่ที่ไม่เรียงลำดับ ( std::mapและstd::unordered_map) ในการสั่งซื้อ map องค์ประกอบจะเรียงตามคีย์แทรกและการเข้าถึงอยู่ในO (log n) โดยปกติแล้วห้องสมุดมาตรฐานภายในจะใช้ต้นไม้สีดำสีแดงสำหรับแผนที่ที่สั่งซื้อ แต่นี่เป็นเพียงรายละเอียดการดำเนินการ ในการแทรกแผนที่ที่ไม่ได้เรียงลำดับและการเข้าถึงอยู่ใน O (1) มันเป็นเพียงชื่ออื่นสำหรับ hashtable

ตัวอย่างที่มี (สั่งซื้อ) 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
รหัส: สวัสดีค่า: 23

หากคุณจำเป็นต้องสั่งซื้อในภาชนะของคุณและจะมีการปรับกับ O (log n) std::mapรันไทม์แล้วเพียงแค่ใช้

มิฉะนั้นหากคุณต้องการแฮชตาราง (O (1) การแทรก / การเข้าถึง) ให้ตรวจสอบstd::unordered_mapซึ่งมีstd::mapAPI คล้ายกัน(เช่นในตัวอย่างข้างต้นคุณเพียงแค่ค้นหาและแทนที่mapด้วยunordered_map)

unordered_mapภาชนะที่ถูกนำมาใช้กับC ++ 11 มาตรฐานการแก้ไข ดังนั้นขึ้นอยู่กับคอมไพเลอร์ของคุณคุณต้องเปิดใช้งานคุณสมบัติ C ++ 11 (เช่นเมื่อใช้ GCC 4.8 คุณต้องเพิ่มลง-std=c++11ใน CXXFLAGS)

แม้กระทั่งก่อนที่ C ++ 11 ปล่อย GCC สนับสนุนunordered_map- ในการ std::tr1namespace ดังนั้นสำหรับคอมไพเลอร์ GCC แบบเก่าคุณสามารถลองใช้มันได้เช่นนี้:

#include <tr1/unordered_map>

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

มันยังเป็นส่วนหนึ่งของการเพิ่มพลังเช่นคุณสามารถใช้บูสต์ส่วนหัวที่สอดคล้องกันเพื่อการพกพาที่ดีขึ้น


1
ในขณะที่ไลบรารี่มาตรฐานไม่มีคอนเทนเนอร์แบบแฮชตารางการใช้งานเกือบทั้งหมดรวมถึงhash_mapSGI STL ในบางรูปแบบหรืออย่างอื่น
James McNellis

@JamesMcNellis ซึ่งแนะนำ unordered_map หรือ hash_map สำหรับการนำ HashMap ไปใช้
Shameel Mohamed

2
@ShameelMohamed, 2017, IE 6 ปีหลังจากที่ C ++ 11 มันควรจะเป็นเรื่องยากที่จะหา STL unordered_mapที่ไม่ได้ให้ hash_mapดังนั้นจึงมีเหตุผลที่จะต้องพิจารณาที่ไม่ได้มาตรฐานไม่มี
maxschlepzig

30

A hash_mapคือเวอร์ชันที่เก่ากว่าและไม่ได้มาตรฐานสำหรับสิ่งที่เรียกว่ามาตรฐานunordered_map(แต่เดิมใน TR1 และรวมอยู่ในมาตรฐานตั้งแต่ C ++ 11) ตามชื่อหมายถึงมันแตกต่างจากstd::mapการไม่มีการเรียงลำดับหลัก - ตัวอย่างเช่นถ้าคุณวนซ้ำแผนที่จากbegin()ไปยังend()คุณจะได้รับรายการตามคีย์1แต่ถ้าคุณวนซ้ำunordered_mapจากจากbegin()เป็นend()คุณจะได้รับรายการใน คำสั่งโดยพลการมากหรือน้อย

unordered_mapคาดว่าปกติจะมีความซับซ้อนอย่างต่อเนื่อง นั่นคือการแทรกการค้นหา ฯลฯ โดยทั่วไปแล้วจะใช้เวลาในการแก้ไขไม่ว่าจะมีกี่รายการในตาราง A std::mapมีความซับซ้อนที่ลอการิทึมเกี่ยวกับจำนวนของรายการที่จัดเก็บ - ซึ่งหมายถึงเวลาที่จะแทรกหรือดึงรายการที่เพิ่มขึ้น แต่ค่อนข้างช้าเมื่อแผนที่ขยายใหญ่ขึ้น ตัวอย่างเช่นหากใช้เวลา 1 microsecond ในการค้นหาหนึ่งใน 1 ล้านรายการคุณสามารถคาดหวังได้ว่าจะใช้เวลาประมาณ 2 microseconds เพื่อค้นหาหนึ่งใน 2 ล้านรายการและ 3 microseconds หนึ่งใน 4 ล้านรายการหนึ่ง 4 microseconds หนึ่งใน 8 ล้านรายการ รายการ ฯลฯ

จากมุมมองของภาคปฏิบัตินั่นไม่ใช่เรื่องจริงทั้งหมด โดยธรรมชาติตารางแฮชที่เรียบง่ายมีขนาดคงที่ การปรับให้เข้ากับความต้องการขนาดตัวแปรสำหรับคอนเทนเนอร์วัตถุประสงค์ทั่วไปค่อนข้างไม่สำคัญ เป็นผลให้การดำเนินการที่ (อาจ) ขยายตาราง (เช่นการแทรก) อาจจะค่อนข้างช้า (นั่นคือส่วนใหญ่จะค่อนข้างเร็ว แต่ระยะหนึ่งจะช้ากว่ามาก) การค้นหาซึ่งไม่สามารถเปลี่ยนขนาดของตารางได้โดยทั่วไปจะเร็วกว่ามาก เป็นผลให้ตารางแฮชส่วนใหญ่มักจะดีที่สุดเมื่อคุณทำการค้นหาจำนวนมากเมื่อเทียบกับจำนวนการแทรก สำหรับสถานการณ์ที่คุณแทรกข้อมูลจำนวนมากจากนั้นวนซ้ำผ่านตารางหนึ่งครั้งเพื่อดึงผลลัพธ์ (เช่นการนับจำนวนคำที่ไม่ซ้ำกันในไฟล์) โอกาสที่std::map จะเร็วและอาจเร็วกว่า (แต่อีกครั้งความซับซ้อนในการคำนวณแตกต่างกันดังนั้นจึงสามารถขึ้นอยู่กับจำนวนคำที่ไม่ซ้ำกันในไฟล์)


1ตำแหน่งที่ใบสั่งถูกกำหนดโดยพารามิเตอร์เทมเพลตที่สามเมื่อคุณสร้างแผนที่std::less<T>ตามค่าเริ่มต้น


1
ฉันรู้ว่าฉันกำลังจะมา 9 ปีหลังจากคำตอบถูกโพสต์ แต่ ... คุณมีลิงค์ไปยังเอกสารที่กล่าวถึงความจริงที่ว่าแผนที่ที่ไม่ได้เรียงลำดับสามารถย่อขนาดได้หรือไม่? โดยปกติแล้วคอลเล็กชัน std จะเติบโตขึ้นเท่านั้น ยิ่งไปกว่านั้นถ้าคุณใส่ข้อมูลจำนวนมาก แต่รู้ล่วงหน้าว่าจะใส่คีย์มากหรือน้อยคุณสามารถระบุขนาดของแผนที่ในการสร้างได้ซึ่งโดยทั่วไปจะทำให้ต้นทุนการปรับขนาดเป็นโมฆะ (สาเหตุจะไม่มี) .
Zonko

@Zonko: ขออภัยฉันไม่ได้สังเกตสิ่งนี้เมื่อถูกถาม เท่าที่ผมรู้ว่าการ unordered_map rehashไม่หดตัวยกเว้นในการตอบสนองต่อการเรียก เมื่อคุณโทรrehashคุณจะระบุขนาดสำหรับตาราง ขนาดนั้นจะถูกนำมาใช้เว้นแต่ว่าการทำเช่นนั้นจะเกินตัวประกอบภาระสูงสุดที่กำหนดไว้สำหรับตาราง (ในกรณีนี้ขนาดจะเพิ่มขึ้นโดยอัตโนมัติเพื่อให้ตัวประกอบโหลดอยู่ภายในขีด จำกัด )
Jerry Coffin

22

นี่คือตัวอย่างที่สมบูรณ์และยืดหยุ่นมากขึ้นซึ่งไม่จำเป็นต้องรวมถึงการสร้างข้อผิดพลาดในการรวบรวม:

#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 *" ในการประกาศคีย์ควรแก้ไขปัญหานี้)


4
ฉันต้องบอกว่าตัวอย่างนี้เป็นการฝึกฝนที่แย่มากใน C ++ void*คุณกำลังใช้ภาษาพิมพ์มั่นและทำลายมันโดยใช้ สำหรับผู้เริ่มไม่มีเหตุผลที่จะห่อunordered_mapเนื่องจากเป็นส่วนหนึ่งของมาตรฐานและลดการบำรุงรักษาโค้ด templatesต่อไปถ้ายืนยันในห่อไว้ใช้ นั่นคือสิ่งที่พวกเขามีไว้เพื่อ
guyarad

พิมพ์อย่างรุนแรง? คุณอาจหมายถึงพิมพ์แบบคงที่ ความจริงที่ว่าเขาสามารถเปลี่ยนจาก const char ptr เป็นโมฆะได้อย่างเงียบ ๆ ทำให้ C ++ คงที่ แต่ไม่ได้พิมพ์ มีหลายประเภท แต่คอมไพเลอร์จะไม่พูดอะไรนอกจากคุณจะเปิดใช้งานการตั้งค่าสถานะคลุมเครือซึ่งส่วนใหญ่ไม่มีอยู่จริง
Sahsahae

6

หลักฐานที่std::unordered_mapใช้แผนที่แฮชใน GCC stdlibc ++ 6.4

สิ่งนี้ถูกกล่าวถึงที่: https://stackoverflow.com/a/3578247/895245แต่ในคำตอบต่อไปนี้: โครงสร้างข้อมูลใดอยู่ภายใน std :: map ใน C ++? ฉันได้ให้หลักฐานเพิ่มเติมสำหรับการใช้ 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);
    }
  };

}

0

สำหรับพวกเราที่พยายามคิดวิธีแฮชคลาสของเราเองในขณะที่ยังใช้เทมเพลตมาตรฐานอยู่นั้นมีวิธีแก้ไขง่ายๆดังนี้:

  1. ==ในชั้นเรียนของคุณคุณจะต้องกำหนดเกินความเสมอภาคผู้ประกอบการ หากคุณไม่ทราบวิธีการทำเช่นนี้ GeeksforGeeks มีบทช่วยสอนที่ยอดเยี่ยมhttps://www.geeksforgeeks.org/operator-overloading-c/

  2. ภายใต้เนมสเปซมาตรฐานให้ประกาศโครงสร้างเทมเพลตที่เรียกว่าแฮชพร้อมกับชื่อคลาสของคุณเป็นประเภท (ดูด้านล่าง) ฉันพบ blogpost ที่ยอดเยี่ยมซึ่งแสดงตัวอย่างของการคำนวณแฮชโดยใช้ XOR และ bitshifting แต่มันอยู่นอกขอบเขตของคำถามนี้ แต่ก็ยังมีคำแนะนำโดยละเอียดเกี่ยวกับวิธีทำให้สำเร็จโดยใช้ฟังก์ชันแฮชด้วยเช่นกันhttps://prateekvjoshi.com/ 2014 / 05/06 / ใช้แฮชฟังก์ชั่นในคสำหรับผู้ใช้กำหนดเรียน /

namespace std {

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

}
  1. ดังนั้นเมื่อต้องการใช้ hashtable โดยใช้ฟังก์ชันแฮชใหม่ของคุณคุณต้องสร้างstd::mapหรือstd::unordered_mapเหมือนกับที่คุณทำตามปกติและใช้my_typeเป็นคีย์ไลบรารีมาตรฐานจะใช้ฟังก์ชันแฮชที่คุณกำหนดไว้ก่อน (ในขั้นตอนที่ 2) โดยอัตโนมัติเพื่อแฮช กุญแจของคุณ
#include <unordered_map>

int main() {
  std::unordered_map<my_type, other_type> my_map;
}
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.