Bir ağaçtaki tüm düğümlerin tüm kökenlerini oluşturmanın en etkili yolu


9

Bir ağaç almak için en verimli algoritma arıyorum (kenarların listesi olarak saklanır; VEYA üst düğümden alt düğümler listesine eşlemeler listesi olarak); HER düğüm için, ondan gelen tüm düğümlerin bir listesini (yaprak seviyesi ve yaprak olmayan seviye) üretir.

Uygulama ölçeğe bağlı olarak, tekrarlama yerine döngüler aracılığıyla yapılmalıdır; ve ideal olarak O (N) olmalıdır.

Bu SO sorusu , bir ağaçtaki ONE düğümü için cevabı bulmak için makul derecede açık bir standart çözümü kapsar. Fakat açıkçası, bu algoritmayı her ağaç düğümünde tekrarlamak oldukça verimsizdir (başımın üstünden O (NlogN) ila O (N ^ 2)).

Ağaç kökü bilinir. Ağaç kesinlikle keyfi bir şekle sahiptir (örneğin N-nary değil, herhangi bir şekilde, şekil veya formda dengelenmemiş, tekdüze derinlik değil) - bazı düğümlerin 1-2 çocuğu vardır, bazılarının 30K çocuğu vardır.

Pratik bir seviyede (algoritmayı etkilememesine rağmen) ağacın ~ 100K-200K düğümleri vardır.


Bir döngü ve bir yığın kullanarak özyinelemeyi simüle edebilirsiniz, çözümünüz için buna izin veriliyor mu?
Giorgio

@Giorgio - tabii ki. Ben "tekrarlama yerine döngüler aracılığıyla" ima etmeye çalıştım budur.
DVK

Yanıtlar:


5

Aslında her listeyi farklı kopyalar olarak üretmek istiyorsanız, en kötü durumda n ^ 2 alanından daha iyi bir alan elde etmeyi umamazsınız. Her listeye ERİŞİM için ihtiyacınız varsa:

Kökten başlayarak ağacın sıralı bir geçişini gerçekleştiririm:

http://en.wikipedia.org/wiki/Tree_traversal

Daha sonra ağaçtaki her bir düğüm için alt sıradaki minimum sipariş numarasını ve maksimum sipariş numarasını saklayın (bu, özyineleme yoluyla kolayca korunur - ve isterseniz bunu bir yığınla simüle edebilirsiniz).

Şimdi tüm düğümleri, sıra numarası i olan düğümün i konumunda olduğu n uzunluğunda bir A dizisine koyarsınız. Daha sonra X düğümü için listeyi bulmanız gerektiğinde, A [X.min, X.max] 'a bakarsınız - bu aralığın kolayca düzeltilebilen X düğümünü içereceğini unutmayın.

Bütün bunlar O (n) zamanında gerçekleştirilir ve O (n) alanı kaplar.

Umarım bu yardımcı olur.


2

Verimsiz kısım ağacın üzerinden geçmiyor, düğümlerin listesini yapıyor. Listeyi şu şekilde oluşturmak mantıklı görünecektir:

descendants[node] = []
for child in node.childs:
    descendants[node].push(child)
    for d in descendants[child]:
        descendants[node].push(d)

Her bir alt düğüm her ebeveynin listesine kopyalandığından, dengeli ağaçlar için ortalama O (n log n) karmaşıklığı ve gerçekten bağlantılı listeler olan dejenere ağaçlar için en kötü durum O (n²) ile sonuçlanır.

Listeleri tembel olarak hesaplama hilesi kullanırsak, herhangi bir kurulum yapmanız gerekip gerekmediğine bağlı olarak O (n) veya O (1) 'e düşebiliriz. Varsayalım child_iterator(node)ki bize o düğümün çocuklarını veren bir tane var. O zaman önemsiz bir şekilde şöyle tanımlayabiliriz descendant_iterator(node):

def descendant_iterator(node):
  for child in child_iterator(node):
    yield from descendant_iterator(child)
  yield node

Yineleyici olmayan bir çözüm çok daha fazla işin içerisindedir, çünkü yineleyici kontrol akışı zor (coroutines!). Bu cevabı bugün daha sonra güncelleyeceğim.

Bir ağacın geçişi O (n) olduğundan ve bir liste üzerindeki yineleme de doğrusal olduğundan, bu hile yine de ödenene kadar maliyeti tamamen ortadan kaldırır. Örneğin, her düğüm için torun listesini yazdırmak O (n²) en kötü durum karmaşıklığına sahiptir: Tüm düğümler üzerinde yineleme O (n) 'dir ve böylece bir listede depolanmış veya hesaplanmış geçici olarak her düğümün torunları üzerinde yinelenir .

Tabii ki, üzerinde çalışmak için gerçek bir koleksiyona ihtiyacınız varsa bu işe yaramaz.


Üzgünüm -1. Agloritmanın tüm amacı verileri önceden hesaplamaktır. Tembel hesaplama, algoyu çalıştırmanın nedenini tamamen yeniyor.
DVK

2
@DVK Tamam, ihtiyaçlarınızı yanlış anlamış olabilirim. Ortaya çıkan listelerle ne yapıyorsunuz? Listelerin önceden hesaplanması bir darboğazsa (ancak listeleri kullanmıyorsa), bu, topladığınız tüm verileri kullanmadığınızı gösterir ve tembel hesaplama bir kazanç olacaktır. Ancak tüm verileri kullanırsanız, ön hesaplama algoritması büyük ölçüde ilgisizdir - verileri kullanmanın algoritmik karmaşıklığı, en azından listelerin oluşturulmasının karmaşıklığına eşit olacaktır.
amon

0

Bu kısa algoritma yapmalı, koda bir göz atın public void TestTreeNodeChildrenListing()

Algoritma aslında ağaç düğümlerinden sırayla geçer ve geçerli düğümün ebeveynlerinin listesini tutar. Gereksiniminize göre, mevcut düğüm her bir ana düğümün bir çocuğudur ve her birine bir çocuk olarak eklenir.

Nihai sonuç sözlükte saklanır.

    [TestFixture]
    public class TreeNodeChildrenListing
    {
        private TreeNode _root;

        [SetUp]
        public void SetUp()
        {
            _root = new TreeNode("root");
            int rootCount = 0;
            for (int i = 0; i < 2; i++)
            {
                int iCount = 0;
                var iNode = new TreeNode("i:" + i);
                _root.Children.Add(iNode);
                rootCount++;
                for (int j = 0; j < 2; j++)
                {
                    int jCount = 0;
                    var jNode = new TreeNode(iNode.Value + "_j:" + j);
                    iCount++;
                    rootCount++;
                    iNode.Children.Add(jNode);
                    for (int k = 0; k < 2; k++)
                    {
                        var kNode = new TreeNode(jNode.Value + "_k:" + k);
                        jNode.Children.Add(kNode);
                        iCount++;
                        rootCount++;
                        jCount++;

                    }
                    jNode.Value += " ChildCount:" + jCount;
                }
                iNode.Value += " ChildCount:" + iCount;
            }
            _root.Value += " ChildCount:" + rootCount;
        }

        [Test]
        public void TestTreeNodeChildrenListing()
        {
            var iteration = new Stack<TreeNode>();
            var parents = new List<TreeNode>();
            var dic = new Dictionary<TreeNode, IList<TreeNode>>();

            TreeNode node = _root;
            while (node != null)
            {
                if (node.Children.Count > 0)
                {
                    if (!dic.ContainsKey(node))
                        dic.Add(node,new List<TreeNode>());

                    parents.Add(node);
                    foreach (var child in node.Children)
                    {
                        foreach (var parent in parents)
                        {
                            dic[parent].Add(child);
                        }
                        iteration.Push(child);
                    }
                }

                if (iteration.Count > 0)
                    node = iteration.Pop();
                else
                    node = null;

                bool removeParents = true;
                while (removeParents)
                {
                    var lastParent = parents[parents.Count - 1];
                    if (!lastParent.Children.Contains(node)
                        && node != _root && lastParent != _root)
                    {
                        parents.Remove(lastParent);
                    }
                    else
                    {
                        removeParents = false;
                    }
                }
            }
        }
    }

    internal class TreeNode
    {
        private IList<TreeNode> _children;
        public string Value { get; set; }

        public TreeNode(string value)
        {
            _children = new List<TreeNode>();
            Value = value;
        }

        public IList<TreeNode> Children
        {
            get { return _children; }
        }
    }
}

Bana göre bu, O (n log n) ila O (n²) karmaşıklığına çok benziyor ve DVK'nin sorularına bağladığı cevap üzerinde sadece marjinal bir şekilde gelişiyor. Peki bu bir gelişme değilse, bu soruya nasıl cevap verir? Bu cevabın eklediği tek değer, saf algoritmanın yinelemeli bir ifadesini göstermektir.
amon

O (n), Algoritmaya yakından bakarsanız, düğümler üzerinde bir kez yinelenir. Aynı zamanda, her bir üst düğüm için aynı anda alt düğümler toplamasını oluşturur.
Düşük Uçan Pelikan

1
O (n) olan tüm düğümler arasında geçiş yaparsınız. O zaman şimdilik görmezden geleceğimiz tüm çocukları dolaşıyorsunuz (bunun sabit bir faktör olduğunu düşünelim). Sonra geçerli düğümün tüm ebeveynleri arasında geçiş yaparsınız. Bir dengeleme ağacında bu O (log n), ancak ağacımızın bağlantılı bir liste olduğu dejenere durumda, O (n) olabilir. Dolayısıyla, tüm düğümlerde döngü maliyetini ebeveynleri ile döngü maliyetiyle çarparsak, O (n log n) değerini O (n²) zaman karmaşıklığına getiririz. Çoklu kullanım olmadan “aynı anda” yoktur.
amon

"aynı zamanda", koleksiyonu aynı döngüde oluşturduğu ve başka döngülerin olmadığı anlamına gelir.
Alçak Uçan Pelikan

0

Normalde, yinelemeli bir yaklaşım kullanırsınız, çünkü yürütme sırasınızı yapraklardan yukarı doğru başlayan yaprak sayısını hesaplayabileceğiniz şekilde değiştirmenize izin verir. Geçerli düğümü güncellemek için özyinelemeli çağrınızın sonucunu kullanmanız gerektiğinden, kuyruk özyinelemeli bir sürüm almak için özel çaba harcanacaktır. Eğer bu çabayı göstermezseniz, elbette, bu yaklaşım yığınızı büyük bir ağaç için patlatacaktır.

Ana fikrin, yapraklardan başlayıp köke doğru geri dönerek döngüsel bir düzen elde etmek olduğunu fark ettiğimizde, akla gelen doğal fikir , ağaç üzerinde topolojik bir sıralama yapmaktır . Ortaya çıkan düğüm dizisi, yaprak sayısını toplamak için doğrusal olarak geçilebilir (bir düğümün bir yaprak olduğunu doğrulayabildiğinizi varsayarak O(1)). Topolojik türün genel zaman karmaşıklığıdır O(|V|+|E|).

Sanırım (DAG terminolojisinden) tipik Nolan düğümlerin sayısı olduğunu varsayıyorum |V|. EÖte yandan büyüklüğü büyük ölçüde ağacınızın saflığına bağlıdır. Örneğin, bir ikili ağaç düğüm başına en fazla 2 kenara sahiptir, bu nedenle O(|E|) = O(2*|V|) = O(|V|)bu durumda genel bir O(|V|)algoritmaya yol açar. Bir ağacın genel yapısı nedeniyle, böyle bir şeye sahip olamayacağınızı unutmayın O(|E|) = O(|V|^2). Aslında, her düğümün benzersiz bir ebeveyni olduğundan, yalnızca ebeveyn ilişkilerini düşündüğünüzde düğüm başına sayılacak en fazla bir kenarınız olabilir, bu yüzden ağaçlar için bunu garanti ederiz O(|E|) = O(|V|). Bu nedenle, yukarıdaki algoritma her zaman ağacın boyutunda lineerdir.

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.