Apa yang lebih baik, daftar kedekatan atau matriks kedekatan untuk masalah grafik di C ++?

129

Apa yang lebih baik, daftar kedekatan atau matriks kedekatan, untuk masalah grafik di C ++? Apa kelebihan dan kekurangan masing-masing?

magiix
sumber
21
Struktur yang Anda gunakan tidak bergantung pada bahasa tetapi pada masalah yang Anda coba selesaikan.
avakar
1
Maksud saya untuk penggunaan umum seperti algoritma djikstra, saya menanyakan pertanyaan ini karena saya tidak tahu apakah implementasi daftar tertaut patut dicoba karena lebih sulit untuk membuat kode daripada matriks adjacency.
magiix
Daftar di C ++ semudah mengetik std::list(atau lebih baik lagi, std::vector).
avakar
1
@avakar: atau std::dequeatau std::set. Itu tergantung pada cara grafik akan berubah seiring waktu dan algoritma apa yang ingin Anda jalankan padanya.
Alexandre C.22

Jawaban:

125

Tergantung masalahnya.

Matriks Adjacency

  • Menggunakan memori O (n ^ 2)
  • Sangat cepat untuk mencari dan memeriksa ada atau tidaknya tepi tertentu
    antara dua node O (1)
  • Lambat untuk beralih ke semua sisi
  • Lambat untuk menambah / menghapus node; operasi kompleks O (n ^ 2)
  • Cepat untuk menambahkan tepi baru O (1)

Daftar Adjacency

  • Penggunaan memori tergantung pada jumlah edge (bukan jumlah node),
    yang mungkin menghemat banyak memori jika matriks adjacency jarang
  • Menemukan ada atau tidak adanya sisi tertentu antara dua node
    sedikit lebih lambat dibandingkan dengan matriks O (k); dimana k adalah jumlah node tetangga
  • Cepat untuk melakukan iterasi ke semua sisi karena Anda dapat mengakses node tetangga secara langsung
  • Cepat untuk menambah / menghapus node; lebih mudah daripada representasi matriks
  • Menambahkan tepi baru O dengan cepat (1)
Mark Byers
sumber
daftar tertaut lebih sulit untuk dikodekan, menurut Anda apakah penerapannya layak meluangkan waktu untuk mempelajarinya?
magiix
11
@magiix: Ya, saya pikir Anda harus memahami cara membuat kode daftar tertaut jika diperlukan, tetapi juga penting untuk tidak menemukan kembali roda: cplusplus.com/reference/stl/list
Mark Byers
Adakah yang bisa memberikan tautan dengan kode bersih untuk mengatakan luasnya pencarian pertama dalam format daftar tertaut ??
magiix
78

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.

keyser
sumber
9
+1 untuk wawasan, tetapi ini harus diperbaiki dengan struktur data aktual yang digunakan untuk menyimpan daftar kedekatan. Anda mungkin ingin menyimpan daftar kedekatannya untuk setiap simpul sebagai peta atau vektor, dalam hal ini angka sebenarnya dalam rumus Anda harus diperbarui. Juga, perhitungan serupa dapat digunakan untuk menilai titik impas untuk kompleksitas waktu algoritma tertentu.
Alexandre C.22
3
Ya, rumus ini untuk skenario tertentu. Jika Anda menginginkan jawaban kasar, lanjutkan dan gunakan rumus ini, atau modifikasi sesuai dengan spesifikasi Anda sesuai kebutuhan (misalnya, kebanyakan orang memiliki komputer 64 bit saat ini :))
keyser
1
Bagi yang tertarik, rumus untuk titik putus (jumlah maksimum rata-rata tepi dalam grafik n node) adalah e = n / s, di mana sukuran penunjuk.
deceleratedcaviar
33

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.

masukkan deskripsi gambar di sini

John Red
sumber
Jika tidak diketahui apakah grafiknya padat atau jarang, apakah tepat untuk mengatakan bahwa kompleksitas ruang untuk daftar kedekatan adalah O (v + e)?
Untuk sebagian besar algoritme praktis, salah satu operasi terpenting adalah melakukan iterasi melalui semua tepi keluar dari simpul tertentu. Anda mungkin ingin menambahkannya ke daftar Anda - ini adalah O (derajat) untuk AL dan O (V) untuk AM.
maks
@johnred bukan lebih baik untuk mengatakan bahwa Menambahkan simpul (waktu) untuk AL adalah O (1) karena alih-alih O (en) karena kita tidak benar-benar menambahkan tepi saat menambahkan simpul. Menambahkan tepi dapat dianggap sebagai operasi terpisah. Untuk AM masuk akal untuk menghitung tetapi bahkan di sana kita hanya perlu menginisialisasi baris dan kolom yang relevan dari simpul baru ke nol. Penambahan tepi bahkan untuk AM dapat dihitung secara terpisah.
Usman
Bagaimana cara menambahkan simpul ke AL ​​O (V)? Kita harus membuat matriks baru, menyalin nilai sebelumnya ke dalamnya. Ini harus O (v ^ 2).
Alex_ban
19

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.

Alex Ntousias
sumber
bagaimana jika Anda menggunakan kamus untuk menyimpan daftar kedekatan, yang akan memberi Anda adanya keunggulan dalam O (1) waktu diamortisasi.
Rohith Yeravothula
10

Mari kita asumsikan kita memiliki grafik yang memiliki n jumlah node dan m jumlah edge,

Contoh grafik
masukkan deskripsi gambar di sini

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:

if(matrix[1][2] == 1)

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

masukkan deskripsi gambar di sini
Anda harus membuat pilihan sesuai dengan kebutuhan Anda. Karena reputasi saya, saya tidak bisa meletakkan gambar matriks, maaf untuk itu

Muhammed Kadir
sumber
7

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

Biner Nerd
sumber
Terima kasih, saya akan memeriksa perpustakaan ini
magiix
1 untuk meningkatkan grafik. Inilah cara untuk pergi (tentu saja kecuali jika itu untuk tujuan pendidikan)
Tristram Gräbener
5

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.)

Evgeni Sergeev
sumber
2
Untuk algoritme seperti DFS dan BFS, jika Anda menggunakan matriks maka Anda perlu memeriksa seluruh baris setiap kali Anda ingin menemukan node yang berdekatan, sedangkan Anda sudah memiliki node yang berdekatan dalam daftar yang berdekatan. Menurut Anda mengapa an adjacency list is as good as a matrixdalam kasus tersebut?
realUser404
@ realUser404 Tepatnya, memindai seluruh baris matriks adalah operasi O (n). Daftar kedekatan lebih baik untuk grafik yang jarang ketika Anda perlu melintasi semua tepi keluar, mereka dapat melakukannya di O (d) (d: derajat node). Matriks memiliki kinerja cache yang lebih baik daripada daftar ketetanggaan, karena akses berurutan, jadi untuk grafik yang agak padat, pemindaian matriks dapat lebih masuk akal.
Jochem Kuijpers
3

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 0bit 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 / suntuk menentukan jumlah maksimum tepi, di mana sukuran 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 n300, jumlah optimal tepi per node yang menggunakan daftar kedekatan adalah:

= 300 / 64
= 4

Jika kita memasukkan ini ke rumus keyser5053, d = e / n^2(di mana ejumlah total tepi), kita dapat melihat bahwa kita berada di bawah break point ( 1 / s):

d = (4 * 300) / (300 * 300)
d < 1/64
aka 0.0133 < 0.0156

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.

= 300 / 16
= 18

d = ((18 * 300) / (300^2))
d < 1/16
aka 0.06 < 0.0625

Masing-masing contoh ini mengabaikan overhead daftar kedekatan itu sendiri ( 64*2untuk vektor dan 64 bit pointer).

kaviar yang melambat
sumber
Saya tidak mengerti bagiannya d = (4 * 300) / (300 * 300), bukankah seharusnya d = 4 / (300 * 300)? Karena rumusnya adalah d = e / n^2.
Saurabh
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?

ChrisOdney
sumber
1

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 berdekatan O(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).

maks
sumber
1

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.

justcoding121
sumber
Untuk matriks ketetanggaan, hapus simpul mengambil O (V ^ 2) bukan O (V)
Saurabh
Iya. Tetapi jika Anda menggunakan kamus untuk melacak indeks array, maka itu akan turun ke O (V). Lihat implementasi RemoveVertex ini .
justcoding121