ทำไมทุกคนจะใช้ชุดแทน unordered_set


145

C ++ 0x กำลังแนะนำunordered_setซึ่งมีอยู่ในboostและที่อื่น ๆ อีกมากมาย สิ่งที่ฉันเข้าใจคือunordered_setตารางแฮชที่มีO(1)ความซับซ้อนในการค้นหา ในทางตรงกันข้ามsetไม่มีอะไรเลยนอกจากต้นไม้ที่มีlog(n)ความซับซ้อนในการค้นหา ทำไมทุกคนบนโลกจะใช้setแทนunordered_set? เช่นมีความต้องการsetอีกต่อไปหรือไม่


22
คำถามของคุณคือการถามพื้นฐานว่าต้องการต้นไม้อีกต่อไปหรือไม่
Vinko Vrsalovic

2
ฉันคิดว่าฉันพูดอย่างชัดเจนในบรรทัดแรกว่านี่เป็นคำถามที่โง่ ฉันหายไปบางสิ่งบางอย่างและตอนนี้ฉันได้รับคำตอบ :)
38946 AraK

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

(ทำไมบนโลกนี้จะมีใครบางคนใช้ชื่อสามัญสำหรับการนำไปปฏิบัติ / ส่วนต่อประสานกับคำสัญญาที่นอกเหนือจากชื่อนั้นโดยการสร้างสถานการณ์ที่น่าอึดอัดใจสำหรับคนที่ไม่มี?)
greybeard

คำตอบ:


219

เมื่อใดสำหรับคนที่ต้องการทำซ้ำสิ่งต่างๆในชุดคำสั่งจะมีความสำคัญ


มันเป็นคำสั่งตามคำสั่งแทรกหรือตามการเปรียบเทียบจริงโดยใช้ผู้ประกอบการ< >?
SomethingSomething

2
มันสั่งให้ใช้ std :: less ตามค่าเริ่มต้น คุณสามารถลบล้างสิ่งนี้และจัดหาผู้ให้บริการเปรียบเทียบของคุณเอง cplusplus.com/reference/set/set
moonshadow

หรือบางครั้งเมื่อคุณต้องการที่จะย้ำแม้ว่าการสั่งซื้อไม่สำคัญ
mfnx

319

ชุดที่ไม่ได้สั่งซื้อจะต้องจ่ายค่า O (1) เวลาในการเข้าถึงโดยเฉลี่ยในสองสามวิธี:

  • set การใช้งาน หน่วยความจำน้อยกว่าunordered_setเพื่อเก็บองค์ประกอบจำนวนเดียวกัน
  • สำหรับองค์ประกอบจำนวนน้อยการค้นหาsetอาจจะเป็นเร็วขึ้นunordered_setกว่าการค้นหาใน
  • แม้ว่าการดำเนินการจำนวนมากจะเร็วกว่าในกรณีทั่วไปสำหรับunordered_setพวกเขามักจะรับประกันว่าจะมีความซับซ้อนกรณีที่เลวร้ายที่สุดที่ดีกว่าสำหรับset(ตัวอย่างinsert)
  • การset เรียงลำดับองค์ประกอบนั้นมีประโยชน์หากคุณต้องการเข้าถึงตามลำดับ
  • คุณสามารถlexicographically เปรียบเทียบที่แตกต่างกันsetS กับ<, <=, และ> ไม่จำเป็นต้องใช้เพื่อสนับสนุนการดำเนินการเหล่านี้>=unordered_set


9
+1 คะแนนที่ยอดเยี่ยมทั้งหมด ผู้คนมักจะมองข้ามความจริงที่ว่าแฮชเทเบิลมี O (1) เวลาในการเข้าถึงโดยเฉลี่ยซึ่งหมายความว่าบางครั้งพวกเขาอาจมีความล่าช้ามาก ความแตกต่างอาจมีความสำคัญสำหรับระบบเรียลไทม์
j_random_hacker

จุดที่ดี แต่ที่นี่ ( en.cppreference.com/w/cpp/container/unordered_set/operator_cmp ) มีการระบุว่าเราสามารถเปรียบเทียบ unordered_sets
Michiel uit het Broek

5
กำหนด "องค์ประกอบจำนวนน้อย"
Sunjay Varma

4
@SunjayVarma โดยปกติจะมีองค์ประกอบ 100 รายการที่ดีระหว่างทั้งสอง เมื่อมีข้อสงสัยไม่มีอะไรสามารถแทนที่ประสิทธิภาพการทดสอบของทั้งสองในกรณีใช้งานเฉพาะของคุณ
เนท

3
@MichieluithetBroek การเปรียบเทียบความเท่าเทียมกันเท่านั้นที่ระบุไว้ไม่ใช่การสั่งซื้อ ( <)
lisyarus

26

เมื่อใดก็ตามที่คุณชอบต้นไม้ไปที่โต๊ะแฮช

ตัวอย่างเช่นตารางแฮชคือ "O (n)" ในกรณีที่เลวร้ายที่สุด O (1) เป็นตัวพิมพ์เล็กโดยเฉลี่ย ต้นไม้คือ "O (บันทึก n)" ที่เลวร้ายที่สุด


18
/ สมดุล / ต้นไม้เป็น O (ln n) ในกรณีที่เลวร้ายที่สุด คุณสามารถจบลงด้วยต้นไม้ O (n) (รายการเชื่อมโยงเป็นหลัก)
แปลกหน้า

5
หากคุณสามารถเขียนฟังก์ชันแฮชที่ชาญฉลาดพอสมควรคุณสามารถทำให้ O (1) หลุดพ้นจาก hashtable ได้ตลอดเวลา หากคุณไม่สามารถเขียนฟังก์ชันแฮชของหากคุณต้องการย้ำ "ตามลำดับ" ในชุดของคุณแล้วคุณควรใช้ต้นไม้ แต่คุณไม่ควรใช้ต้นไม้เพราะคุณกลัวว่า "O (n) ประสิทธิภาพที่แย่ที่สุด"
จัสตินลิตร

6
stager: เพื่ออวดความรู้ใช่ แต่เรากำลังพูดถึงการตั้งค่าใน C ++ ซึ่งดำเนินการโดยทั่วไปแล้วจะเป็นต้นไม้ค้นหาสมดุลไบนารี เราควรระบุการดำเนินการจริงเพื่อพูดคุยเกี่ยวกับความซับซ้อน ในบริบทนี้มันชัดเจนว่าเรากำลังพูดถึงการค้นหา
Mehrdad Afshari

1
Justin L: มันเป็นเพียงเหตุผลเดียวที่คุณอาจชอบต้นไม้ แก่นแท้ของคำตอบคือบรรทัดแรก เมื่อใดก็ตามที่คุณต้องการโครงสร้างข้อมูลแบบต้นไม้กับตารางแฮช มีหลายกรณีที่ต้นไม้ต้องการแฮชตาราง ตารางแฮชจะดูดสิ่งต่าง ๆ เช่น "การแยกช่วง" โดยเฉพาะ
Mehrdad Afshari

2
ต้นไม้ stl เกือบจะถูกนำไปใช้อย่างแพร่หลายในระดับสากลต้นไม้สีแดงดำซึ่งเป็นต้นไม้ที่ปรับสมดุลตนเองขั้นสูง มีหลายกรณีที่ O (n) ค้นหาในกรณีที่แย่กว่านั้นไม่ยอมรับ บริการเว็บที่ให้บริการและส่วนต่อประสานเพื่อเก็บค่าผู้ใช้ไม่ควรใช้แฮชแม็พเนื่องจากผู้ใช้ที่ประสงค์ร้ายสามารถสร้าง DoS ได้อย่างมีประสิทธิภาพโดยการจัดเก็บค่าที่สร้างขึ้นเป็นพิเศษ ที่สำคัญระบบที่ไวต่อเวลาอาจไม่อนุญาตให้ใช้การค้นหา O (n), การควบคุมการจราจรทางอากาศเป็นต้นแม้ว่าโดยทั่วไปแล้วคุณจะถูกต้องให้ใช้แผนที่แฮชตามค่าเริ่มต้นและสลับรุ่นต้นไม้เมื่อคุณต้องการจริงๆเท่านั้น
deft_code

14

ใช้ตั้งเมื่อ:

  1. เราต้องการข้อมูลที่สั่งซื้อ (องค์ประกอบที่แตกต่าง)
  2. เราจะต้องพิมพ์ / เข้าถึงข้อมูล (เรียงตามลำดับ)
  3. เราต้องการบรรพบุรุษ / ผู้สืบทอดองค์ประกอบ

ใช้ unordered_set เมื่อ:

  1. เราจำเป็นต้องเก็บชุดขององค์ประกอบที่แตกต่างและไม่จำเป็นต้องสั่งซื้อ
  2. เราต้องการการเข้าถึงองค์ประกอบเดียวคือไม่มีการแวะผ่าน

ตัวอย่าง:

ตั้ง:

อินพุต: 1, 8, 2, 5, 3, 9

เอาต์พุต: 1, 2, 3, 5, 8, 9

Unordered_set:

อินพุต: 1, 8, 2, 5, 3, 9

ผลลัพธ์: 9 3 1 8 2 5 (อาจจะเป็นคำสั่งนี้ได้รับอิทธิพลจากฟังก์ชั่นแฮช)

ความแตกต่างส่วนใหญ่:

ป้อนคำอธิบายรูปภาพที่นี่

หมายเหตุ: (ในบางกรณีsetสะดวกกว่า) ตัวอย่างเช่นใช้vectorเป็นคีย์

set<vector<int>> s;
s.insert({1, 2});
s.insert({1, 3});
s.insert({1, 2});

for(const auto& vec:s)
    cout<<vec<<endl;   // I have override << for vector
// 1 2
// 1 3 

สาเหตุที่vector<int>สามารถเป็นกุญแจสำคัญในsetเพราะการvectorแทนที่operator<แทนที่

แต่ถ้าคุณใช้unordered_set<vector<int>>คุณต้องสร้างฟังก์ชันแฮชvector<int>เพราะ vector ไม่มีฟังก์ชันแฮชดังนั้นคุณต้องกำหนดหนึ่งอย่าง:

struct VectorHash {
    size_t operator()(const std::vector<int>& v) const {
        std::hash<int> hasher;
        size_t seed = 0;
        for (int i : v) {
            seed ^= hasher(i) + 0x9e3779b9 + (seed<<6) + (seed>>2);
        }
        return seed;
    }
};

vector<vector<int>> two(){
    //unordered_set<vector<int>> s; // error vector<int> doesn't  have hash function
    unordered_set<vector<int>, VectorHash> s;
    s.insert({1, 2});
    s.insert({1, 3});
    s.insert({1, 2});

    for(const auto& vec:s)
        cout<<vec<<endl;
    // 1 2
    // 1 3
}

คุณจะเห็นว่าในบางกรณี unordered_setมีความซับซ้อนมากขึ้น

ส่วนใหญ่อ้างจาก: https://www.geeksforgeeks.org/set-vs-unordered_set-c-stl/ https://stackoverflow.com/a/29855973/6329006


6

เนื่องจาก std :: set เป็นส่วนหนึ่งของ Standard C ++ และ unordered_set ไม่ใช่ C ++ 0x ไม่ใช่มาตรฐานและไม่ใช่ Boost สำหรับพวกเราหลายคนความสะดวกในการพกพาเป็นสิ่งสำคัญและนั่นหมายถึงการยึดมั่นในมาตรฐาน


2
ถ้าฉันเข้าใจเขาอย่างถูกต้องเขาจะไม่ถามว่าทำไมคนในปัจจุบันยังคงใช้ชุด เขากำลังแจ้งตัวเองเกี่ยวกับ C ++ 0x
Johannes Schaub - litb

2
อาจจะ. ฉันคิดว่าทุกคนรู้ว่าตารางแฮชและต้นไม้แก้ปัญหาต่าง ๆ ได้

21
ตอนนี้มันเป็นมาตรฐานแล้ว (ใช้เวลาเพียงไม่กี่ปี)
Clayton Hughes

6

พิจารณาอัลกอริทึม sweepline อัลกอริทึมเหล่านี้อาจล้มเหลวอย่างสิ้นเชิงกับตารางแฮช แต่ทำงานได้อย่างสวยงามกับต้นไม้ที่สมดุล เพื่อให้ตัวอย่างที่เป็นรูปธรรมของอัลกอริทึม sweepline ให้พิจารณาอัลกอริทึมของฟอร์จูน http://en.wikipedia.org/wiki/Fortune%27s_algorithm


1
ฉันคิดว่าการอ้างอิงดังกล่าวซับซ้อนเกินกว่าที่กำหนดไว้สำหรับคำถาม (ฉันต้องมองมันขึ้นมา)
hectorpal

3

อีกสิ่งหนึ่งนอกเหนือจากสิ่งที่คนอื่นพูดถึงแล้ว ในขณะที่ความซับซ้อนที่คาดว่าจะถูกตัดค่าใช้จ่ายสำหรับการแทรกองค์ประกอบไปยัง unordered_set คือ O (1) ทุก ๆ คราวจากนั้นจะใช้ O (n) เนื่องจากตารางแฮชจำเป็นต้องได้รับการปรับโครงสร้างใหม่ (จำนวนถังต้องเปลี่ยน) - แม้ด้วย ฟังก์ชันแฮช 'ดี' เช่นเดียวกับการแทรกองค์ประกอบในเวกเตอร์ใช้เวลา O (n) ทุก ๆ ครั้งแล้วเพราะอาร์เรย์ที่ต้องถูกจัดสรรใหม่

การแทรกในชุดจะใช้เวลาไม่เกิน O (ล็อก n) นี่อาจเป็นที่นิยมในบางแอปพลิเคชัน


3

ให้อภัยฉันอีกสิ่งหนึ่งที่ควรสังเกตเกี่ยวกับสถานที่ให้บริการที่จัดเรียง:

ถ้าคุณต้องการช่วงของข้อมูลในคอนเทนเนอร์ตัวอย่างเช่น: คุณเก็บเวลาไว้ในชุดและคุณต้องการเวลาตั้งแต่ 2013-01-01 ถึง 2014-01-01

สำหรับunordered_setมันเป็นไปไม่ได้

แน่นอนตัวอย่างนี้จะมีมากขึ้นน่าเชื่อสำหรับกรณีการใช้งานระหว่างแผนที่และunordered_map


3

g++ 6.4 stdlibc ++ สั่งเทียบกับชุดมาตรฐานที่ไม่เรียงลำดับ

ฉันเปรียบเทียบการใช้ Linux C ++ ที่โดดเด่นนี้เพื่อดูความแตกต่าง:

ป้อนคำอธิบายรูปภาพที่นี่

รายละเอียดมาตรฐานและการวิเคราะห์เต็มรูปแบบได้รับที่: โครงสร้างข้อมูลพื้นฐานของชุด STL ใน C ++ คืออะไร?และฉันจะไม่ทำซ้ำพวกเขาที่นี่

"BST" หมายถึง "การทดสอบกับstd::setและ 'แผนที่กัญชา' หมายถึง" std::unordered_setการทดสอบกับ "Heap" เป็นstd::priority_queueสิ่งที่ฉันวิเคราะห์ที่: Heap vs Binary Search Tree (BST)

สรุปโดยย่อ:

  • กราฟแสดงให้เห็นอย่างชัดเจนว่าภายใต้เงื่อนไขเหล่านี้การแทรก hashmap จะเร็วกว่าเสมอเมื่อมีรายการมากกว่า 100k และความแตกต่างก็เพิ่มขึ้นเมื่อจำนวนรายการเพิ่มขึ้น

    ต้นทุนของการเพิ่มความเร็วนี้คือคุณไม่สามารถท่องไปตามลำดับได้อย่างมีประสิทธิภาพ

  • เส้นโค้งชัดเจนแนะนำว่าสั่งstd::setเป็น BST-based และstd::unordered_setเป็น hashmap ในคำตอบอ้างอิงฉันยืนยันต่อไปว่าโดย GDB ขั้นตอนการแก้จุดบกพร่องรหัส

คำถามที่คล้ายกันสำหรับmapvs unordered_map: มีข้อได้เปรียบของการใช้แผนที่บน unordered_map ในกรณีที่เป็นกุญแจสำคัญหรือไม่?


1

มือฉันจะบอกว่ามันสะดวกที่จะมีสิ่งต่าง ๆ ในความสัมพันธ์หากคุณต้องการแปลงมันให้อยู่ในรูปแบบที่แตกต่างกัน

นอกจากนี้ยังเป็นไปได้ว่าในขณะที่หนึ่งเข้าถึงได้เร็วขึ้นเวลาในการสร้างดัชนีหรือหน่วยความจำที่ใช้เมื่อสร้างและ / หรือเข้าถึงได้มากขึ้น


+1, สัญลักษณ์ใหญ่โอ้ซ่อนปัจจัยคงที่และสำหรับขนาดของปัญหาโดยทั่วไปมักเป็นปัจจัยคงที่ที่สำคัญที่สุด
j_random_hacker

1

หากคุณต้องการเรียงลำดับสิ่งต่าง ๆ คุณจะต้องใช้ set แทน unordered_set unordered_set ใช้เกินชุดเมื่อสั่งซื้อที่เก็บไว้ไม่สำคัญ


1

ในขณะที่คำตอบนี้อาจใช้เวลา 10 ปี แต่ก็คุ้มค่าที่จะชี้ให้เห็นว่าstd::unordered_setยังมีข้อเสียด้านความปลอดภัย

หากฟังก์ชั่นแฮชสามารถคาดเดาได้ (โดยทั่วไปจะเป็นกรณีนี้ยกเว้นว่าจะใช้มาตรการตอบโต้เช่นเกลือแบบสุ่ม) ผู้โจมตีสามารถส่งข้อมูลด้วยมือที่สร้างการชนกันของแฮชและทำให้การแทรกและค้นหาทั้งหมดใช้เวลา O (n) .

สิ่งนี้สามารถใช้สำหรับการโจมตีแบบปฏิเสธการให้บริการที่มีประสิทธิภาพและสวยงาม

การใช้งานหลายภาษา (ส่วนใหญ่) ของภาษาที่ใช้แผนที่แฮชภายในได้พบกับสิ่งนี้:

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