Apa yang lebih baik, daftar kedekatan atau matriks kedekatan, untuk masalah grafik di C ++? Apa kelebihan dan kekurangan masing-masing?
c++
graph
adjacency-list
adjacency-matrix
magiix
sumber
sumber
std::list
(atau lebih baik lagi,std::vector
).std::deque
ataustd::set
. Itu tergantung pada cara grafik akan berubah seiring waktu dan algoritma apa yang ingin Anda jalankan padanya.Jawaban:
Tergantung masalahnya.
Matriks Adjacency
antara dua node O (1)
Daftar Adjacency
yang mungkin menghemat banyak memori jika matriks adjacency jarang
sedikit lebih lambat dibandingkan dengan matriks O (k); dimana k adalah jumlah node tetangga
sumber
Jawaban ini bukan hanya untuk C ++ karena semua yang disebutkan adalah tentang struktur datanya sendiri, apa pun bahasanya. Dan, jawaban saya adalah mengasumsikan bahwa Anda mengetahui struktur dasar daftar dan matriks kedekatan.
Penyimpanan
Jika memori adalah perhatian utama Anda, Anda dapat mengikuti rumus ini untuk grafik sederhana yang memungkinkan loop:
Matriks adjacency menempati n 2 /8 ruang byte (satu bit per entri).
Daftar kedekatan menempati ruang 8e, di mana e adalah jumlah tepi (komputer 32bit).
Jika kita mendefinisikan kepadatan grafik sebagai d = e / n 2 (jumlah tepi dibagi dengan jumlah maksimum tepi), kita dapat menemukan "breakpoint" di mana daftar menggunakan lebih banyak memori daripada matriks:
8e> n 2 /8 ketika d> 1/64
Jadi dengan angka-angka ini (masih spesifik 32-bit) breakpoint mendarat di 1/64 . Jika densitas (e / n 2 ) lebih besar dari 1/64, maka matriks lebih disukai jika Anda ingin menghemat memori.
Anda dapat membaca tentang ini di wikipedia (artikel tentang matriks adjacency) dan banyak situs lainnya.
Catatan samping : Seseorang dapat meningkatkan efisiensi ruang dari matriks ketetanggaan dengan menggunakan tabel hash di mana kuncinya adalah pasangan simpul (hanya tidak diarahkan).
Iterasi dan pencarian
Daftar kedekatan adalah cara kompak untuk hanya mewakili tepi yang ada. Namun, ini datang dengan biaya pencarian tepi tertentu yang mungkin lambat. Karena setiap daftar sepanjang derajat simpul, waktu pencarian kasus terburuk untuk memeriksa tepi tertentu dapat menjadi O (n), jika daftar tidak berurutan. Namun, mencari tetangga dari simpul menjadi sepele, dan untuk grafik yang jarang atau kecil biaya iterasi melalui daftar kedekatan mungkin dapat diabaikan.
Di sisi lain, matriks kedekatan menggunakan lebih banyak ruang untuk menyediakan waktu pencarian yang konstan. Karena setiap entri yang mungkin ada, Anda dapat memeriksa keberadaan edge dalam waktu yang konstan menggunakan indeks. Namun, pencarian tetangga membutuhkan O (n) karena Anda perlu memeriksa semua tetangga yang memungkinkan. Kelemahan ruang yang jelas adalah bahwa untuk grafik yang jarang, banyak bantalan ditambahkan. Lihat pembahasan memori di atas untuk informasi lebih lanjut tentang ini.
Jika Anda masih tidak yakin apa yang harus digunakan : Sebagian besar masalah dunia nyata menghasilkan grafik yang jarang dan / atau besar, yang lebih cocok untuk representasi daftar kedekatan. Mereka mungkin tampak lebih sulit untuk diterapkan tetapi saya jamin mereka tidak, dan ketika Anda menulis BFS atau DFS dan ingin mengambil semua tetangga dari sebuah node, mereka hanya berjarak satu baris kode. Namun, perhatikan bahwa saya tidak mempromosikan daftar kedekatan secara umum.
sumber
e = n / s
, di manas
ukuran penunjuk.Oke, saya telah menyusun kompleksitas Ruang dan Waktu dari operasi dasar pada grafik.
Gambar di bawah seharusnya sudah cukup jelas.
Perhatikan bagaimana Adjacency Matrix lebih disukai ketika kita mengharapkan grafik menjadi padat, dan bagaimana Adjacency List lebih disukai ketika kita mengharapkan grafik menjadi jarang.
Saya telah membuat beberapa asumsi. Tanyakan kepada saya apakah suatu kompleksitas (Waktu atau Ruang) membutuhkan klarifikasi. (Misalnya, Untuk grafik renggang, saya menganggap En sebagai konstanta kecil, karena saya berasumsi bahwa penambahan titik baru hanya akan menambahkan beberapa tepi, karena kita mengharapkan grafik tetap jarang bahkan setelah menambahkannya puncak.)
Tolong beritahu saya jika ada kesalahan.
sumber
Itu tergantung pada apa yang Anda cari.
Dengan matriks ketetanggaan, Anda dapat menjawab dengan cepat pertanyaan mengenai apakah tepi tertentu antara dua simpul termasuk dalam grafik, dan Anda juga dapat memasukkan dan menghapus tepi dengan cepat. The downside adalah bahwa Anda harus menggunakan ruang yang berlebihan, terutama untuk grafik dengan banyak simpul, yang sangat tidak efisien terutama jika grafik jarang.
Di sisi lain, dengan daftar kedekatan , lebih sulit untuk memeriksa apakah tepi tertentu ada dalam grafik, karena Anda harus mencari melalui daftar yang sesuai untuk menemukan tepi, tetapi lebih hemat ruang.
Namun secara umum, daftar kedekatan adalah struktur data yang tepat untuk sebagian besar aplikasi grafik.
sumber
Mari kita asumsikan kita memiliki grafik yang memiliki n jumlah node dan m jumlah edge,
Contoh grafik
Adjacency Matrix: Kami membuat matriks yang memiliki n jumlah baris dan kolom sehingga dalam memori akan memakan ruang yang sebanding dengan n 2 . Memeriksa apakah dua node bernama u dan v memiliki tepi di antara keduanya akan membutuhkan waktu Θ (1). Misal memeriksa (1, 2) apakah sebuah edge akan terlihat seperti berikut pada kode:
Jika Anda ingin mengidentifikasi semua sisi, Anda harus melakukan iterasi pada matriks karena ini akan membutuhkan dua loop bersarang dan akan membutuhkan Θ (n 2 ). (Anda dapat menggunakan bagian segitiga atas dari matriks untuk menentukan semua sisi tetapi akan menjadi lagi Θ (n 2 ))
Daftar Adjacency: Kami membuat daftar yang setiap node juga menunjuk ke daftar lain. Daftar Anda akan memiliki n elemen dan setiap elemen akan mengarah ke daftar yang memiliki jumlah item yang sama dengan jumlah tetangga dari node ini (lihat gambar untuk visualisasi yang lebih baik). Sehingga akan membutuhkan ruang dalam memori yang sebanding dengan n + m . Memeriksa apakah (u, v) adalah sebuah sisi akan membutuhkan waktu O (derajat (u)) di mana derajat (u) sama dengan jumlah tetangga dari u. Karena paling banyak, Anda harus mengulang daftar yang ditunjukkan oleh u. Mengidentifikasi semua sisi akan membutuhkan Θ (n + m).
Daftar kedekatan grafik contoh
Anda harus membuat pilihan sesuai dengan kebutuhan Anda. Karena reputasi saya, saya tidak bisa meletakkan gambar matriks, maaf untuk itu
sumber
Jika Anda melihat analisis grafik di C ++, mungkin tempat pertama untuk memulai adalah pustaka grafik boost , yang mengimplementasikan sejumlah algoritme termasuk BFS.
EDIT
Pertanyaan sebelumnya tentang SO ini mungkin akan membantu:
cara-membuat-ac-boost-undirected-graph-and-traverse-it-in-depth-first-searc h
sumber
Ini paling baik dijawab dengan contoh.
Pikirkan Floyd-Warshall misalnya. Kami harus menggunakan matriks kedekatan, atau algoritme akan menjadi lebih lambat secara asimtotik.
Atau bagaimana jika itu adalah grafik yang padat pada 30.000 simpul? Kemudian matriks kedekatan mungkin masuk akal, karena Anda akan menyimpan 1 bit per pasang simpul, bukan 16 bit per tepi (minimum yang Anda perlukan untuk daftar kedekatan): itu 107 MB, bukan 1,7 GB.
Tetapi untuk algoritme seperti DFS, BFS (dan yang menggunakannya, seperti Edmonds-Karp), Penelusuran prioritas pertama (Dijkstra, Prim, A *), dll., Daftar kedekatan sama baiknya dengan matriks. Nah, matriks mungkin memiliki sedikit tepi jika grafiknya padat, tetapi hanya dengan faktor konstanta yang biasa-biasa saja. (Berapa banyak? Ini masalah eksperimen.)
sumber
an adjacency list is as good as a matrix
dalam kasus tersebut?Untuk menambah jawaban keyser5053 tentang penggunaan memori.
Untuk setiap graf berarah, matriks kedekatan (pada 1 bit per tepi) menggunakan
n^2 * (1)
bit memori.Untuk grafik lengkap , daftar kedekatan (dengan 64 bit pointer) menggunakan
n * (n * 64)
bit memori, tidak termasuk overhead daftar.Untuk grafik yang tidak lengkap, daftar kedekatan memakan
0
bit memori, tidak termasuk overhead daftar.Untuk daftar kedekatan, Anda dapat menggunakan rumus berikut untuk menentukan jumlah maksimum tepi (
e
) sebelum matriks kedekatan optimal untuk memori.edges = n^2 / s
untuk menentukan jumlah maksimum tepi, di manas
ukuran penunjuk platform.Jika grafik Anda diperbarui secara dinamis, Anda dapat mempertahankan efisiensi ini dengan jumlah tepi rata-rata (per node) sebesar
n / s
.Beberapa contoh dengan 64 bit pointer dan grafik dinamis (Grafik dinamis memperbarui solusi masalah secara efisien setelah perubahan, daripada menghitung ulang dari awal setiap kali setelah perubahan dilakukan.)
Untuk graf berarah, di mana
n
300, jumlah optimal tepi per node yang menggunakan daftar kedekatan adalah:Jika kita memasukkan ini ke rumus keyser5053,
d = e / n^2
(di manae
jumlah total tepi), kita dapat melihat bahwa kita berada di bawah break point (1 / s
):Namun, 64 bit untuk penunjuk bisa berlebihan. Jika Anda malah menggunakan bilangan bulat 16bit sebagai offset penunjuk, kita dapat memasukkan hingga 18 tepi sebelum titik putus.
Masing-masing contoh ini mengabaikan overhead daftar kedekatan itu sendiri (
64*2
untuk vektor dan 64 bit pointer).sumber
d = (4 * 300) / (300 * 300)
, bukankah seharusnyad = 4 / (300 * 300)
? Karena rumusnya adalahd = e / n^2
.Bergantung pada implementasi Adjacency Matrix, 'n' dari grafik harus diketahui lebih awal untuk implementasi yang efisien. Jika grafik terlalu dinamis dan membutuhkan ekspansi matriks sesekali itu juga dapat dihitung sebagai sisi negatifnya?
sumber
Jika Anda menggunakan tabel hash dan bukan matriks atau daftar ketetanggaan, Anda akan mendapatkan waktu dan ruang run-O besar yang lebih baik atau sama untuk semua operasi (memeriksa edge adalah
O(1)
, mendapatkan semua edge yang berdekatanO(degree)
, dll.).Ada beberapa overhead faktor konstan meskipun untuk run-time dan space (tabel hash tidak secepat daftar tertaut atau pencarian array, dan membutuhkan ruang ekstra yang layak untuk mengurangi tabrakan).
sumber
Saya hanya akan menyentuh tentang mengatasi trade-off dari representasi daftar kedekatan reguler, karena jawaban lain telah mencakup aspek lain.
Dimungkinkan untuk merepresentasikan grafik dalam daftar kedekatan dengan kueri EdgeExists dalam waktu konstan diamortisasi, dengan memanfaatkan struktur data Dictionary dan HashSet . Idenya adalah untuk menyimpan simpul dalam kamus, dan untuk setiap simpul, kita menyimpan himpunan hash yang merujuk ke simpul lain yang memiliki tepi dengannya.
Salah satu trade-off kecil dalam implementasi ini adalah bahwa ia akan memiliki kompleksitas ruang O (V + 2E) daripada O (V + E) seperti dalam daftar ketetanggaan biasa, karena tepi diwakili dua kali di sini (karena setiap simpul memiliki set hashnya sendiri tepi). Tetapi operasi seperti AddVertex , AddEdge , RemoveEdge dapat dilakukan dalam waktu diamortisasi O (1) dengan implementasi ini, kecuali untuk RemoveVertex yang mengambil O (V) seperti matriks adjacency. Ini berarti bahwa selain kesederhanaan implementasi, matriks ketetanggaan tidak memiliki keunggulan khusus. Kami dapat menghemat ruang pada grafik renggang dengan kinerja yang hampir sama dalam penerapan daftar kedekatan ini.
Lihat implementasi di bawah ini di repositori Github C # untuk detailnya. Perhatikan bahwa untuk grafik berbobot itu menggunakan kamus bertingkat dan bukan kombinasi kumpulan hash kamus untuk mengakomodasi nilai bobot. Demikian pula untuk grafik terarah, ada set hash terpisah untuk tepi masuk & keluar.
Algoritma-Lanjutan
Catatan: Saya yakin dengan menggunakan penghapusan malas, kita dapat lebih mengoptimalkan operasi RemoveVertex ke O (1) diamortisasi, meskipun saya belum menguji gagasan itu. Misalnya, setelah penghapusan cukup tandai simpul sebagai dihapus dalam kamus, dan kemudian hapus tepi yatim piatu dengan malas selama operasi lain.
sumber