Cấu trúc dữ liệu cho tập hợp giao nhau?


19

Có cấu trúc dữ liệu nào duy trì một tập hợp các tập hợp (của tập hợp mặt đất hữu hạn) hỗ trợ các hoạt động sau không? Bất kỳ thời gian chạy tuyến tính sẽ được đánh giá cao?

  1. Ban đầu một bộ trống.
  2. Thêm một yếu tố vào một bộ.
  3. Cho hai bộ, báo cáo xem chúng giao nhau.

1
Đây là một câu hỏi rất chung chung, bởi vì bất kỳ cấu trúc dữ liệu nào cũng có thể hỗ trợ các hoạt động đó với miền hữu hạn. Bạn có thể nêu chi tiết hơn được không ? Ví dụ. Bạn cần gì phức tạp, bạn sẵn sàng hy sinh những gì để có được các hoạt động được thiết lập, v.v.
Bartosz Przybylski

Câu trả lời:


12

Nếu mỗi bộ duy trì một bản ghi về những bộ khác tồn tại và bạn có tổng số s>0 bộ, bạn có thể dễ dàng biến bất kỳ cấu trúc dữ liệu nào cho một bộ sưu tập ( ví dụ: cây tìm kiếm nhị phân, v.v. ) thành một nơi bạn có thể truy xuất một phần tử của giao điểm của hai bộ trong thời gian O(logs) .

  • Mỗi bộ nên có một mã định danh duy nhất từ ​​một số bộ hoàn toàn được sắp xếp. Nếu bạn đặt tên một cách rõ ràng bộ của bạn sau đó nhận dạng có thể chỉ là chỉ số.S1,S2,

  • Bạn nên thực hiện một "sổ đăng ký" của các bộ; một cấu trúc dữ liệu duy trì một tập hợp tất cả các bộ mà bạn đã xác định. Sổ đăng ký nên được triển khai như một cấu trúc dữ liệu cây tìm kiếm, để cho phép truy xuất dễ dàng ( ví dụ:  nếu bạn muốn xóa tập hợp) và duyệt qua thời gian tuyến tính của các tập hợp.

  • Mỗi bộ cũng duy trì một "chỉ mục" của từng bộ khác - không phải là bản sao của chúng, mà là một cấu trúc dữ liệu được lập chỉ mục bởi các nhãn của các bộ khác. Chỉ số này sẽ được sử dụng để duy trì, cho mỗi bộ S k , một cây tìm kiếm nhị phân của tất cả các phần tử của S jS k . (Hai bộ S jS k chia sẻ một bản sao của cây tìm kiếm đó.)SjSkSjSkSjSk

Khởi tạo

Khởi tạo của một tập gồm O ( 1 ) hoạt động để khởi tạo cây của các yếu tố của nó, O ( s ) hoạt động như bạn khởi tạo (sao chép từ registry) chỉ số cho các thiết lập T , và O ( s log s ) hoạt động khi bạn duyệt qua sổ đăng ký để thêm T vào các chỉ số của từng bộ khác S j . Trong các chỉ số của T , chúng ta tạo ra cây tìm kiếm đại diện cho T S j = T=O(1)O(s)TO(slogs)TSjTTSj=cho các bộ khác ; chúng tôi sao chép cùng một con trỏ cho chỉ mục của S j .SjSj

Thêm một phần tử vào tập T

Thêm một số vào tập T sẽ mất thời gian O ( log n T ) như bình thường, trong đó n T = | T | . Chúng tôi cũng kiểm tra cho thành viên của x trong mỗi bộ khác S 1 , S 2 , ... , mà cần có thời gian O ( log n S 1 + log n S 2 + ) O ( s log nxVTO(lognT)nT=|T|xS1,S2, trong đó n = | V | là kích thước của vũ trụ (hoặc của tập lớn nhất S j ) và s là số lượng tập hợp trong sổ đăng ký. Đối với mỗi bộ S j x S j , cũng chèn x vào chỉ mục cho tập S jT . Đối với mỗi bộ S j như vậy, việc này sẽ mất thời gian O ( log s + log n T ) , để tra cứu S j

O(lognS1+lognS2+)O(slogn),
n=|V|SjsSjxSjxSjTSjO(logs+lognT)Sjtrong chỉ số của và để chèn x vào S jT ; trên tất cả các bộ S 1 , S 2 , ... này đòi hỏi thời gian O ( s log s + s log n T ) . Nếu chúng ta giả sử rằng số lượng tập hợp S j nhỏ hơn nhiều so với kích thước của vũ trụ V (nghĩa là, nếu chúng ta giả sử s n ), thì tổng thời gian để chèn phần tử là O ( s log n )TxSjTS1,S2,O(slogs+slognT)SjVsnO(slogn).

Nếu bạn không cho phép trùng lặp theo bộ, chúng tôi có thể tiết kiệm thời gian trong trường hợp đã bằng cách gửi bài kiểm tra thành viên và chèn thêm cho các bộ T khác . "Chèn" trong trường hợp x đã có thì chỉ mất thời gian O ( log n T ) .xSTxO(lognT)

Kiểm tra giao lộ

Chỉ số của mỗi bộ được duy trì chính xác để cho phép đánh giá nhanh xem hai bộ S k có giao nhau hay không. Đối với một tập hợp S j , chỉ cần kiểm tra chỉ mục của nó cho tập hợp S k , chúng ta không chỉ xác định được thời gian O ( log s ) có hay không S j giao nhau với S k , mà chúng ta cũng có thể truy xuất một cây nhị phân chứa toàn bộ tập hợp S jS k .SjSkSjSkO(logs)SjSkSjSk

Loại bỏ yếu tố

Để xóa một yếu tố từ một tập T , chúng tôi loại bỏ nó không chỉ từ cây tìm kiếm cho T chính nó, nhưng từ mỗi nút giao thông S jT cho bộ S j trong chỉ mục của nó. Điều này làm mất thời gian O ( s log n T ) , trong đó n T = | T | .xTTSjTSjO(slognT)nT=|T|

Đặt xóa

Do chi phí tìm kiếm sổ đăng ký, nếu bạn có nhiều bộ, có thể muốn xóa các bộ một khi chúng không còn cần thiết. Bằng cách duyệt qua toàn bộ sổ đăng ký, chúng tôi có thể xóa khỏi chỉ mục của tất cả các bộ khác S j trong thời gian O ( s n T ) , bị chi phối bởi chi phí xóa cây tìm kiếm đại diện cho S jT cho mỗi bộ khác S j , trong đó n T = | T | .SSjO(snT)SjTSjnT=|T|

Nhận xét

Nếu bạn chỉ mong đợi thực hiện một số lượng bộ không đổi, thì thời gian chạy ở trên giảm xuống:

  • Khởi tạo: O(1)

  • phần tử chèn: O(logn)

  • kiểm tra giao lộ (và truy xuất giao lộ): O(1)

  • loại bỏ phần tử: O(lognT)

  • thiết lập xóa: O(nS)

Trong đó là kích thước của tập lớn nhất trong sổ đăng ký và n T = | T | cho tập T mà bạn đang hoạt động.nnT=|T|T

Nếu bạn muốn có các bộ , trong đó V là vũ trụ của bạn, bạn có thể cần một cấu trúc dữ liệu khác nếu bạn muốn các hoạt động này hoạt động trong thời gian tuyến tính phụ. Tuy nhiên, nếu bạn có các cặp tập hợp có giao điểm mà bạn biết bạn sẽ không bao giờ kiểm tra, bạn có thể giảm kích thước của chỉ mục cho các bộ (bằng cách không bao gồm bất kỳ bộ nào có giao lộ bạn sẽ kiểm tra) hoặc sử dụng nhiều hơn một sổ đăng ký ( một cho mỗi bộ sưu tập có giao điểm mà bạn có thể kiểm tra). Trong thực tế, một sổ đăng ký chỉ hữu ích nếu bạn muốn kiểm soát tập trung để đảm bảo rằng mỗi cặp bộ có một bản ghi của nhau trong chỉ mục: trong một số trường hợp, nó có thể thực tế trong việc khởi tạo một bộ S , chỉ đơn giản là ghi lạiO(|V|)VSad hoc mỗi bộ mới vào các chỉ số của các bộ có giao điểm với S mà bạn quan tâm.TS


5

Có các cấu trúc dữ liệu cho phép bạn thực hiện việc này trong thời gian ngắn hơn thời gian tuyến tính, ngay cả đối với các đầu vào trong trường hợp xấu nhất. Xem http://research.microsoft.com/pub/173795/vldb11intersection.pdf (và các tài liệu tham khảo trong đó).

Nếu hai bộ S và T của bạn có một giao điểm lớn và bạn có một từ điển cho S, việc tìm kiếm các phần tử của T theo thứ tự ngẫu nhiên sẽ nhanh chóng cung cấp cho bạn một yếu tố chung. Trường hợp khó khăn nhất là khi kích thước giao lộ là 0 hoặc 1.


3

Thông thường ngôn ngữ lập trình bạn chọn sẽ hỗ trợ cấu trúc dữ liệu với các yếu tố độc đáo. Nói chung, có ba cách tiếp cận phổ biến: Cây, Băm và Bitmasks. Các phần tử cây phải có thể so sánh được, các phần tử Hash phải có thể băm và các phần tử Bitmask phải có một số cách chuyển đổi sang số nguyên.

Một tập hợp cây sẽ hỗ trợ chèn trong O (log n) và kiểm tra giao nhau trong Trường hợp xấu nhất O (n log n).

Một bộ băm sẽ hỗ trợ chèn trong Amortized O (1 * h) trong đó 'h' là thời gian chạy của thuật toán băm và kiểm tra giao nhau trong Trường hợp xấu nhất O (n).

Các bộ Bitmask thường không được sử dụng như bộ cây và bộ băm.


2
Đây sẽ là một câu trả lời Stack Overflow khá , nhưng ở đây chúng tôi muốn một số chi tiết về cách thứclý do tại sao nó hoạt động.
Raphael

3

Nếu trường hợp của bạn cho phép câu trả lời dương tính giả, tôi sẽ sử dụng Bộ lọc Bloom với một hàm băm duy nhất.

Bạn có thể thực hiện nó như sau:

Bắt đầu một tập hợp trống

  • Bnn

Thêm một yếu tố vào một bộ.

  • B[hash(element)]=1

Cho hai bộ (B1, B2), báo cáo xem chúng có giao nhau không.

  • B1 AND B2 = 0

Phức tạp

  • nO(1)
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.