Làm thế nào được thiết lập () thực hiện?


151

Tôi đã thấy mọi người nói rằng setcác đối tượng trong python có kiểm tra tư cách thành viên O (1). Làm thế nào họ được thực hiện trong nội bộ để cho phép điều này? Nó sử dụng loại cấu trúc dữ liệu nào? Việc thực hiện đó có ý nghĩa gì khác?

Mọi câu trả lời ở đây đều thực sự khai sáng, nhưng tôi chỉ có thể chấp nhận một câu, vì vậy tôi sẽ đi với câu trả lời gần nhất cho câu hỏi ban đầu của mình. Cảm ơn tất cả các thông tin!

Câu trả lời:


139

Theo chủ đề này :

Thật vậy, các bộ của CPython được triển khai như một thứ giống như từ điển với các giá trị giả (các khóa là thành viên của bộ), với một số tối ưu hóa khai thác sự thiếu giá trị này

Vì vậy, về cơ bản, a setsử dụng hashtable làm cấu trúc dữ liệu cơ bản của nó. Điều này giải thích việc kiểm tra tư cách thành viên O (1), vì trung bình việc tìm kiếm một mục trong hashtable là hoạt động O (1).

Nếu bạn rất có khuynh hướng, bạn thậm chí có thể duyệt mã nguồn CPython cho tập hợp , theo Achim Domma , chủ yếu là một phần cắt và dán từ việc dictthực hiện.


18
IIRC, việc settriển khai ban đầu thực sự dict với các giá trị giả và nó đã được tối ưu hóa sau đó.
dan04

1
Không phải O lớn là trường hợp xấu nhất sao? Nếu bạn có thể tìm thấy một ví dụ trong đó thời gian là O (n) thì đó là O (n) .. Tôi không hiểu bất cứ điều gì ngay bây giờ từ tất cả các hướng dẫn.
Claudiu Creanga

4
Không, trường hợp trung bình là O (1) nhưng trường hợp xấu nhất là O (N) để tra cứu bảng băm.
Justin Ethier

4
@ClaudiuCreanga đây là một nhận xét cũ, nhưng chỉ cần làm rõ: ký hiệu big-O cho bạn biết giới hạn trên về tốc độ tăng trưởng của sự vật, nhưng bạn có thể vượt quá mức tăng trưởng của hiệu suất trường hợp trung bình và bạn có thể ngăn chặn sự phát triển của trường hợp xấu nhất hiệu suất.
Kirk Boyer

79

Khi mọi người nói các bộ có kiểm tra thành viên O (1), họ đang nói về trường hợp trung bình . Trong trường hợp xấu nhất (khi tất cả các giá trị băm va chạm) kiểm tra tư cách thành viên là O (n). Xem wiki Python về độ phức tạp thời gian .

Các bài viết trên Wikipedia nói rằng trường hợp tốt nhất độ phức tạp thời gian cho một bảng băm mà không thay đổi kích thước là O(1 + k/n). Kết quả này không áp dụng trực tiếp cho các bộ Python vì các bộ Python sử dụng bảng băm thay đổi kích thước.

Một chút nữa trên bài viết Wikipedia nói rằng đối với trường hợp trung bình , và giả sử một hàm băm thống nhất đơn giản, độ phức tạp thời gian là O(1/(1-k/n)), nơi k/ncó thể được giới hạn bởi một hằng số c<1.

Big-O chỉ đề cập đến hành vi tiệm cận là n →. Vì k / n có thể được giới hạn bởi một hằng số, c <1, không phụ thuộc vào n ,

O(1/(1-k/n))không lớn hơn O(1/(1-c))tương đương với O(constant)= O(1).

Vì vậy, giả sử trung bình băm đơn giản, trung bình , kiểm tra tư cách thành viên cho các bộ Python là O(1).


14

Tôi nghĩ đó là một lỗi phổ biến, settra cứu (hoặc hashtable cho vấn đề đó) không phải là O (1).
từ Wikipedia

Trong mô hình đơn giản nhất, hàm băm hoàn toàn không xác định và bảng không thay đổi kích thước. Đối với sự lựa chọn tốt nhất có thể của hàm băm, một bảng có kích thước n với địa chỉ mở không có xung đột và chứa tối đa n phần tử, với một so sánh duy nhất để tra cứu thành công và một bảng có kích thước n với các chuỗi và k khóa có tối đa (0, kn) va chạm và so sánh O (1 + k / n) để tra cứu. Đối với sự lựa chọn tồi tệ nhất của hàm băm, mỗi lần chèn đều gây ra xung đột và các bảng băm suy biến thành tìm kiếm tuyến tính, với các so sánh được khấu hao Ω (k) cho mỗi lần chèn và lên đến k so sánh để tra cứu thành công.

Liên quan: Là một hashmap Java thực sự O (1)?


4
Nhưng họ mất nhiều thời gian để tra cứu các mục: python -m timeit -s "s = set (phạm vi (10))" "5 trong s" 10000000 vòng, tốt nhất là 3: 0,0642 usec mỗi vòng <-> python - m timeit -s "s = set (phạm vi (10000000))" "5 in s" 10000000 vòng, tốt nhất là 3: 0,0634 usec mỗi vòng ... và đó là tập lớn nhất không ném MemoryErrors
Jochen Ritzel

2
@ THC4k Tất cả những gì bạn đã chứng minh là việc tìm kiếm X được thực hiện trong thời gian không đổi, nhưng điều đó không có nghĩa là thời gian để tìm kiếm X + Y sẽ mất cùng thời gian, đó là tất cả những gì O (1) hướng tới.
Shay Erlichmen

3
@intuited: Có, nhưng lần chạy thử ở trên không chứng minh rằng bạn có thể tra cứu "5" cùng lúc bạn có thể tra cứu "485398" hoặc một số số khác có thể ở trong không gian va chạm khủng khiếp. Không phải là tìm kiếm cùng một yếu tố trong hàm băm có kích thước khác nhau trong cùng một thời điểm (thực tế, điều đó không bắt buộc), mà là về việc bạn có thể truy cập từng mục trong cùng một khoảng thời gian trong bảng hiện tại hay không - một cái gì đó về cơ bản là không thể thực hiện được các bảng băm vì thường sẽ luôn có xung đột.
Nick Bastin

3
Nói cách khác, thời gian để tra cứu phụ thuộc vào số lượng giá trị được lưu trữ, bởi vì điều đó làm tăng khả năng va chạm.
trực giác

3
@intuited: không, điều đó không chính xác. Khi số lượng giá trị được lưu trữ tăng lên, Python sẽ tự động tăng kích thước của hàm băm và tốc độ va chạm không đổi. Giả sử thuật toán băm O (1) phân bố đồng đều, sau đó tra cứu hashtable được khấu hao O (1). Bạn có thể muốn xem bản trình bày video "Từ điển mạnh mẽ
Lie Ryan

13

Tất cả chúng ta đều có quyền truy cập dễ dàng vào nguồn , trong đó nhận xét trước set_lookkey()cho biết:

/* set object implementation
 Written and maintained by Raymond D. Hettinger <python@rcn.com>
 Derived from Lib/sets.py and Objects/dictobject.c.
 The basic lookup function used by all operations.
 This is based on Algorithm D from Knuth Vol. 3, Sec. 6.4.
 The initial probe index is computed as hash mod the table size.
 Subsequent probe indices are computed as explained in Objects/dictobject.c.
 To improve cache locality, each probe inspects a series of consecutive
 nearby entries before moving on to probes elsewhere in memory.  This leaves
 us with a hybrid of linear probing and open addressing.  The linear probing
 reduces the cost of hash collisions because consecutive memory accesses
 tend to be much cheaper than scattered probes.  After LINEAR_PROBES steps,
 we then use open addressing with the upper bits from the hash value.  This
 helps break-up long chains of collisions.
 All arithmetic on hash should ignore overflow.
 Unlike the dictionary implementation, the lookkey function can return
 NULL if the rich comparison returns an error.
*/


...
#ifndef LINEAR_PROBES
#define LINEAR_PROBES 9
#endif

/* This must be >= 1 */
#define PERTURB_SHIFT 5

static setentry *
set_lookkey(PySetObject *so, PyObject *key, Py_hash_t hash)  
{
...

2
Câu trả lời này sẽ được hưởng lợi từ C nổi bật cú pháp . Cú pháp đánh dấu Python của bình luận trông thực sự xấu.
dùng202729

Về nhận xét "Điều này khiến chúng ta có sự kết hợp giữa thăm dò tuyến tính và địa chỉ mở", không phải tuyến tính là một loại giải quyết va chạm trong địa chỉ mở, như được mô tả trong en.wikipedia.org/wiki/Open_addressing ? Do đó, thăm dò tuyến tính là một kiểu con của địa chỉ mở và nhận xét không có ý nghĩa.
Alan Evangelista

2

Để nhấn mạnh thêm một chút sự khác biệt giữa set'sdict's, đây là một đoạn trích từ các setobject.cphần bình luận, trong đó làm rõ sự khác biệt chính của tập hợp chống lại các điều khoản.

Các trường hợp sử dụng cho các bộ khác nhau đáng kể so với từ điển trong đó các phím tra cứu có nhiều khả năng có mặt. Ngược lại, các bộ chủ yếu là về thử nghiệm thành viên trong đó sự hiện diện của một yếu tố không được biết trước. Theo đó, việc thực hiện thiết lập cần tối ưu hóa cho cả trường hợp tìm thấy và không tìm thấy.

nguồn trên github

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.