map เทียบกับ hash_map ใน C ++


117

ฉันมีคำถามเกี่ยวกับhash_mapและmapใน C ++ ฉันเข้าใจว่าmapอยู่ใน STL แต่hash_mapไม่ใช่มาตรฐาน อะไรคือความแตกต่างระหว่างทั้งสอง?

คำตอบ:


133

มีการใช้งานในรูปแบบที่แตกต่างกันมาก

hash_map( unordered_mapใน TR1 และ Boost ใช้สิ่งเหล่านี้แทน) ใช้ตารางแฮชที่มีการแฮชคีย์ไปยังสล็อตในตารางและค่าจะถูกเก็บไว้ในรายการที่เชื่อมโยงกับคีย์นั้น

map ถูกนำไปใช้เป็นต้นไม้ค้นหาไบนารีที่สมดุล (โดยปกติจะเป็นต้นไม้สีแดง / ดำ)

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


7
ฉันไม่เห็นด้วยกับคุณอย่างเต็มที่เกี่ยวกับประสิทธิภาพ ประสิทธิภาพได้รับอิทธิพลจากพารามิเตอร์หลายตัวและฉันจะดุโปรแกรมเมอร์ทุกคนที่ใช้ unordered_map เพียง 10 รายการเพราะ "เร็วกว่า" กังวลเกี่ยวกับอินเทอร์เฟซ / ฟังก์ชันก่อนประสิทธิภาพในภายหลัง
Matthieu M.

24
ใช่มันช่วยได้ถ้าคุณเข้าใจปัญหาของคุณ ตามลำดับขนาดที่แน่นอนอาจเป็นประสิทธิภาพในการล้าง แต่สิ่งสำคัญคือต้องเข้าใจลักษณะการทำงานของภาชนะทั้งสองเนื่องจากมีการเบี่ยงเบนไปในรูปแบบที่แตกต่างกันเนื่องจากปริมาณข้อมูลมีมากขึ้น
โจ

7
ที่น่าสนใจคือฉันเพิ่งเปลี่ยน std :: map ด้วย boost :: unordered_map ในแอปพลิเคชั่นที่ฉันทำการค้นหาแบบสุ่มจำนวนมาก แต่ยังทำซ้ำบนคีย์ทั้งหมดในแผนที่ด้วย ฉันประหยัดเวลาในการค้นหาเป็นจำนวนมาก แต่ได้รับมันกลับคืนมาจากการทำซ้ำดังนั้นฉันจึงเปลี่ยนกลับไปที่แผนที่และกำลังมองหาวิธีอื่นในการปรับปรุงประสิทธิภาพของแอปพลิเคชัน
Erik Garrison

4
@ErikGarrison หากคุณใช้การเข้าถึงแบบสุ่มและการทำซ้ำมากกว่าที่คุณแทรกและลบองค์ประกอบคุณสามารถมีวัตถุของคุณทั้งในแผนภูมิและ hash_map (โดยการจัดเก็บตัวชี้หรือดีกว่า shared_ptr ไปยังวัตถุเดียวกันทั้งใน กรณีที่คุณใช้อินสแตนซ์จริง) จากนั้นคุณจะมีเวลาในการเข้าถึง O (1) เวลาผ่าน hash_map และ O (n) เวลาในการทำซ้ำผ่านแผนที่ แน่นอนคุณต้องอย่าลืมเพิ่มและลบตัวชี้จากทั้งสองทุกครั้ง คุณสามารถเขียนคลาสคอนเทนเนอร์ที่กำหนดเองได้อย่างง่ายดายซึ่ง (อาจเป็นเทมเพลตเช่นกัน) ที่จะสรุปพฤติกรรมนี้ให้คุณ
สไปรต์

2
@ErikGarrison แน่นอนถ้าคุณลองใช้วิธีนี้คุณจะต้องจ่ายเงินด้วยพื้นที่เพิ่มเติมเล็กน้อย อย่างไรก็ตามเนื่องจากคุณใช้พอยน์เตอร์จึงไม่ควรมากเกินไป หากคุณต้องการจริงๆคุณสามารถลงน้ำและเขียนการใช้งาน AVL ของคุณเองและใช้ตัวชี้โหนดเป็นประเภทข้อมูลของคุณใน hash_map ซึ่งจะทำให้คุณมีเวลา O (1) ในการเข้าถึงโหนดในโครงสร้างที่ คุณจะสามารถทำซ้ำแบบเชิงเส้นได้ทุกที่ที่คุณต้องการ แน่นอนว่าสิ่งนี้จะเกี่ยวข้องกับการเข้ารหัสค่อนข้างน้อยและฉันไม่แน่ใจว่ามันจะคุ้มค่าเว้นแต่คุณจะต้องวนซ้ำจากและไปยังสถานที่เข้าถึงแบบสุ่ม
สไปรต์

35

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


9
ฉันจะชี้ให้เห็นว่าแฮชแมปมีการเข้าถึง O (N) ในกรณีที่เลวร้ายที่สุดเมื่อมีโอกาสเกิดการชนกัน (แฮช fcn ไม่ดีปัจจัยการโหลดสูงเกินไป ฯลฯ )
KitsuneYMG

แฮชแมปที่ดีมีต้นทุนที่คาดว่าจะเป็น O (1) ไม่รับประกันว่าจะเป็นเช่นนั้น แฮชแมปที่ไม่ถูกต้องอาจมีค่าใช้จ่ายที่คาดไว้ซึ่งไม่ใช่ O (1)
ชัดเจนกว่า

14

ความแตกต่างที่สำคัญบางประการอยู่ในข้อกำหนดด้านความซับซ้อน

  • mapต้องใช้O(log(N))เวลาสำหรับการแทรกและพบว่าการดำเนินงานที่มีการดำเนินการเป็นต้นไม้แดงดำโครงสร้างข้อมูล

  • unordered_mapต้องใช้เวลาเฉลี่ยของO(1)สำหรับใส่และพบ O(N)แต่ได้รับอนุญาตให้มีช่วงเวลาที่เลวร้ายที่สุดกรณีของ เนื่องจากมีการใช้โครงสร้างข้อมูลHash Table

โดยปกติแล้วunordered_mapจะเร็วขึ้น แต่ขึ้นอยู่กับคีย์และฟังก์ชันแฮชที่คุณจัดเก็บอาจแย่ลงมาก


4

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

หลายคนต้องการตารางแฮชจริงๆดังนั้นคอนเทนเนอร์แบบเชื่อมโยง STL ที่ใช้แฮชจึงเป็นส่วนขยายที่ใช้กันทั่วไปมาหลายปีแล้ว ดังนั้นพวกเขาจึงเพิ่มunordered_mapมาตรฐาน C ++ ในเวอร์ชันที่ใหม่กว่า


มันถูกเพิ่มจริงใน TR1 (std :: tr1 :: unordered_map) ไม่ใช่ C ++ 0x
Terry Mahaffey

ฉันคิดว่าสาเหตุmapโดยทั่วไปแล้ว btree ที่สมดุลนั้นเกิดจากการใช้operator<()เป็นตัวกำหนดตำแหน่ง
KitsuneYMG

@kts: การใช้งาน STL ใช้ B-tree จริงหรือไม่?
bk1e

ในทางเทคนิคต้นไม้ค้นหาไบนารีทั้งหมดคือต้นไม้ b (ต้นไม้ 1-2 ต้น) ที่ถูกกล่าวว่าฉันไม่รู้จัก STL ที่ใช้อย่างอื่นนอกจากสีแดง / ดำ
KitsuneYMG

@ bk1e B-tree "เหมาะสม" มีประโยชน์อย่างยิ่งในฐานข้อมูลโดยที่คุณต้องการโหนดทรี "fat" ที่สอดคล้องกับหน้าดิสก์ OTOH สิ่งนี้ไม่มีประโยชน์ในโมเดลหน่วยความจำแบบ "แบน" ที่ใช้ในโปรแกรม "ปกติ" - การใช้งาน STL ทั้งหมดที่ฉันรู้จักใช้ต้นไม้สีแดงดำ
Branko Dimitrijevic

3

mapถูกนำมาใช้จากbalanced binary search tree(โดยปกติ a rb_tree) เนื่องจากสมาชิกทั้งหมดในbalanced binary search treeถูกจัดเรียงดังนั้นจึงเป็นแผนที่

hash_mapถูกนำมาใช้จากhashtableเนื่องจากสมาชิกทั้งหมดในhashtableไม่ถูกเรียงลำดับดังนั้นสมาชิกในhash_map(unordered_map)จึงไม่ถูกจัดเรียง

hash_mapไม่ใช่ไลบรารีมาตรฐาน c ++ แต่ตอนนี้เปลี่ยนชื่อเป็นunordered_map(คุณสามารถคิดได้ว่าเปลี่ยนชื่อแล้ว) และกลายเป็นไลบรารีมาตรฐาน c ++ ตั้งแต่ c ++ 11 ดูคำถามนี้ความแตกต่างระหว่าง hash_map และ unordered_map? สำหรับรายละเอียดเพิ่มเติม

ด้านล่างฉันจะให้อินเทอร์เฟซหลักจากซอร์สโค้ดเกี่ยวกับวิธีการใช้งานแผนที่สองประเภท

แผนที่:

โค้ดด้านล่างเป็นเพียงการแสดงให้เห็นว่าแผนที่เป็นเพียงส่วนห่อหุ้มของbalanced binary search treeฟังก์ชันเกือบทั้งหมดเป็นเพียงการเรียกใช้balanced binary search treeฟังก์ชัน

template <typename Key, typename Value, class Compare = std::less<Key>>
class map{
    // used for rb_tree to sort
    typedef Key    key_type;

    // rb_tree node value
    typedef std::pair<key_type, value_type> value_type;

    typedef Compare key_compare;

    // as to map, Key is used for sort, Value used for store value
    typedef rb_tree<key_type, value_type, key_compare> rep_type;

    // the only member value of map (it's  rb_tree)
    rep_type t;
};

// one construct function
template<typename InputIterator>
map(InputIterator first, InputIterator last):t(Compare()){
        // use rb_tree to insert value(just insert unique value)
        t.insert_unique(first, last);
}

// insert function, just use tb_tree insert_unique function
//and only insert unique value
//rb_tree insertion time is : log(n)+rebalance
// so map's  insertion time is also : log(n)+rebalance 
typedef typename rep_type::const_iterator iterator;
std::pair<iterator, bool> insert(const value_type& v){
    return t.insert_unique(v);
};

hash_map:

hash_mapถูกนำมาใช้จากhashtableโครงสร้างที่มีลักษณะดังนี้:

ใส่คำอธิบายภาพที่นี่

ในโค้ดข้างล่างนี้ฉันจะให้ส่วนหลักของแล้วให้hashtablehash_map

// used for node list
template<typename T>
struct __hashtable_node{
    T val;
    __hashtable_node* next;
};

template<typename Key, typename Value, typename HashFun>
class hashtable{
    public:
        typedef size_t   size_type;
        typedef HashFun  hasher;
        typedef Value    value_type;
        typedef Key      key_type;
    public:
        typedef __hashtable_node<value_type> node;

        // member data is buckets array(node* array)
        std::vector<node*> buckets;
        size_type num_elements;

        public:
            // insert only unique value
            std::pair<iterator, bool> insert_unique(const value_type& obj);

};

เช่นเดียวกับmap'sสมาชิกเท่านั้นคือrb_treeที่สมาชิกเพียงอย่างเดียวคือhash_map's hashtableเป็นรหัสหลักด้านล่าง:

template<typename Key, typename Value, class HashFun = std::hash<Key>>
class hash_map{
    private:
        typedef hashtable<Key, Value, HashFun> ht;

        // member data is hash_table
        ht rep;

    public:
        // 100 buckets by default
        // it may not be 100(in this just for simplify)
        hash_map():rep(100){};

        // like the above map's insert function just invoke rb_tree unique function
        // hash_map, insert function just invoke hashtable's unique insert function
        std::pair<iterator, bool> insert(const Value& v){
                return t.insert_unique(v);
        };

};

ภาพด้านล่างนี้แสดงเมื่อ hash_map มี 53 ที่เก็บข้อมูลและแทรกค่าบางอย่างซึ่งเป็นโครงสร้างภายใน

ใส่คำอธิบายภาพที่นี่

ภาพด้านล่างแสดงความแตกต่างระหว่าง map และ hash_map (unordered_map) ภาพมาจากHow to choose between map and unordered_map? :

ใส่คำอธิบายภาพที่นี่


1

ฉันไม่รู้ว่าอะไรให้ แต่ hash_map ใช้เวลามากกว่า 20 วินาทีในการล้าง () 150K คีย์จำนวนเต็มและค่าลอย ฉันแค่วิ่งและอ่านรหัสของคนอื่น

นี่คือวิธีการรวม hash_map

#include "StdAfx.h"
#include <hash_map>

ฉันอ่านที่นี่ https://bytes.com/topic/c/answers/570079-perfomance-clear-vs-swap

บอกว่า clear () เป็นลำดับของ O (N) สำหรับฉันแล้วมันแปลกมาก แต่นั่นคือวิธีที่เป็นอยู่

โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.