Hangi kendini dengeleyen ikili ağacı önerirsiniz?


18

Haskell öğreniyorum ve bir egzersiz olarak ikili ağaçlar yapıyorum. Düzenli bir ikili ağaç yaptıktan sonra, kendini dengeleyecek şekilde uyarlamak istiyorum. Yani:

  • Hangisi en verimli?
  • Hangisini uygulamak en kolay?
  • En çok hangisi kullanılır?

Ama en önemlisi, hangisini önerirsiniz?

Bunun buraya ait olduğunu düşünüyorum çünkü tartışmaya açık.


Verimlilik ve uygulama kolaylığı açısından genel verimlilikler iyi tanımlanmıştır, ancak uygulamanız için en iyi şeyin, bulabildiğiniz kadar çok uygulamak ve sonra hangisinin en iyi çalıştığını bize bildirmek olduğunu düşünüyorum ...
Glenatron

Yanıtlar:


15

Kırmızı-Siyah ağaç veya AVL ağacı ile başlamanızı öneririm .

Kırmızı-siyah ağaç eklemek için daha hızlıdır, ancak AVL ağacının aramalar için hafif bir kenarı vardır. AVL ağacının uygulanması muhtemelen biraz daha kolaydır, ancak kendi deneyimlerime dayanarak bu kadar değil.

AVL ağacı, her ekleme veya silme işleminden sonra ağacın dengelenmesini sağlar (hiçbir alt ağacın 1 / -1'den daha büyük bir denge faktörü yoktur, Kırmızı-siyah ağaç ise ağacın her zaman makul bir şekilde dengelenmesini sağlar.


1
Şahsen, kırmızı-siyah eki AVL ekinden daha kolay buluyorum. Nedeni, B-ağaçlarına (kusurlu) benzetilmesidir. Ekler fiddly, ancak silmeler kötü (dikkate alınması gereken birçok durum). Aslında artık kendime ait bir C ++ kırmızı-siyah silme uygulaması yok - fark ettiğimde sildim (1) Hiç kullanmadım - her silmek istediğimde birden fazla öğe sildim, bu yüzden ağaçtan dönüştürdüm listeden silin, ardından bir ağaca geri dönün ve (2) yine de kırıldı.
Steve314

2
@ Steve314, kırmızı-siyah ağaçlar daha kolay, ama işe yarayan bir uygulama yapamadınız mı? O zaman AVL ağaçları nasıldır?
dan_waterworth

@dan_waterworth - Henüz çalışan bir ekleme yöntemiyle bile bir uygulama yapmadım - notlar aldım, temel prensibi anladım, ancak asla doğru motivasyon, zaman ve güven kombinasyonunu elde etmedim. Sadece çalışan sürümleri isteseydim, bu sadece kopyala-pseudocode-from-textbook-and-translate (ve C ++ 'ın standart kütüphane kaplarına sahip olduğunu unutmayın), ama buradaki eğlence nerede?
Steve314

BTW - Oldukça popüler bir ders kitabının dengeli ikili ağaç algoritmalarından birinin hatalı bir uygulamasını içerdiğine inanıyorum (ancak referansı sağlayamıyorum) - emin değilim, ancak kırmızı-siyah silme olabilir. Yani sadece ben değilim ;-)
Steve314

1
@ Steve314, biliyorum, ağaçlar zorunlu dilde son derece karmaşık olabilir, ama şaşırtıcı bir şekilde, bunları Haskell'de uygulamak bir esinti oldu. Hafta sonu düzenli bir AVL ağacı ve ayrıca 1D uzamsal varyant yazdım ve ikisi de sadece 60 satır.
dan_waterworth

10

Eğer rastgele veri yapıları ile iyi iseniz bir alternatif düşünün : Listeleri atla .

Üst düzey bir bakış açısından, bir ağaç yapısı değil, birden çok bağlantı katmanına sahip bir liste olarak uygulanması dışında bir ağaç yapısıdır.

O (log N) eklemeleri / aramaları / silmelerini alırsınız ve tüm bu zor dengeleme durumlarıyla uğraşmanıza gerek kalmaz.

Bunları bir İşlevsel Dilde uygulamayı hiç düşünmedim ve wikipedia sayfası hiç göstermiyor, bu yüzden kolay olmayabilir (değişmezliğe wrt)


Ben gerçekten atlama listeleri zevk ve fonksiyonel bir dilde olmasa da, daha önce uyguladım. Sanırım bundan sonra onları deneyeceğim, ama şu anda kendi kendini dengeleyen ağaçlardayım.
dan_waterworth

Ayrıca, insanlar genellikle eşzamanlı veri yapıları için atlama listelerini kullanır. Değişmezliği zorlamak yerine, haskell'in eşzamanlılık ilkellerini (MVar veya TVar gibi) kullanmak daha iyi olabilir. Bununla birlikte, bu bana fonksiyonel kod yazma hakkında çok fazla şey öğretmeyecek.
dan_waterworth

2
@ Fanatic23, bir Atla Listesi bir ADT değildir. ADT, bir küme veya ilişkilendirilebilir bir dizidir.
dan_waterworth

@ dan_waterworth benim hatam, haklısın.
Fanatic23

5

Başlangıç ​​olarak nispeten kolay bir yapı istiyorsanız (hem AVL ağaçları hem de kırmızı-siyah ağaçlar hareketsizdir), seçeneklerden biri "ağaç" ve "yığın" birleşimi olarak adlandırılan bir davranıştır.

Her düğüm, genellikle düğüm oluşturulurken rastgele atanan bir "öncelik" değeri alır. Düğümler, anahtar sıralamasına uyulacak ve öncelik değerlerinin yığın benzeri sıralamasına uyulacak şekilde ağaçta konumlandırılır. Öbek benzeri sıralama, bir ebeveynin her iki çocuğunun da ebeveynlerden daha düşük önceliklere sahip olduğu anlamına gelir.

EDIT yukarıdaki "anahtar değerler içinde" silindi - öncelik ve anahtar sırası birlikte geçerlidir, bu nedenle benzersiz anahtarlar için bile öncelik önemlidir.

İlginç bir kombinasyon. Anahtarlar benzersizse ve öncelikler benzersizse, herhangi bir düğüm kümesi için benzersiz bir ağaç yapısı vardır. Yine de, kesici uçlar ve silmeler etkilidir. Kesin olarak, ağaç etkili bir şekilde bağlantılı bir liste olduğu noktaya dengesiz olabilir, ancak sırayla yerleştirilen anahtarlar (standart ikili ağaçların aksine) gibi normal durumlar da dahil olmak üzere (standart ikili ağaçlarda olduğu gibi) son derece düşüktür.


1
+1. Treaps benim kişisel seçimim, nasıl uygulandıkları hakkında bir blog yazısı bile yazdım .
P

5

Hangisi en verimli?

Belirsiz ve cevaplaması zor. Hesaplama karmaşıklıklarının hepsi iyi tanımlanmıştır. Verimlilik ile kastettiğiniz buysa, gerçek bir tartışma yoktur. Gerçekten de, tüm iyi algoritmalar kanıtlar ve karmaşıklık faktörleriyle birlikte gelir.

"Çalışma süresi" veya "bellek kullanımı" anlamına geliyorsa, gerçek uygulamaları karşılaştırmanız gerekir. Ardından dil, çalışma zamanı, işletim sistemi ve diğer faktörler devreye girerek sorunun yanıtlanmasını zorlaştırır.

Hangisini uygulamak en kolay?

Belirsiz ve cevaplaması zor. Bazı algoritmalar sizin için karmaşık görünebilir, ancak benim için önemsiz görünebilir.

En çok hangisi kullanılır?

Belirsiz ve cevaplaması zor. Önce "kim tarafından?" bunun bir parçası? Sadece Haskell? C veya C ++ ne olacak? İkincisi, anket yapmak için kaynağa erişemediğimiz tescilli yazılım sorunu var.

Ama en önemlisi, hangisini önerirsiniz?

Bunun buraya ait olduğunu düşünüyorum çünkü tartışmaya açık.

Doğru. Diğer kriterleriniz çok yardımcı olmadığından, elde edeceğiniz tek şey budur.

Çok sayıda ağaç algoritması için kaynak alabilirsiniz. Bir şey öğrenmek istiyorsanız, bulabileceğiniz her şeyi uygulayabilirsiniz. Bir "öneri" istemek yerine, bulabileceğiniz her algoritmayı toplayın.

İşte liste:

http://en.wikipedia.org/wiki/Self-balancing_binary_search_tree

Tanımlanmış altı popüler olan vardır. Bunlarla başlayın.


3

Splay ağaçları ile ilgileniyorsanız, ilk olarak Allen ve Munroe tarafından bir makalede açıklandığına inandığımların daha basit bir versiyonu var. Aynı performans garantisine sahip değildir, ancak "zig-zig" ve "zig-zag" yeniden dengelenmesi ile ilgili komplikasyonları önler.

Temel olarak, arama yaparken (silmek için bir ekleme noktası veya düğüm aramaları dahil), bulduğunuz düğüm doğrudan köke doğru, aşağıdan yukarıya doğru döndürülür (örn. Özyinelemeli arama işlevi çıkarken). Her adımda, köke doğru başka bir adım atmak istediğiniz çocuğun sağ çocuk veya sol çocuk olduğuna bağlı olarak tek bir sol veya sağ rotasyon seçersiniz (rotasyon yönlerimi doğru hatırlıyorsam, sırasıyla).

Splay ağaçları gibi, son zamanlarda erişilen öğelerin her zaman ağacın köküne yakın olduğu, tekrar erişimin çok hızlı olduğu düşünülmektedir. Daha basit olmakla birlikte, bu Allen-Munroe rotasyon-kök ağaçları (onlara dediğim - resmi adı bilmiyorum) daha hızlı olabilir, ancak aynı amortismana tabi performans garantisi yoktur.

Bir şey - tanım gereği bu veri yapısı, bulma işlemleri için bile mutasyona uğradığından, muhtemelen tek tek uygulanması gerekir. IOW belki fonksiyonel programlama için iyi bir seçim değildir.


Buluntular, ağacı bulurken bile değiştirdikleri için biraz can sıkıcıdır. Bu, ilk etapta Haskell gibi fonksiyonel bir dili kullanmak için büyük motivasyonlardan biri olan çok iş parçacıklı ortamlarda oldukça acı verici olacaktır. Daha sonra, daha önce hiç işlevsel diller kullanmadım, belki de bu bir faktör değildir.
Hızlı Joe Smith

@Quick - ağacı nasıl kullanmayı planladığınıza bağlıdır. Gerçek işlevsel stil kodunda kullanıyorsanız, mutasyonu her bulgunun üzerine bırakırsınız (bir Splay ağacını biraz aptal hale getirirsiniz) veya her aramada ikili ağacın önemli bir bölümünü çoğaltırsınız, ve işiniz ilerledikçe hangi ağaç durumuyla çalıştığınızı takip edin (muhtemelen monadik bir stil kullanmanın nedeni). Yeni ağaç oluşturulduktan sonra artık eski ağaç durumuna başvurmazsanız, bu kopyalama derleyici tarafından optimize edilebilir (işlevsel programlamada benzer varsayımlar yaygındır), ancak olmayabilir.
Steve314

Her iki yaklaşım da çabaya değmez. Sonra tekrar, çoğu zaman tamamen işlevsel diller kullanmayın.
Hızlı Joe Smith

1
@Quick - Ağacı çoğaltmak, herhangi bir ağaç veri yapısı için ekler gibi algoritmaları mutasyona uğratmak için saf işlevsel bir dilde yapacağınız şeydir. Kaynak terimlerle, kod, yerinde güncellemeler yapan zorunlu koddan farklı olmayacaktır. Farklılıklar, muhtemelen dengesiz ikili ağaçlar için zaten ele alınmıştır. Düğümlere üst bağlantı eklemeye çalışmadığınız sürece, kopyalar en azından ortak alt ağaçları paylaşacak ve Haskell'deki derin optimizasyon mükemmel değilse oldukça sert. Ben prensip olarak Haskell'e karşıyım, ama bu mutlaka bir sorun değil.
Steve314

2

Çok basit dengeli bir ağaç AA ağacıdır . Değişmez daha basittir ve bu nedenle uygulanması daha kolaydır. Sadeliği nedeniyle performansı hala iyidir.

Gelişmiş bir alıştırma olarak, değişmezleri tür sistem türü tarafından zorlanan dengeli ağaçların varyantlarından birini uygulamak için GADT'leri kullanmayı deneyebilirsiniz .

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.