Hangisi daha iyi, C ++ 'daki grafik problemleri için bitişiklik listeleri veya bitişik matrisler?


129

C ++ 'daki grafik problemleri için daha iyi olan bitişiklik listeleri veya bitişik matris nedir? Her birinin avantajları ve dezavantajları nelerdir?


21
Kullandığınız yapı, dile değil, çözmeye çalıştığınız soruna bağlıdır.
avakar

1
Djikstra algoritması gibi genel kullanımı kastettim, bu soruyu sordum çünkü bilmiyorum bağlantılı liste uygulaması denemeye değer çünkü kodlamanın bitişik matrisinden daha zor.
magiix

C ++ 'daki listeler yazmak kadar kolaydır std::list(veya daha iyisi std::vector).
avakar

1
@avakar: veya std::dequeveya std::set. Grafiğin zamanla nasıl değişeceğine ve bunlar üzerinde hangi algoritmaları çalıştırmayı düşündüğünüze bağlıdır.
Alexandre C.

Yanıtlar:


125

Soruna bağlı.

Bitişiklik Matrisi

  • O (n ^ 2) hafızasını kullanır

  • Herhangi iki düğüm arasında belirli bir kenarın varlığını veya yokluğunu aramak ve kontrol etmek hızlıdır O (1)
  • Tüm kenarlarda yineleme yavaş
  • Bir düğüm eklemek / silmek yavaştır; karmaşık bir işlem O (n ^ 2)
  • Yeni bir kenar eklemek hızlıdır O (1)

Komşuluk Listesi

  • Bellek kullanımı
    , bitişik matris seyrekse çok fazla bellek tasarrufu sağlayan kenar sayısına (düğüm sayısına değil) bağlıdır.
  • Herhangi iki düğüm arasında belirli bir kenarın varlığını veya yokluğunu bulmak,
    O (k) matrisinden biraz daha yavaştır; k, komşu düğümlerin sayısıdır
  • Tüm kenarlarda yineleme yapmak hızlıdır çünkü herhangi bir düğüm komşusuna doğrudan erişebilirsiniz
  • Düğüm eklemek / silmek hızlıdır; matris gösteriminden daha kolay
  • Yeni bir kenar eklemek hızlı O (1)

bağlantılı listeleri kodlamak daha zordur, uygulamanın onu öğrenmek için biraz zaman harcamaya değer olduğunu düşünüyor musunuz?
magiix

11
@magiix: Evet, gerekirse bağlantılı listeleri nasıl kodlayacağınızı anlamanız gerektiğini düşünüyorum, ancak tekerleği yeniden icat etmemek de önemlidir: cplusplus.com/reference/stl/list
Mark Byers

herhangi biri, bağlantılı listeler biçiminde Genişlik ilk arama demek için temiz bir kod içeren bir bağlantı sağlayabilir mi?
magiix


78

Bu cevap sadece C ++ için değildir, çünkü bahsedilen her şey, dilden bağımsız olarak veri yapılarının kendisiyle ilgilidir. Ve cevabım, bitişiklik listelerinin ve matrislerinin temel yapısını bildiğinizi varsaymaktır.

Hafıza

Bellek birincil endişenizse, döngülere izin veren basit bir grafik için bu formülü uygulayabilirsiniz:

Bir komşuluk matrisi N kaplar 2 /8 bayt alanı (giriş başına bir bit).

Bir bitişiklik listesi, 8e alanını kaplar, burada e, kenar sayısıdır (32 bit bilgisayar).

Grafiğin yoğunluğunu d = e / n 2 (kenar sayısı bölü maksimum kenar sayısı) olarak tanımlarsak, bir listenin bir matristen daha fazla bellek kapladığı "kesme noktası" nı bulabiliriz:

8e> n 2 /8 d> 1/64

Yani bu sayılarla (hala 32 bit spesifik) kesme noktası 1 / 64'e iner . Yoğunluk (e / n 2 ) 1 / 64'ten büyükse, hafızadan tasarruf etmek istiyorsanız bir matris tercih edilir.

Bunu wikipedia'da (bitişik matrislerle ilgili makale) ve diğer pek çok sitede okuyabilirsiniz .

Yan not : Anahtarların köşe çiftleri olduğu (yalnızca yönlendirilmemiş) bir karma tablo kullanılarak bitişik matrisin alan verimliliği artırılabilir.

Yineleme ve arama

Bitişiklik listeleri, yalnızca mevcut kenarları temsil etmenin kompakt bir yoludur. Bununla birlikte, bu, belirli kenarların muhtemelen yavaşça aranması pahasına gelir. Her liste bir tepe noktası kadar uzun olduğundan, belirli bir kenarı kontrol etmenin en kötü durum arama süresi, liste sıralı değilse O (n) olabilir. Bununla birlikte, bir tepe noktasının komşularına bakmak önemsiz hale gelir ve seyrek veya küçük bir grafik için bitişik listelerde yineleme maliyeti ihmal edilebilir olabilir.

Öte yandan bitişik matrisler, sabit arama süresi sağlamak için daha fazla alan kullanır. Olası her giriş var olduğundan, dizinleri kullanarak sabit zamanda bir kenarın varlığını kontrol edebilirsiniz. Ancak, olası tüm komşuları kontrol etmeniz gerektiğinden komşu araması O (n) alır. Bariz alan dezavantajı, seyrek grafikler için çok fazla dolgu eklenmesidir. Bununla ilgili daha fazla bilgi için yukarıdaki hafıza tartışmasına bakın.

Hala ne kullanacağınızdan emin değilseniz : Gerçek dünyadaki çoğu problem, bitişik liste temsilleri için daha uygun olan seyrek ve / veya büyük grafikler üretir. Uygulanması daha zor görünebilir, ancak sizi temin ederim ki, bir BFS veya DFS yazıp bir düğümün tüm komşularını getirmek istediğinizde, sadece bir satır kod uzaktadırlar. Ancak, genel olarak bitişiklik listelerini desteklemediğimi unutmayın.


9
İçgörü için +1, ancak bu, bitişik listeleri depolamak için kullanılan gerçek veri yapısı ile düzeltilmelidir. Her köşe için bitişik listesini bir harita veya vektör olarak saklamak isteyebilirsiniz, bu durumda formüllerinizdeki gerçek sayıların güncellenmesi gerekir. Ayrıca, benzer hesaplamalar, belirli algoritmaların zaman karmaşıklığı için başa baş noktaları değerlendirmek için kullanılabilir.
Alexandre C.

3
Evet, bu formül belirli bir senaryo içindir. Kaba bir cevap istiyorsanız, devam edin ve bu formülü kullanın veya ihtiyaç duyduğunuz şekilde spesifikasyonlarınıza göre değiştirin (örneğin, günümüzde çoğu insanın 64 bit bilgisayarı var :))
keyer

1
İlgilenenler için, kırılma noktası formülü (düğümlerin bir grafiğindeki maksimum ortalama kenar sayısı), işaretçi boyutu e = n / snerede s.
deceleratedcaviar

33

Tamam, temel işlemlerin Zaman ve Uzay karmaşıklıklarını grafikler üzerinde derledim.
Aşağıdaki resim kendinden açıklamalı olmalıdır.
Grafiğin yoğun olmasını beklediğimizde Bitişiklik Matrisinin ne kadar tercih edilebilir olduğuna ve grafiğin seyrek olmasını beklediğimizde Bitişiklik Listesinin ne kadar tercih edilebilir olduğuna dikkat edin.
Bazı varsayımlar yaptım. Bir karmaşıklığın (Zaman veya Uzay) açıklamaya ihtiyacı olup olmadığını bana sorun. (Örneğin, seyrek bir grafik için En'i küçük bir sabit olarak aldım, çünkü yeni bir tepe noktasının eklenmesinin yalnızca birkaç kenar ekleyeceğini varsaydım, çünkü bunu ekledikten sonra bile grafiğin seyrek kalmasını bekliyoruz. tepe).

Lütfen bana herhangi bir hata varsa söyle.

görüntü açıklamasını buraya girin


Grafiğin yoğun mu yoksa seyrek mi olduğunun bilinmemesi durumunda, bir bitişik listesi için uzay karmaşıklığının O (v + e) ​​olacağını söylemek doğru olur mu?

Çoğu pratik algoritma için, en önemli işlemlerden biri, belirli bir tepe noktasından çıkan tüm kenarları yinelemektir. Listenize eklemek isteyebilirsiniz - AL için O (derece) ve AM için O (V).
en fazla

@johnred, AL için bir köşe (zaman) eklemenin O (1) olduğunu söylemek daha iyi değil çünkü O (en) yerine bir köşe eklemeye kenar eklemiyoruz. Bir kenar eklemek ayrı bir işlem olarak ele alınabilir. AM için hesaba katmak mantıklıdır, ancak orada bile yeni köşe noktasının ilgili satırlarını ve sütunlarını sıfıra sıfırlamamız gerekir. AM için bile kenarların eklenmesi ayrı ayrı hesaba katılabilir.
Usman

AL O (V) 'ye bir köşe nasıl eklenir? Yeni bir matris oluşturmalı, önceki değerleri ona kopyalamalıyız. O (v ^ 2) olmalıdır.
Alex_ban

19

Ne aradığına bağlı.

İle komşuluk matrisleri iki köşe arasındaki belirli bir kenar grafiğinin aitse ilgili sorulara hızlı cevap verebilir ve ayrıca hızlı eklemeleri ve kenarlarının silmeleri sahip olabilir. Olumsuz Eğer özellikle grafik seyrek özellikle çok verimsiz birçok köşe ile grafikler için, aşırı alanı kullanmak zorunda olmasıdır.

Öte yandan, bitişik listeleri ile , belirli bir kenarın bir grafikte olup olmadığını kontrol etmek daha zordur, çünkü kenarı bulmak için uygun listede arama yapmanız gerekir, ancak bunlar daha fazla alan verimlidir.

Genel olarak, bitişik listeleri çoğu grafik uygulaması için doğru veri yapısıdır.


Ya bitişiklik listesini saklamak için sözlükler kullanırsanız, bu size O (1) amorti edilmiş zamanda bir avantaj sağlayacaktır.
Rohith Yeravothula

10

N düğüm sayısı ve m kenarı olan bir grafiğimiz olduğunu varsayalım.

Örnek grafik
görüntü açıklamasını buraya girin

Bitişiklik Matrisi: N sayıda satır ve sütuna sahip bir matris oluşturuyoruz, böylece bellekte n 2 ile orantılı olarak yer kaplayacaktır . U ve v olarak adlandırılan iki düğümün aralarında bir kenar olup olmadığını kontrol etmek Θ (1) zaman alacaktır. Örneğin (1, 2) 'yi kontrol etmek, kodda aşağıdaki gibi görünecektir:

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

Tüm kenarları tanımlamak istiyorsanız, matris üzerinde yinelemelisiniz, çünkü bu iki iç içe döngü gerektirecek ve Θ (n 2 ) alacaktır . (Tüm kenarları belirlemek için matrisin üst üçgen kısmını kullanabilirsiniz, ancak yine Θ (n 2 ) olacaktır)

Bitişiklik Listesi: Her düğümün başka bir listeye işaret ettiği bir liste oluşturuyoruz. Listeniz n öğeye sahip olacak ve her öğe, bu düğümün komşularının sayısına eşit sayıda öğe içeren bir listeyi gösterecektir (daha iyi görselleştirme için resme bakın). Böylece bellekte n + m ile orantılı yer kaplayacaktır . (U, v) 'nin bir kenar olup olmadığını kontrol etmek O (derece (u)) zaman alacaktır, burada deg (u) u'nun komşularının sayısına eşittir. Çünkü en fazla, u işaretinin işaret ettiği listeyi yinelemelisiniz. Tüm kenarların belirlenmesi Θ (n + m) alacaktır.

Örnek grafiğin bitişiklik listesi

görüntü açıklamasını buraya girin
Seçiminizi ihtiyaçlarınıza göre yapmalısınız. İtibarım yüzünden matris imajını koyamadım, bunun için üzgünüm


7

C ++ 'da grafik analizine bakıyorsanız, muhtemelen ilk başlayacağınız yer , BFS dahil bir dizi algoritmayı uygulayan destek grafik kitaplığı olacaktır .

DÜZENLE

SO ile ilgili bu önceki soru muhtemelen yardımcı olacaktır:

nasıl Oluşturulması-Ac-boost-yönlendirilmeyen-grafik-ve-traversi-o-derinlemesine-birinci searc h


Teşekkürler bu kütüphaneyi kontrol
edeceğim

Arttırma grafiği için +1.
Gitmenin

5

Bu, en iyi örneklerle yanıtlanır.

Örneğin Floyd-Warshall'ı düşünün . Bir bitişik matris kullanmalıyız, yoksa algoritma asimptotik olarak daha yavaş olacaktır.

Ya da 30.000 köşede yoğun bir grafikse? O zaman bir bitişik matrisi mantıklı olabilir, çünkü kenar başına 16 bit (bitişiklik listesi için ihtiyacınız olan minimum) yerine çift köşe başına 1 bit depolayacaksınız: 1,7 GB yerine 107 MB.

Ancak DFS, BFS (ve bunu kullananlar, Edmonds-Karp gibi), Öncelikli arama (Dijkstra, Prim, A *) vb. Gibi algoritmalar için bitişiklik listesi bir matris kadar iyidir. Eh, bir matris, grafik yoğun olduğunda hafif bir kenara sahip olabilir, ancak yalnızca dikkate değer bir sabit faktörle. (Ne kadar? Bu bir deney meselesi.)


2
DFS ve BFS gibi algoritmalar için, bir matris kullanıyorsanız, bitişik bir listede zaten bitişik düğümleriniz varken, bitişik düğümleri her bulmak istediğinizde tüm satırı kontrol etmeniz gerekir. Neden an adjacency list is as good as a matrixbu durumlarda düşünüyorsunuz ?
realUser404

@ realUser404 Tam olarak, bir matris satırının tamamını taramak bir O (n) işlemidir. Bitişiklik listeleri, tüm giden kenarları geçmeniz gerektiğinde seyrek grafikler için daha iyidir, bunu O (d) (d: düğümün derecesi) içinde yapabilirler. Matrisler, sıralı erişim nedeniyle bitişik listelerden daha iyi önbellek performansına sahiptir, bu nedenle biraz yoğun grafikler için bir matrisin taranması daha mantıklı olabilir.
Jochem Kuijpers

3

Keyser5053'ün bellek kullanımı hakkındaki yanıtına eklemek için.

Yönlendirilmiş herhangi bir grafik için, bir bitişik matrisi (kenar başına 1 bit) n^2 * (1)bellek bitleri tüketir .

Tam bir grafik için , bitişik bir liste (64 bitlik işaretçilerle) n * (n * 64), liste ek yükü hariç bellek bitlerini tüketir .

Eksik bir grafik için, bir bitişik listesi 0, liste ek yükünü hariç tutarak bellek bitlerini tüketir .


Bir bitişiklik listesi için, ebir bitişik matris bellek için en uygun hale gelmeden önce maksimum kenar sayısını ( ) belirlemek için aşağıdaki formülü kullanabilirsiniz .

edges = n^2 / smaksimum kenar sayısını belirlemek için s, platformun işaretçi boyutu nerede .

Grafik dinamik olarak güncelleniyorsa, bu verimliliği ortalama kenar sayısı (düğüm başına) ile koruyabilirsiniz n / s.


64 bit işaretçiler ve dinamik grafik içeren bazı örnekler (Dinamik grafik, bir değişiklik yapıldıktan sonra her seferinde sıfırdan yeniden hesaplamak yerine, bir sorunun çözümünü, değişikliklerden sonra verimli bir şekilde günceller.)

n300'ün olduğu yönlendirilmiş bir grafik için, bitişiklik listesi kullanılarak düğüm başına optimum kenar sayısı şöyledir:

= 300 / 64
= 4

Bunu keyser5053 formülüne eklersek d = e / n^2( etoplam kenar sayısı nerede ), kırılma noktasının ( 1 / s) altında olduğumuzu görebiliriz :

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

Bununla birlikte, bir işaretçi için 64 bit aşırı olabilir. Bunun yerine işaretçi ofsetleri olarak 16 bitlik tamsayılar kullanırsanız, kırılma noktasından önce 18 kenara kadar sığabiliriz.

= 300 / 16
= 18

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

Bu örneklerin her biri, bitişik listelerin ek yükünü ( 64*2bir vektör ve 64 bitlik işaretçiler için) göz ardı eder .


Kısmı anlamıyorum d = (4 * 300) / (300 * 300), olmamalı d = 4 / (300 * 300)mı? Formül olduğu için d = e / n^2.
Saurabh

2

Bitişiklik Matrisi uygulamasına bağlı olarak, verimli bir uygulama için grafiğin 'n'si önceden bilinmelidir. Grafik çok dinamikse ve ara sıra matrisin genişletilmesini gerektiriyorsa, bu da bir dezavantaj olarak sayılabilir mi?


1

Bitişik matris veya liste yerine bir karma tablo kullanırsanız, tüm işlemler için daha iyi veya aynı büyük-O çalışma zamanı ve alanı elde edersiniz (bir kenarın kontrol edilmesi O(1), tüm bitişik kenarların alınması O(degree)vb.).

Hem çalışma zamanı hem de alan için bazı sabit faktör yükü vardır (hash tablosu bağlantılı liste veya dizi araması kadar hızlı değildir ve çarpışmaları azaltmak için makul miktarda fazladan alan alır).


1

Diğer cevaplar başka yönleri de kapsadığı için, düzenli bitişik liste temsilinin ödünleşmesinin üstesinden gelmeye değineceğim.

Dictionary ve HashSet veri yapılarından yararlanarak, EdgeExists sorgusu ile bitişik listedeki bir grafiği amorti edilmiş sabit zamanda göstermek mümkündür . Buradaki fikir, köşeleri bir sözlükte tutmaktır ve her köşe için, kenarları olan diğer köşelere referans veren bir karma set tutarız.

Bu uygulamadaki küçük bir takas, kenarlar burada iki kez temsil edildiğinden (çünkü her köşe kendi hash setine sahip olduğundan, normal bitişiklik listesinde olduğu gibi O (V + E) yerine uzay karmaşıklığına O (V + 2E) sahip olacağıdır. kenarlar). Ancak AddVertex , AddEdge , RemoveEdge gibi işlemler , bitişik matris gibi O (V) alan RemoveVertex haricinde, bu uygulama ile amortize edilmiş O (1) zamanında yapılabilir . Bu, uygulama basitliği dışında, bitişik matrisin belirli bir avantajı olmadığı anlamına gelir. Bu bitişik liste uygulamasında neredeyse aynı performansla seyrek grafikte yer kazanabiliriz.

Ayrıntılar için Github C # deposunda aşağıdaki uygulamalara göz atın. Ağırlıklı grafik için, ağırlık değerini barındırmak için sözlük-karma set kombinasyonu yerine iç içe geçmiş bir sözlük kullandığına dikkat edin. Benzer şekilde yönlendirilmiş grafik için, içeri ve dışarı kenarlar için ayrı hash kümeleri vardır.

Gelişmiş-Algoritmalar

Not: Bu fikri test etmemiş olmama rağmen, tembel silme özelliğini kullanarak RemoveVertex işlemini amortize edilmiş olarak O (1) olarak daha da optimize edebileceğimize inanıyorum . Örneğin, silindikten sonra sadece köşeyi sözlükte silinmiş olarak işaretleyin ve ardından diğer işlemler sırasında artık kenarları tembel olarak temizleyin.


Bitişik matris için, tepe noktasını kaldırmak O (V) yerine O (V ^ 2) alır
Saurabh

Evet. Ancak dizi indekslerini izlemek için bir sözlük kullanırsanız, o zaman O (V) 'ye inecektir. Bu RemoveVertex uygulamasına bir göz atın .
justcoding121
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.