Tại sao mọi người sẽ sử dụng set thay vì unordered_set?


145

C ++ 0x đang giới thiệu unordered_setcó sẵn ở boostnhiều nơi khác. Điều tôi hiểu là unordered_setbảng băm với O(1)độ phức tạp tra cứu. Mặt khác, setkhông có gì ngoài một cái cây với log(n)sự phức tạp tra cứu. Tại sao trên trái đất mọi người sẽ sử dụng setthay vì unordered_set? tức là có cần thiết setnữa không?


22
Câu hỏi của bạn về cơ bản là hỏi nếu có cần một cây nữa không.
Vinko Vrsalovic

2
Tôi nghĩ rằng tôi đã nêu nó rõ ràng trong dòng đầu tiên, rằng đây là một câu hỏi ngu ngốc. Tôi đã bỏ lỡ điều gì đó và bây giờ tôi đã có câu trả lời :)
AraK

2
Lý do thực sự là mọi thứ không giống như B & W như chúng có vẻ. Có rất nhiều màu xám và các màu khác ở giữa. Bạn cần nhớ những container này là công cụ. Đôi khi hiệu suất không quan trọng và sự tiện lợi có ý nghĩa hơn nhiều. Nếu mọi người đều tìm kiếm giải pháp hiệu quả nhất, chúng tôi sẽ không bao giờ sử dụng C ++ (không đề cập đến Python) ngay từ đầu và liên tục viết và tối ưu hóa mã bằng ngôn ngữ máy.
AturSams

(Tại sao mọi người lại sử dụng tên chung cho việc triển khai / giao diện với những lời hứa ngoài những điều được ngụ ý bởi tên đó, tạo ra một tình huống khó xử cho những người không có?)
greybeard

Câu trả lời:


219

Khi, đối với ai đó muốn lặp đi lặp lại các mục của tập hợp, thứ tự quan trọng.


Được đặt hàng theo thứ tự chèn, hoặc theo so sánh thực tế bằng cách sử dụng các toán tử < >?
Một cái gì đó

2
Nó được đặt hàng bằng cách sử dụng std :: less theo mặc định; bạn có thể ghi đè lên điều này và cung cấp toán tử so sánh của riêng bạn. cplusplus.com/reference/set/set
moonshadow

Hoặc đôi khi khi bạn chỉ muốn lặp lại, ngay cả khi đơn hàng không thành vấn đề.
mfnx

319

Các bộ không có thứ tự phải trả tiền cho thời gian truy cập trung bình O (1) của chúng theo một số cách:

  • setsử dụng ít bộ nhớ hơn unordered_setđể lưu trữ cùng một số phần tử.
  • Đối với một số lượng nhỏ các yếu tố , tra cứu trong một setcó thể nhanh hơn tra cứu trong một unordered_set.
  • Mặc dù nhiều hoạt động được nhanh hơn trong trường hợp trung bình cho unordered_set, chúng thường được đảm bảo để có tốt hơn trường hợp xấu nhất phức tạp cho set(ví dụ insert).
  • Điều đó set sắp xếp các yếu tố hữu ích nếu bạn muốn truy cập chúng theo thứ tự.
  • Bạn có thể tự từ điển so sánh khác nhau sets với <, <=, >>=. unordered_sets không cần thiết để hỗ trợ các hoạt động này.


9
+1, tất cả các điểm xuất sắc. Mọi người có xu hướng bỏ qua thực tế là các hashtag có thời gian truy cập trường hợp trung bình O (1) , nghĩa là đôi khi họ có thể có độ trễ lớn. Sự khác biệt có thể quan trọng đối với các hệ thống thời gian thực.
j_random_hacker

Tuy nhiên, điểm tốt ở đây ( en.cppreference.com/w/cpp/container/unordered_set/operator_cmp ) có thể nói rằng chúng ta có thể so sánh unordered_sets.
Michiel uit het Broek

5
Xác định một "số lượng nhỏ các yếu tố"
Sunjay Varma

4
@SunjayVarma thường 100 yếu tố là một điểm dừng tốt giữa hai yếu tố. Khi nghi ngờ, không có gì có thể thay thế hiệu suất thử nghiệm của cả hai trong trường hợp sử dụng cụ thể của bạn.
Nate

3
@MichieluithetBroek Chỉ có so sánh bình đẳng được nêu, không đặt hàng ( <).
lisyarus

26

Bất cứ khi nào bạn thích một cây cho bảng băm.

Ví dụ, các bảng băm là "O (n)" trong trường hợp xấu nhất. O (1) là trường hợp trung bình. Cây là "O ( log n)" tồi tệ nhất.


18
/ Cân bằng / cây là O (ln n) trong trường hợp xấu nhất. Bạn có thể kết thúc với cây O (n) (danh sách liên kết cơ bản).
strager

5
Nếu bạn có thể viết một hàm băm thông minh hợp lý, bạn hầu như luôn có thể nhận được O (1) hoàn hảo từ hàm băm. Nếu bạn không thể viết hàm băm như vậy nếu bạn cần lặp lại "theo thứ tự" trên tập hợp của mình, thì bạn nên sử dụng cây. Nhưng bạn không nên sử dụng cây vì bạn sợ "hiệu suất trong trường hợp xấu nhất" (O). "
Justin L.

6
stager: Để được pedantic, có. Tuy nhiên, chúng ta đang nói về thiết lập trong C ++, thường được triển khai như một cây tìm kiếm nhị phân cân bằng . Chúng ta nên chỉ định hoạt động thực tế để nói về sự phức tạp. Trong bối cảnh này, rõ ràng là chúng ta đang nói về việc tra cứu.
Mehrdad Afshari

1
Justin L: Đó chỉ là một lý do bạn có thể thích một cái cây. Cốt lõi của câu trả lời của tôi là dòng đầu tiên. Bất cứ khi nào bạn thích một cấu trúc dữ liệu cây cho bảng băm. Có rất nhiều trường hợp cây được ưa thích để băm bảng. Các bảng băm đặc biệt hút vào những thứ như "giao điểm phạm vi."
Mehrdad Afshari

2
Cây stl là những cây đỏ đen được triển khai gần như toàn cầu, một cây tự cân bằng tiên tiến. Thực sự có những trường hợp O (n) tra cứu trong trường hợp xấu hơn là không thể chấp nhận được. Dịch vụ web cung cấp và giao diện để lưu trữ giá trị người dùng không nên sử dụng bản đồ băm, vì người dùng độc hại có thể tạo DoS một cách hiệu quả bằng cách lưu trữ các giá trị được chế tạo đặc biệt. Các hệ thống quan trọng, nhạy cảm với thời gian cũng có thể không cho phép tra cứu O (n), kiểm soát không lưu, v.v. Mặc dù nói chung bạn đúng, sử dụng bản đồ băm theo mặc định và chỉ chuyển đổi phiên bản cây khi bạn có nhu cầu thực sự.
deft_code

14

Sử dụng thiết lập khi:

  1. Chúng tôi cần dữ liệu theo thứ tự (các yếu tố riêng biệt).
  2. Chúng tôi sẽ phải in / truy cập dữ liệu (theo thứ tự được sắp xếp).
  3. Chúng ta cần người tiền nhiệm / kế thừa các yếu tố.

Sử dụng unordered_set khi:

  1. Chúng ta cần giữ một tập hợp các yếu tố riêng biệt và không yêu cầu đặt hàng.
  2. Chúng ta cần truy cập phần tử đơn tức là không có giao dịch.

Ví dụ:

bộ:

Đầu vào: 1, 8, 2, 5, 3, 9

Đầu ra: 1, 2, 3, 5, 8, 9

Không có thứ tự_set:

Đầu vào: 1, 8, 2, 5, 3, 9

Đầu ra: 9 3 1 8 2 5 (có thể là thứ tự này, bị ảnh hưởng bởi hàm băm)

Khác biệt chủ yếu:

nhập mô tả hình ảnh ở đây

Lưu ý: (trong một số trường hợp setthuận tiện hơn) ví dụ sử dụng vectorlàm khóa

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 

Lý do tại sao vector<int>có thể là chìa khóa setvectorghi đè operator<.

Nhưng nếu bạn sử dụng, unordered_set<vector<int>>bạn phải tạo hàm băm cho vector<int>, vì vectơ không có hàm băm, vì vậy bạn phải xác định một hàm như:

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
}

bạn có thể thấy rằng trong một số trường hợp unordered_setphức tạp hơn.

Chủ yếu được trích dẫn từ: https://www.geekforgeek.org/set-vs-unordered_set-c-stl/ https://stackoverflow.com/a/29855973/6329006


6

Bởi vì std :: set là một phần của Standard C ++ và unordered_set thì không. C ++ 0x KHÔNG phải là tiêu chuẩn và cũng không phải là Boost. Đối với nhiều người trong chúng ta, tính di động là cần thiết và điều đó có nghĩa là tuân thủ tiêu chuẩn.


2
Nếu tôi hiểu anh ta một cách chính xác, anh ta không hỏi tại sao mọi người hiện vẫn sử dụng set. Anh ấy đang thông báo cho mình về C ++ 0x.
Julian Schaub - litb

2
Có lẽ. Tôi nghĩ mọi người đều biết bảng băm và cây đã giải quyết các vấn đề khác nhau.

21
Chà, bây giờ nó là một tiêu chuẩn (chỉ mất vài năm)
Clayton Hughes

6

Hãy xem xét các thuật toán quét. Các thuật toán này sẽ thất bại hoàn toàn với các bảng băm, nhưng hoạt động rất đẹp với các cây cân bằng. Để cung cấp cho bạn một ví dụ cụ thể về thuật toán quét, hãy xem xét thuật toán vận may. http://en.wikipedia.org/wiki/Fortune%27s_alacticm


1
Tôi nghĩ rằng tài liệu tham khảo như vậy là quá phức tạp cho câu hỏi. (Tôi phải bất lực nhìn nó lên)
hectorpal

3

Một điều nữa, ngoài những gì người khác đã đề cập. Mặc dù độ phức tạp được khấu hao dự kiến ​​để chèn một phần tử vào unordered_set là O (1), mọi lúc và sau đó sẽ mất O (n) vì bảng băm cần phải được cấu trúc lại (số lượng xô cần thay đổi) - ngay cả với hàm băm 'tốt'. Giống như việc chèn một phần tử trong một vectơ sẽ mất O (n) mỗi lần bởi vì mảng bên dưới cần được phân bổ lại.

Chèn vào một bộ luôn mất tối đa O (log n). Điều này có thể được ưa thích trong một số ứng dụng.


3

Xin lỗi, một điều đáng chú ý nữa về tài sản được sắp xếp:

Nếu bạn muốn một phạm vi dữ liệu trong vùng chứa, ví dụ: Bạn đã lưu trữ thời gian trong tập hợp và bạn muốn có thời gian từ 2013-01-01 đến 2014-01-01.

Đối với unordered_set thì không thể.

Tất nhiên, ví dụ này sẽ thuyết phục hơn cho các trường hợp sử dụng giữa bản đồunordered_map .


3

g++ 6.4 stdlibc ++ đã đặt hàng so với điểm chuẩn thiết lập không có thứ tự

Tôi đã điểm chuẩn việc triển khai Linux C ++ vượt trội này để thấy sự khác biệt:

nhập mô tả hình ảnh ở đây

Các chi tiết và phân tích điểm chuẩn đầy đủ đã được đưa ra tại: Cấu trúc dữ liệu cơ bản của bộ STL trong C ++ là gì? và tôi sẽ không lặp lại chúng ở đây.

"BST" có nghĩa là "được thử nghiệm với std::setvà" bản đồ băm "có nghĩa là" được thử nghiệm với std::unordered_set. "Heap" là std::priority_queuecái mà tôi đã phân tích tại: Heap vs Binary Search Tree (BST)

Tóm tắt nhanh:

  • biểu đồ cho thấy rõ ràng trong các điều kiện này, việc chèn hashmap luôn nhanh hơn rất nhiều khi có hơn 100 nghìn mục và sự khác biệt tăng lên khi số lượng mục tăng lên

    Chi phí cho việc tăng tốc độ này là bạn không thể di chuyển hiệu quả theo thứ tự.

  • các đường cong gợi ý rõ ràng rằng thứ tự std::setlà dựa trên BST và dựa trên hàm std::unordered_setbăm. Trong câu trả lời tham khảo, tôi xác nhận thêm rằng bằng bước GDB gỡ lỗi mã.

Câu hỏi tương tự cho mapvs unordered_map: Có bất kỳ lợi thế nào của việc sử dụng bản đồ so với unordered_map trong trường hợp các khóa tầm thường không?


1

Nói chung, tôi sẽ nói thật thuận tiện khi có những thứ trong một mối quan hệ nếu bạn đang muốn chuyển đổi nó sang một định dạng khác.

Cũng có thể trong khi một người truy cập nhanh hơn, thời gian để xây dựng chỉ mục hoặc bộ nhớ được sử dụng khi tạo và / hoặc truy cập vào nó là lớn hơn.


+1, ký hiệu Big Oh ẩn các yếu tố không đổi và đối với các kích thước vấn đề điển hình, đó thường là các yếu tố không đổi quan trọng nhất.
j_random_hacker

1

Nếu bạn muốn sắp xếp mọi thứ, thì bạn sẽ sử dụng set thay vì unordered_set. unordered_set được sử dụng qua bộ khi đặt hàng được lưu trữ không quan trọng.


1

Mặc dù câu trả lời này có thể trễ 10 năm, nhưng đáng để chỉ ra rằng nó std::unordered_setcũng có nhược điểm về bảo mật.

Nếu chức năng băm có thể dự đoán được (điều này thường xảy ra trừ khi áp dụng các biện pháp đối phó như muối ngẫu nhiên), kẻ tấn công có thể tự tạo dữ liệu tạo ra va chạm băm và khiến tất cả các lần chèn và tra cứu mất thời gian O (n) .

Điều này có thể được sử dụng cho các cuộc tấn công từ chối dịch vụ rất hiệu quả và thanh lịch.

Nhiều (hầu hết?) Các ngôn ngữ sử dụng bản đồ băm trong nội bộ đã chạy vào đây:

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.