Struktur Data untuk Set persimpangan?

21

Apakah ada struktur data yang memelihara kumpulan himpunan (himpunan terbatas hingga) yang mendukung operasi berikut? Setiap waktu menjalankan sublinear akan dihargai?

  1. Init set kosong.
  2. Tambahkan elemen ke set.
  3. Diberi dua set, laporkan apakah mereka berpotongan.
Dawei Huang
sumber
1
Ini adalah pertanyaan yang sangat umum, karena setiap struktur data dapat mendukung operasi tersebut dengan domain terbatas. Bisakah Anda sedikit lebih spesifik? Misalnya. Kompleksitas apa yang Anda butuhkan, apa yang rela Anda korbankan untuk mendapatkan operasi yang ditetapkan, dll.
Bartosz Przybylski

Jawaban:

13

Jika setiap set menyimpan catatan apa yang ada, dan Anda memiliki total set, Anda dapat dengan mudah mengubah struktur data apa pun untuk koleksi ( mis. Pohon pencarian biner, dll. ) Menjadi satu di mana Anda dapat mengambil unsur persimpangan dua set dalam waktu O ( log s ) .s>0O(logs)

  • Setiap set harus memiliki pengidentifikasi unik dari beberapa set yang dipesan secara total. Jika Anda secara eksplisit memberi nama set Anda maka pengidentifikasi hanya bisa menjadi indeks.S1,S2,

  • Anda harus menerapkan "registri" set; struktur data yang memelihara koleksi semua set yang telah Anda tetapkan. Registri harus diimplementasikan sebagai struktur data pohon pencarian, untuk memungkinkan pengambilan mudah ( misalnya  jika Anda ingin menghapus set) dan traversal linear-time dari set.

  • Setiap set juga mempertahankan "index" dari masing-masing set lain - bukan salinan dari mereka, melainkan struktur data yang diindeks oleh label set lainnya. Indeks ini akan digunakan untuk mempertahankan, untuk setiap set S k , sebuah pohon pencarian biner dari semua elemen dari S jS k . (Dua set S j dan S k membagikan satu salinan dari pohon pencarian itu.)SjSkSjSkSjSk

Inisialisasi

Inisialisasi set terdiri dari O ( 1 ) operasi untuk menginisialisasi pohon unsur-unsurnya, O ( s ) operasi seperti yang Anda menginisialisasi (menyalin dari registri) indeks untuk set T , dan O ( s log s ) operasi saat Anda melintasi registri untuk menambahkan T ke indeks masing-masing set lainnya S j . Dalam indeks T , kami membuat pohon pencarian yang mewakili T S j = T=O(1)O(s)TO(slogs)TSjTTSj=untuk perangkat lainnya ; kami menyalin pointer yang sama untuk indeks S j .SjSj

Menambahkan elemen ke set T

Menambahkan beberapa ke himpunan T membutuhkan waktu O ( log n T ) seperti biasa, di mana n T = | T | . Kami juga tes untuk keanggotaan x di masing-masing set lain S 1 , S 2 , ... , yang membutuhkan waktu O ( log n S 1 + log n S 2 + ) O ( s log nxVTO(lognT)nT=|T|xS1,S2, mana n = | V | adalah ukuran alam semesta (atau himpunan terbesar S j ) dan s adalah jumlah himpunan dalam registri. Untuk setiap set S j sehingga x S j , juga insert x ke dalam indeks untuk set S jT . Untuk setiap seperti set S j , ini membutuhkan waktu O ( log s + log n T ) waktu, untuk mencari S j

O(lognS1+lognS2+)O(slogn),
n=|V|SjsSjxSjxSjTSjO(logs+lognT)Sjdalam indeks dan untuk memasukkan x dalam S jT ; di semua set S 1 , S 2 , ... ini membutuhkan waktu O ( s log s + s log n T ) . Jika kita menganggap bahwa jumlah set S j jauh lebih kecil dari ukuran alam semesta V (yaitu, jika kita menganggap s « n ), total waktu untuk penyisipan elemen kemudian O ( s log n )TxSjTS1,S2,O(slogs+slognT)SjVsnO(slogn).

Jika Anda tidak mengizinkan duplikat di set, kita dapat menghemat waktu dalam kasus yang sudah dengan berpantang tes keanggotaan dan sisipan untuk set lain T . "Penyisipan" dalam hal x sudah ada maka hanya membutuhkan waktu O ( log n T ) .xSTxO(lognT)

Pengujian titik-temu

Indeks setiap set dipertahankan dengan tepat untuk memungkinkan evaluasi cepat apakah dua set dan S k berpotongan. Untuk satu set S j , hanya dengan memeriksa indeks untuk set S k , kita tidak bisa hanya menentukan dalam waktu O ( log s ) apakah S j berpotongan S k , tetapi kita juga bisa mengambil sebuah pohon biner yang berisi seluruh himpunan S jS k .SjSkSjSkO(logs)SjSkSjSk

Penghapusan Elemen

Untuk menghapus elemen dari himpunan T , kami menghapusnya tidak hanya dari pohon pencarian untuk T itu sendiri, tetapi dari masing-masing persimpangan S jT untuk himpunan S j dalam indeksnya. Ini membutuhkan waktu O ( s log n T ) , di mana n T = | T | .xTTSjTSjO(slognT)nT=|T|

Tetapkan Penghapusan

Karena overhead mencari registri, jika Anda memiliki banyak set, mungkin diinginkan untuk menghapus set setelah mereka tidak lagi diperlukan. Dengan melintasi seluruh registri, kami dapat menghapus dari indeks semua set lainnya S j dalam waktu O ( s n T ) , didominasi oleh biaya menghapus pohon pencarian yang mewakili S jT untuk masing-masing set lainnya S j , di mana n T = | T | .SSjO(snT)SjTSjnT=|T|

Catatan

Jika Anda hanya berharap untuk mengimplementasikan jumlah set yang konstan, maka run-times di atas berkurang menjadi:

  • inisialisasi: O(1)

  • penyisipan elemen: O(logn)

  • pengujian persimpangan (dan pengambilan persimpangan): O(1)

  • penghapusan elemen: O(lognT)

  • setel penghapusan: O(nS)

di mana adalah ukuran set terbesar dalam registri, dan n T = | T | untuk set T yang Anda operasikan.nnT=|T|T

Jika Anda berharap memiliki set , di mana V adalah semesta Anda, Anda mungkin memerlukan struktur data yang berbeda jika Anda ingin operasi ini beroperasi dalam waktu sub-linear. Namun, jika Anda memiliki pasangan set yang persimpangannya Anda tahu tidak akan pernah Anda uji, Anda mungkin dapat mengurangi ukuran indeks untuk set tersebut (dengan tidak termasuk set yang persimpangannya akan Anda uji) atau menggunakan lebih dari satu registry ( satu untuk setiap kumpulan set yang persimpangannya mungkin Anda uji). Bahkan, registri hanya berguna jika Anda ingin kontrol terpusat untuk memastikan bahwa setiap pasangan set memiliki catatan satu sama lain dalam indeks: itu mungkin praktis dalam beberapa kasus, pada inisialisasi set S , hanya untuk merekamO(|V|)VSad hoc setiap set baru ke dalam indeks set lainnya yang simpang dengan S yang Anda minati.TS

Niel de Beaudrap
sumber
6

Ada struktur data yang memungkinkan Anda melakukan ini dalam waktu kurang dari linear, bahkan untuk input terburuk. Lihat http://research.microsoft.com/pubs/173795/vldb11intersection.pdf (dan referensi makalah di sana).

Jika dua set S dan T Anda memiliki persimpangan besar dan Anda memiliki kamus untuk S, mencari elemen T dalam urutan acak akan dengan cepat memberi Anda elemen umum. Kasus yang paling sulit adalah ketika ukuran persimpangan adalah 0 atau 1.

Rasmus Pagh
sumber
3

Biasanya bahasa pemrograman pilihan Anda akan mendukung struktur data dengan elemen unik. Secara umum ada tiga pendekatan populer: Pohon, Hash dan Bitmask. Elemen pohon harus dapat dibandingkan, elemen hash harus hashable dan elemen Bitmask harus memiliki cara konversi ke integer.

Rangkaian pohon akan mendukung penyisipan dalam O (log n) dan pengujian persimpangan di Worst Case O (n log n).

Hash-set akan mendukung penyisipan dalam Amortized O (1 * h) di mana 'h' adalah waktu berjalan dari algoritma hashing, dan tes persimpangan di Worst Case O (n).

Set bitmask umumnya tidak digunakan seperti set pohon dan hash.

Karl Damgaard Asmussen
sumber
2
Ini akan menjadi jawaban Stack Overflow yang layak , tetapi di sini kami ingin beberapa detail tentang bagaimana dan mengapa ia bekerja.
Raphael
3

Jika kasing Anda memberikan jawaban positif palsu, saya akan menggunakan Bloom Filter dengan fungsi hash tunggal.

Anda dapat menerapkannya sebagai berikut:

Init set kosong

  • Bnn

Tambahkan elemen ke set.

  • B[hSebuahsh(element)]=1

Diberi dua set (B1, B2), laporkan apakah mereka berpotongan.

  • B1 SEBUAHND B2 = 0

Kompleksitas

  • nHAI(1)
Grisha Weintraub
sumber