Ağaçlar “ilk çocuk, nextsibling” yapısına göre düzenlenmiş mi? Değilse, neden olmasın?


12

Genellikle ağaç veri yapıları, her bir düğümün tüm çocuklarına işaretçiler içerecek şekilde düzenlenir.

       +-----------------------------------------+
       |        root                             | 
       | child1            child2         child3 |
       +--+------------------+----------------+--+
          |                  |                |
+---------------+    +---------------+    +---------------+
|    node1      |    |     node2     |    |     node3     |
| child1 child2 |    | child1 child2 |    | child1 child2 |
+--+---------+--+    +--+---------+--+    +--+---------+--+
   |         |          |         |          |         |

Bu doğal görünüyor, ama bazı problemlerle geliyor. Örneğin, alt düğümlerin sayısı değiştiğinde, alt öğeleri yönetmek için bir dizi veya liste gibi bir şeye ihtiyacınız vardır.

Bunun yerine yalnızca (ilk) çocuk ve (sonraki) kardeş işaretçileri kullanarak, şöyle görünen bir şey elde ederiz:

       +-------------------+
       |        root       |
       | child    sibling  +--->NULL
       +--+----------------+
          |             
+----------------+    +----------------+    +----------------+
|    node1       |    |     node2      |    |     node3      |
| child  sibling +--->| child  sibling +--->| child  sibling +--->NULL
+--+-------------+    +--+-------------+    +--+-------------+
   |                     |                     |

Açıkçası, bu tür yapı ağaçları da temsil edebilir, ancak bazı avantajlar da sunar. En önemlisi, artık çocuk düğüm sayısı hakkında endişelenmemize gerek yok. Ayrıştırma ağacı için kullanıldığında, derin bir ağaç olmadan "a + b + c + d + e" gibi bir terim için doğal bir temsil sunar.

Koleksiyon kütüphaneleri böyle ağaç yapıları sunuyor mu? Ayrıştırıcılar böyle bir yapı kullanıyor mu? Değilse, nedenleri nelerdir?


2
Bu yapı açık bir şekilde daha yüksek bir karmaşıklığa mal oluyor. Sadece değişken sayıda çocuğa ihtiyacınız varsa buna değer . Birçok ağacın tasarımlarına özgü sabit sayıda çocuğu (veya en azından sabit bir azami) vardır. Bu durumlarda ek indirimler herhangi bir değer katmaz.
Joachim Sauer

4
Bağlantılı bir listeye öğe koymak O(n)algoritmaya bir faktör katmaktadır.

Ve kökünden Node3 almak için atmanız gereken ediyorum cddar ... kökünün
Tacroy

Tacroy: Doğru, köke geri dönmek tam olarak kolay değil, ama gerçekten buna ihtiyacım olursa, bir geri işaretçi
onaylanır

Yanıtlar:


7

Ağaçlar, listeler gibi, farklı şekillerde uygulanabilen "soyut veri türleridir". Her yolun avantajları ve dezavantajları vardır.

İlk örnekte, bu yapının ana avantajı O (1) 'deki herhangi bir çocuğa erişebilmenizdir. Dezavantajı, dizinin genişletilmesi gerektiğinde bir çocuğa ekleme yapmak bazen biraz daha pahalı olabilir. Bu maliyet nispeten düşüktür. Aynı zamanda en basit uygulamalardan biridir.

İkinci örnekte, ana avantaj her zaman O (1) 'de bir çocuk eklemenizdir. Ana dezavantaj, bir çocuğa rastgele erişimin O (n) maliyetidir. Ayrıca, bu olabilir için daha az ilgi çekici büyük iki nedenden dolayı ağaçların: bu bir amacı başlık ve düğüm başına iki işaretçiler bellek yükü vardır ve düğümler rastgele bellek üzerine yayılır olabilir işlemci önbellek arasında takas bir çok neden Bu uygulama onlar için daha az çekici hale getirerek ağaç gezdirilir. Bu normal ağaçlar ve uygulamalar için bir sorun değildir.

Bahsetilmeyen son bir ilginç olasılık, tüm ağacı tek bir dizide saklamaktır. Bu daha karmaşık bir koda yol açar, ancak özellikle büyük sabit ağaçlar için belirli durumlarda çok avantajlı bir uygulamadır, çünkü nesne başlığının maliyetini yedekleyebilir ve bitişik belleği tahsis edebilirsiniz.


1
Örneğin: bir B + ağacı asla bu "ilk çocuk, nextsibling" yapısını kullanmaz. Disk tabanlı bir ağaç için saçmalık noktasına etkisiz ve yine de bellek tabanlı bir ağaç için çok verimsiz olacaktır. Bellek içi bir R-ağacı bu yapıyı tolere edebilir, ancak yine de çok daha fazla önbellek özlemi anlamına gelir. "İlk çocuk, nextsibling" in daha üstün olacağı bir durum olduğunu düşünmek beni çok zorladı. Evet, ammoQ'nun belirttiği gibi bir sözdizimi ağacı için işe yarayabilir. Başka herhangi bir şey?
Qwertie

3
"Her zaman O (1) 'de bir çocuk eklersiniz" - Bence her zaman O (1)' deki 0 dizinine bir çocuk ekleyebilirsiniz, fakat bir çocuğun eklenmesi açıkça O (n) gibi görünmektedir.
Scott Whitlock

Tüm ağacın tek bir dizide depolanması yığınlar için yaygındır.
Brian

1
@Scott: iyi, bağlantılı listenin de son öğeye de bir işaretçi / referans içerdiğini varsaydım, bu da ilk veya son konum için O (1) yapar ...
OPs

Bahse girerim (belki de aşırı derecede dejenere olan durumlar hariç ) “ilk çocuk, nextsibling” uygulamasının dizi tabanlı alt tablo uygulamalarından asla daha verimli olmadığını. Önbellek konumu kazanır, büyük zaman. B ağaçlarının modern mimariler üzerinde açık arayla en verimli uygulamalar olduğu ve geleneksel olarak kullanılan kırmızı-siyah ağaçlara karşı önbellek konumunun iyileştirilmesinden dolayı kazanıldığı kanıtlanmıştır.
Konrad Rudolph

2

Düzenlenebilir bir modeli veya dokümanı olan hemen hemen her projenin hiyerarşik bir yapısı olacaktır. 'Hiyerarşik düğümü' farklı varlıklar için bir temel sınıf olarak uygulamak kullanışlı olabilir. Çoğunlukla bağlantılı liste (çocuk kardeş, 2. model) birçok sınıf kütüphanesinin büyümesinin doğal yoludur, ancak çocuklar farklı tiplerde olabilir ve muhtemelen bir " nesne modeli " genel olarak ağaçlar hakkında konuşurken düşündüğümüz şey değildir.

İlk modelinizin bir ağacının (düğümünün) en sevdiğim uygulaması, bir astar (C # 'da):

public class node : List<node> { /* props go here */ }

Kendi türünüzün genel Listesinden miras alın (veya kendi türünüzün başka herhangi bir genel koleksiyonundan miras alın). Yürüyüş bir yönde mümkündür: kökü aşağı doğru oluşturun (öğeler ebeveynlerini bilmez).

Yalnızca ebeveyn ağacı

Bahsetmediğiniz başka bir model, her çocuğun ebeveynine referans verdiği modeldir:

               null
                 |
       +---------+---------------------------------+
       |       parent                              |
       | root                                      |
       +-------------------------------------------+
          |                   |                |
+---------+------+    +-------+--------+    +--+-------------+
|     parent     |    |     parent     |    |     parent     |
|     node 1     |    |     node 2     |    |     node 3     |
+----------------+    +----------------+    +----------------+

Bu ağacın sadece tersi yönde yürümesi mümkündür, normalde tüm bu düğümler bir koleksiyonda (dizi, karma, sözlük vb.) Saklanır ve koleksiyonda, genellikle birincil öneme sahip olmayacak bir ağaçtır.

Bu yalnızca ebeveyn ağacı genellikle veritabanı uygulamalarında görülür. Bir düğümün alt öğelerini "SELECT * WHERE ParentId = x" ifadeleriyle bulmak oldukça kolaydır. Ancak bunların nadiren ağaç düğüm sınıfı nesnelere dönüştüğünü buluruz. Durum bilgisi olan (masaüstü) uygulamalarda bunlar mevcut ağaç düğümü denetimlerine sarılabilir. Vatansız (web) uygulamalarda bile bu mümkün olmayabilir. Gördüm ki ORM-haritalama sınıf-jeneratör araçları kendileri ile bir ilişkisi olan (kıkırdama) tablolar için sınıflar oluştururken yığın taşma hataları atmak gördüm, bu yüzden belki de bu ağacın sonuçta yaygın değildir.

çift ​​yönlü gezilebilir ağaçlar

Bununla birlikte, çoğu pratik durumda, her iki dünyanın en iyisine sahip olmak uygundur. Çocuk listesi olan ve ek olarak ebeveynlerini bilir: çift yönlü gezilebilir ağaçlar.

                          null
                            |
       +--------------------+--------------------+
       |                  parent                 |
       |        root                             | 
       | child1            child2         child3 |
       +--+------------------+----------------+--+
          |                  |                |
+---------+-----+    +-------+-------+    +---+-----------+
|      parent   |    |     parent    |    |  parent       |
|    node1      |    |     node2     |    |     node3     |
| child1 child2 |    | child1 child2 |    | child1 child2 |
+--+---------+--+    +--+---------+--+    +--+---------+--+
   |         |          |         |          |         |

Bu, dikkate alınması gereken daha birçok yönü beraberinde getirir:

  • Ebeveynlerin bağlantılarını ve bağlantılarını kaldırma nerede uygulanır?
    • iş mantığının ilgilenmesine izin verin ve yönü düğümün dışında bırakın (unutacaklar!)
    • düğümleri çocuk oluşturma yöntemlerine sahiptir (yeniden sipariş vermeye izin vermez) (System.Xml.XmlDocument DOM uygulamasında Microsofts seçimi, ilk karşılaştığımda beni neredeyse çıldırdı)
    • Düğümler yapıcılarında bir üst öğe alır (yeniden sıralamaya izin vermez)
    • tüm add (), insert () ve remove () yöntemlerini ve düğümlerin aşırı yüklenmelerini (genellikle benim seçimim)
  • sürekliliği
    • Devam ederken ağaçta nasıl yürüyebilirim (örneğin ebeveyn bağlantılarını dışarıda bırakın)
    • Diziselleştirmeyi kaldırdıktan sonra iki yönlü bağlantı nasıl yeniden oluşturulur (tüm ebeveynleri yeniden serileştirme sonrası eylem olarak ayarlama)
  • Bildirimler
    • Statik mekanizmalar (IsDirty bayrağı), özyinelemelerde özyinelemeli olarak işliyor mu?
    • Olaylar, ebeveynlerden, çocuklardan aşağıya veya her iki yoldan da patlar (örneğin, Windows mesaj pompasını düşünün).

Şimdi soruyu cevaplamak için , çift yönlü gezilebilir ağaçlar (şimdiye kadar kariyerimde ve tarlamda) en yaygın kullanılan olma eğilimindedir. .Net çerçevesindeki System.Windows.Forms.Control veya System.Web.UI.Control öğelerinin Microsofts uygulamasıdır, ancak aynı zamanda her DOM (Belge Nesne Modeli) uygulamasında üst öğelerini ve bir numaralandırmayı bilen düğümler bulunur Çocuklarının Nedeni: uygulama kolaylığı üzerinde kullanım kolaylığı. Ayrıca, bunlar genellikle daha spesifik sınıflar için temel sınıflardır (XmlNode, Tag, Attribute ve Text sınıflarının tabanı olabilir) ve bu temel sınıflar, jenerik serileştirme ve olay işleme mimarileri koymak için doğal yerlerdir.

Tree, birçok mimarinin kalbindedir ve serbestçe dolaşabilmek, çözümleri daha hızlı uygulayabileceğiniz anlamına gelir.


1

İkinci durumunuzu doğrudan destekleyen herhangi bir kapsayıcı kitaplığı bilmiyorum, ancak çoğu kap kitaplığı bu senaryoyu kolayca destekleyebilir. Örneğin, C ++ ile şunlara sahip olabilirsiniz:

class Node;  // forward reference to satisfy the compiler
typedef std::list<Node*> NodeList;
class Node : public NodeList { /* . . . */ };  // a node is also a list

Node* n = new Node;
n->push_back(new Node);
Node* tree = new Node;
tree->push_back(new Node);
tree->push_back(n);

Ayrıştırıcılar muhtemelen buna benzer bir yapı kullanır, çünkü değişken sayıda öğe ve çocuk içeren düğümleri etkili bir şekilde destekler. Kesinlikle bilmiyorum çünkü genellikle kaynak kodlarını okumuyorum.


1

Çocuk dizisine sahip olmanın tercih edildiği vakalardan biri, çocuklara rastgele erişime ihtiyaç duymanızdır. Ve bu genellikle çocuklar sıralandığında. Örneğin, dosya benzeri hiyerarşi ağacı daha hızlı yol araması için bunu kullanabilir. Veya dizin erişimi çok doğal olduğunda DOM etiketi ağacı

Başka bir örnek, tüm göstergelere "işaretçiler" eklenmesi daha rahat kullanıma izin verir. Örneğin, tanımladığınız her iki tür de ilişkisel veritabanı ile ağaç ilişkileri uygulanırken kullanılabilir. Ancak birincisi (bu durumda ebeveynden çocuklara ana ayrıntı), yararlı veriler için genel SQL ile sorgulamaya izin verirken, ikincisi sizi önemli ölçüde sınırlar.

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.