2013-04-18 168 views
44

Came qua câu hỏi này tốt, mà là tương tự nhưng không phải ở tất cả cùng vì nó nói về Java, trong đó có thực hiện khác nhau của hash-bàn, nhờ có đồng bộ accessor/mutators Differences between HashMap and Hashtable?sự khác nhau giữa set và unordered_set trong C++ là gì?

Vì vậy, sự khác biệt là gì trong C++ thực hiện thiết lập và unordered_set? Câu hỏi này có thể là ofcourse mở rộng đến bản đồ vs unordered_map và như vậy cho các thùng chứa C++ khác.

Dưới đây là đánh giá ban đầu của tôi

thiết: Trong khi doesnt tiêu chuẩn yêu cầu một cách rõ ràng nó được thực hiện như cây, hạn chế thời gian phức tạp yêu cầu hoạt động của nó đối với find/chèn, có nghĩa là nó sẽ luôn luôn được thực hiện như cây. Thông thường như cây RB (như đã thấy trong GCC 4.8), được cân bằng chiều cao. Vì họ là chiều cao cân, họ có thể dự đoán được thời gian phức tạp cho find()

Ưu điểm: Nhỏ gọn (so với DS khác so)

Côn: Truy cập thời gian phức tạp là O (lg n)

unordered_set: Trong khi chuẩn không rõ ràng yêu cầu nó được thực hiện như cây, ràng buộc phức tạp về thời gian được yêu cầu cho hoạt động tìm/chèn, nghĩa là nó sẽ luôn được triển khai dưới dạng bảng băm.

Ưu điểm:

  1. nhanh hơn (lời hứa trả dần O (1) cho tìm kiếm)
  2. dễ dàng để chuyển đổi nguyên thủy cơ bản để thread-safe, so với cây DS

Nhược điểm:

  1. Tra cứu không được đảm bảo là O (1) Trường hợp xấu nhất có tính chất Therotical là O (n)
  2. Không nhỏ gọn như cây. (cho các yếu tố tải trọng mục đích thực tế là không bao giờ 1)

Lưu ý: O (1), cho hashtable đến từ giả định rằng không có va chạm. Ngay cả với hệ số tải là 0,5, mọi biến chèn thứ hai đều dẫn đến va chạm. Có thể quan sát thấy rằng hệ số tải của bảng băm tỷ lệ nghịch với số lượng hoạt động cần thiết để truy cập vào một phần tử trong đó. Chúng tôi càng giảm # hoạt động, bảng băm nhỏ hơn. Khi phần tử được lưu trữ có kích thước tương đương với con trỏ, thì chi phí trên là khá đáng kể.

Chỉnh sửa: Vì phần lớn câu hỏi có chứa câu trả lời đầy đủ, tôi thay đổi câu hỏi thành "Tôi có bỏ sót bất kỳ sự khác biệt nào giữa bản đồ/tập hợp để phân tích hiệu suất mà một người nên biết không?"

+1

Các phần tử của tập hợp 'std :: set' phải được chuyển ngang theo một thứ tự cụ thể.Đây là lý do thực tế tại sao các thao tác chèn, tra cứu và loại bỏ là 'O (lg n)'. – pyon

+0

@ EduardoLeón: Tôi nghĩ O (lg n), là tác dụng phụ của cây như DS. Điều đó cũng sẽ giải thích các mục có thứ tự nhất định khi đi ngang qua. Tôi không chắc chắn, nhưng tôi không biết 'thứ tự cụ thể' là yêu cầu cho 'set' trong C++. Tôi có thể sai. –

+4

"* Tra cứu không được bảo đảm là O (1) Trường hợp xấu nhất về mặt pháp lý là O (n) *" Đó không phải là quá nhiều "con" như là "bạn không biết cách viết hàm băm". –

Trả lời

23

Tôi nghĩ rằng nói chung bạn đã trả lời câu hỏi của riêng bạn, tuy nhiên, điều này:

Không như nhỏ gọn như cây. (cho các yếu tố tải mục đích thực tế là không bao giờ 1)

không nhất thiết phải đúng.Mỗi nút của một cây (chúng tôi sẽ giả định đó là một cây màu đỏ-đen) cho một loại T sử dụng không gian bằng ít nhất 2 * pointer_size + sizeof(T) + sizeof(bool). Đây có thể là 3 * pointer size tùy thuộc vào việc cây có chứa con trỏ parent cho mỗi nút cây hay không.

So sánh điều này với bản đồ băm: sẽ có không gian mảng bị lãng phí cho mỗi bản đồ băm do thực tế là load factor < 1 như bạn đã nói. Tuy nhiên, giả sử bản đồ băm sử dụng các danh sách được liên kết đơn lẻ cho chuỗi (và thực sự, không có lý do thực sự nào không), mỗi phần tử được chèn chỉ mất sizeof(T) + pointer size.

Lưu ý rằng phân tích này bỏ qua bất kỳ chi phí nào có thể đến từ không gian thừa được sử dụng bằng cách căn chỉnh.

Đối với mọi thành phần T có kích thước nhỏ (vì vậy, bất kỳ loại cơ bản nào), kích thước của con trỏ và chi phí khác chiếm ưu thế. Với hệ số tải là > 0.5 (ví dụ), std::unordered_set thực sự có thể sử dụng ít bộ nhớ hơn số std::set tương đương.

Điểm thiếu lớn khác là thực tế việc lặp qua std::set được đảm bảo tạo ra thứ tự từ nhỏ nhất đến lớn nhất, dựa trên hàm so sánh đã cho, trong khi lặp qua một std::unordered_set sẽ trả về giá trị theo thứ tự "ngẫu nhiên" .

+0

@PeteBecker Đối với (amortized) 'O (1)' tra cứu, nó có hiệu lực buộc phải là một mảng các danh sách (hoặc đơn giản là một mảng nếu buckoo băm được sử dụng) - yêu cầu truy cập ngẫu nhiên để đến nhóm được chỉ định trong 'O (1) 'sẽ thực thi điều này. Nếu bạn đang đề cập đến những gì mỗi thùng sử dụng, đọc bài viết của tôi một lần nữa, tôi xác định rõ ràng tôi giả định một danh sách liên kết đơn lẻ (mặc dù điều này chắc chắn không được thi hành theo tiêu chuẩn). – Yuushi

+0

Tôi nghĩ rằng tôi đã xóa tin nhắn đó, vì đó là một sự mất tập trung. Tôi đã xóa nó ngay bây giờ. –

9

Sự khác biệt (mặc dù không liên quan đến hiệu suất) là việc chèn set không làm mất hiệu lực trình lặp, trong khi chèn unordered_set có thể nếu nó kích hoạt phục hồi. Trong thực tế nó là một mối quan tâm khá nhỏ, vì tham chiếu đến các yếu tố thực tế vẫn còn hiệu lực.

+0

Làm thế nào có thể là vì nếu 'set' được thực hiện như là một rb-tree một chèn có thể kích hoạt một sự cân bằng cây? –

+0

Bởi vì các trình vòng lặp có thể (và AFAIK, luôn luôn) được thực hiện theo một con trỏ tới một nút cây bên trong. Một hoạt động tái cân bằng không cần phải tạo hoặc phá hủy các nút, nó chỉ có thể trộn một số con trỏ trái/phải/cha mẹ xung quanh. Vì vậy, sau đó, một iterator hợp lệ trước đó là trái trỏ vào một nút hợp lệ và vẫn có thể nhận được ở tất cả mọi thứ nó cần để đi qua cây. – dhaffey

1

Yuushi xử lý hiệu quả không gian và các điểm khác tốt; chỉ một vài phần khác của câu hỏi tôi sẽ nhận xét về ...

O (1), cho hashtable xuất phát từ giả định rằng không có va chạm.

Điều đó không đúng. Những gì O (1) có nghĩa là không phải là nỗ lực tra cứu đầu tiên sẽ luôn thành công, đó là - trung bình - một số cố gắng liên tục cần thiết, thay vì cái gì đó phát triển khi số lượng giá trị tăng lên. Ví dụ: với unordered_set hoặc ... _map, max_load_factor mặc định là 1.0 khi xây dựng và nếu hệ số tải tiếp cận với hàm băm tốt, số thành phần trung bình băm vào bất kỳ một nhóm nào sẽ ở khoảng 2 bất kể có bao nhiêu giá trị trong bảng.

Ngay cả với hệ số tải là 0,5, mọi biến chèn thứ hai đều dẫn đến va chạm.

Đúng, nhưng nó không trở nên nghiêm trọng như bạn có thể mong đợi một cách trực quan: chiều dài chuỗi trung bình là 2 ở 1,0 yếu tố tải không tồi.

Nó có thể được quan sát thấy rằng tải trọng-yếu tố của bảng băm là nghịch tỉ lệ với số hoạt động cần thiết để truy cập vào một yếu tố trong đó. Chúng tôi càng giảm # hoạt động, bảng băm nhỏ hơn.

Chắc chắn có sự tương quan (không phải là nghịch đảo).

+0

Bạn có thể đạt được l.f. .5 w/o va chạm ở tất cả? Ít nhất không phải cho mỗi lần chèn thứ hai? – Yola

+0

@Yola: với chức năng băm có mục đích chung không có khả năng làm kém bất kể đầu vào, không, vì vị trí của từng mục mới có hiệu quả ngẫu nhiên nhưng có thể tái sản xuất: cho một nửa số nhóm đã sử dụng có nghĩa là 50/50 cơ hội một sự va chạm. Trong thực tế, nhiều ngôn ngữ/thư viện có khả năng truyền các số nguyên qua không thay đổi, vì vậy nếu các khóa có xu hướng tăng, chúng sẽ ánh xạ độc đáo vào các nhóm liên tiếp và bạn thường thấy ít va chạm hơn (và nhiều bộ nhớ cache thân thiện hơn) với hàm băm ngẫu nhiên nhưng có thể lặp lại được. –

+0

Ở mức độ cao nhất, các chương trình như ['gperf'] (https://www.gnu.org/software/gperf/) thường có thể tạo mã nguồn để làm băm hoàn hảo (tức là 0 va chạm) cho một tập hợp các khóa liên tục, nhưng đó là không sử dụng cho đầu vào không rõ cho đến khi thời gian chạy. –

Các vấn đề liên quan