एक पेड़ में सभी नोड्स के सभी वंश उत्पन्न करने के लिए सबसे कुशल तरीका है


9

मैं एक पेड़ लेने के लिए सबसे कुशल एल्गोरिथ्म की तलाश कर रहा हूं (या तो किनारों की एक सूची के रूप में संग्रहीत; या माता-पिता की सूची से बच्चे की सूची की नकल के रूप में); और उत्पादन, हर नोड के लिए, सभी नोड्स की एक सूची यह (पत्ता स्तर और गैर पत्ती स्तर) के वंशज थे।

पैमाने के कारण कार्यान्वयन को लूप के बजाय छोरों के माध्यम से होना चाहिए; और आदर्श रूप से O (N) होना चाहिए।

यह एसओ प्रश्न एक पेड़ में वन नोड के जवाब खोजने के लिए एक मानक यथोचित स्पष्ट समाधान को शामिल करता है। लेकिन जाहिर है, हर पेड़ नोड पर कि एल्गोरिथ्म दोहरा अत्यधिक अक्षम है (मेरे सिर के ऊपर से, ओ (NlogN) हे (एन ^ 2 के लिए))।

पेड़ जड़ में जाना जाता है। (उदाहरण के लिए नहीं एन आम, किसी भी तरह से, आकार या रूप है, न वर्दी गहराई में संतुलित नहीं) पेड़ बिल्कुल मनमाने आकार की है - कुछ नोड्स 1-2 बच्चे हैं, कुछ 30K बच्चे हैं।

एक व्यावहारिक स्तर पर (हालांकि यह एल्गोरिथ्म को प्रभावित नहीं करना चाहिए) पेड़ में ~ 100K-200K नोड्स हैं।


आप लूप और स्टैक का उपयोग करके पुनरावृत्ति का अनुकरण कर सकते हैं, क्या यह आपके समाधान के लिए अनुमत है?
जियोर्जियो

@ जियोर्जियो - बिल्कुल। यही मैंने "पुनरावृत्ति के बजाय छोरों के माध्यम से" लागू करने की कोशिश की।
DVK

जवाबों:


5

यदि आप वास्तव में हर सूची को विभिन्न प्रतियों के रूप में प्रस्तुत करना चाहते हैं, तो आप सबसे खराब स्थिति में n ^ 2 स्थान से बेहतर हासिल करने की उम्मीद नहीं कर सकते। तुम सिर्फ प्रत्येक सूची के लिए उपयोग की जरूरत है:

मैं जड़ से शुरू होने वाले पेड़ का एक क्रम-क्रम प्रदर्शन करूँगा:

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

फिर पेड़ की दुकान में प्रत्येक नोड के लिए कम से कम में आदेश अपने सबट्री में संख्या और अधिकतम में क्रम संख्या (यह आसानी से प्रत्यावर्तन के माध्यम से बनाए रखा है - और आप एक ढेर के साथ अनुकरण कर सकते हैं कि यदि आप चाहें)।

अब आप सभी n नोड्स को लम्बाई A के सरणी n में रखते हैं जहाँ नोड इन-ऑर्डर नंबर i स्थिति में है। फिर जब आपको नोड X के लिए सूची खोजने की आवश्यकता होती है, तो आप A [X.min, X.max] में देखते हैं - ध्यान दें कि इस अंतराल में नोड X शामिल होगा, जिसे आसानी से ठीक भी किया जा सकता है।

यह सब O (n) समय में पूरा होता है और O (n) स्थान लेता है।

आशा है कि ये आपकी मदद करेगा।


2

अयोग्य भाग पेड़ को पीछे नहीं हटा रहा है, लेकिन नोड्स की सूचियों का निर्माण कर रहा है। इस तरह सूची बनाना समझदारी होगी:

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

चूंकि प्रत्येक वंशज नोड को प्रत्येक माता-पिता की सूची में कॉपी किया जाता है, हम संतुलित पेड़ों के लिए औसतन O (n log n) जटिलता के साथ समाप्त होते हैं, और O (n²) पतित पेड़ों के लिए सबसे खराब स्थिति है जो वास्तव में लिंक की गई सूची हैं।

हम O (n) या O (1) के आधार पर छोड़ सकते हैं कि क्या आपको किसी भी सेटअप को करने की आवश्यकता है अगर हम सूचियों की गणना करने की ट्रिक का उपयोग करते हैं। मान लें child_iterator(node)कि हमारे पास एक है जो हमें उस नोड के बच्चे देता है। हम तो तुच्छ descendant_iterator(node)इस तरह परिभाषित कर सकते हैं :

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

एक गैर-पुनरावर्ती समाधान बहुत अधिक शामिल है, क्योंकि पुनरावृत्त नियंत्रण प्रवाह मुश्किल (कोरटाइन्स!) है। मैं इस उत्तर को आज बाद में अपडेट करूंगा।

चूँकि किसी पेड़ का ट्रैवर्सल हे (n) होता है और किसी सूची पर चलना रेखीय होने के साथ-साथ यह ट्रिक पूरी तरह से लागत को रोक देती है, जब तक कि यह किसी भी तरह से भुगतान नहीं किया जाता है। उदाहरण के लिए, प्रत्येक नोड के लिए वंश की सूची मुद्रण ओ (n²) सबसे ज्यादा मामले जटिलता है: पुनरावृत्ति सभी नोड्स से अधिक हे (एन) है और इसलिए प्रत्येक नोड के वंशजों से अधिक पुनरावृत्ति है, चाहे वे एक सूची में संग्रहीत या गणना कर रहे हैं तदर्थ ।

बेशक, यह अगर तुम पर काम करने के लिए एक वास्तविक संग्रह की जरूरत है काम नहीं करेगा।


क्षमा करें, -1। Aglorithm का पूरा उद्देश्य डेटा को प्रीकोम्प्यूट कर रहा है। आलसी संगणना पूरी तरह से पराजय का कारण है, यहां तक ​​कि एलो को चलाने का कारण भी।
DVK

2
@ डीवीडीके ओके, मुझे आपकी आवश्यकताओं की गलतफहमी हो सकती है। आप परिणामी सूचियों के साथ क्या कर रहे हैं? यदि सूचियों को प्रीकोम्प्यूट करना एक अड़चन है (लेकिन सूचियों का उपयोग नहीं करना), तो यह इंगित करेगा कि आप उन सभी डेटा का उपयोग नहीं कर रहे हैं जिन्हें आप एकत्र करते हैं और फिर आलसी अभिकलन जीत होगा। लेकिन अगर आप सभी डेटा का उपयोग करते हैं, तो प्रीकंप्यूटिंग के लिए एल्गोरिथ्म काफी हद तक अप्रासंगिक है - डेटा का उपयोग करने की एल्गोरिथम जटिलता कम से कम सूचियों के निर्माण की जटिलता के बराबर होगी।
आमोन

0

यह लघु एल्गोरिथ्म इसे करना चाहिए, कोड पर एक नज़र रखना चाहिए public void TestTreeNodeChildrenListing()

एल्गोरिथ्म वास्तव में अनुक्रम में पेड़ के नोड्स के माध्यम से जाता है, और वर्तमान नोड के माता-पिता की सूची रखता है। आपकी आवश्यकता के अनुसार वर्तमान नोड प्रत्येक माता-पिता के नोड का एक बच्चा है, यह एक बच्चे के रूप में प्रत्येक और प्रत्येक को जोड़ा जाता है।

अंतिम परिणाम शब्दकोश में संग्रहीत किया जाता है।

    [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; }
        }
    }
}

मेरे लिए, यह O (n लॉग एन) से O (n complex) जटिलता की तरह बहुत दिखता है, और यह केवल DVK उनके प्रश्न से जुड़े उत्तर पर थोड़ा सुधार करता है। तो अगर यह कोई सुधार नहीं है, तो यह सवाल का जवाब कैसे देता है? एकमात्र उत्तर जो इस मूल्य को जोड़ता है वह भोली एल्गोरिथ्म की पुनरावृत्ति अभिव्यक्ति को प्रदर्शित करता है।
आमोन

यह O (n) है, यदि आप एल्गोरिथ्म को करीब से देखते हैं, तो यह नोड्स पर एक बार पुनरावृत्त करता है। एक ही समय में यह एक ही समय में प्रत्येक माता-पिता नोड के लिए बच्चे के नोड का संग्रह बनाता है।
कम फ्लाइंग हवासील

1
आप सभी नोड्स के माध्यम से लूप, जो हे (एन) है। फिर आप सभी चिल्ड के माध्यम से लूप करते हैं, जिसे हम अभी के लिए अनदेखा करेंगे (चलो कल्पना करें कि यह कुछ स्थिर कारक है)। तो फिर तुम वर्तमान नोड के सभी माता पिता के माध्यम से लूप। एक बैलेंस ट्री में, यह O (लॉग एन) है, लेकिन पतित मामले में जहां हमारा पेड़ एक लिंक्ड सूची है, यह O (n) हो सकता है। इसलिए यदि हम अपने माता-पिता के माध्यम से सभी नोड्स के माध्यम से लूपिंग की लागत को गुणा करते हैं, तो हमें O (n O n) समय जटिलता के लिए O (n log n) मिलता है। मल्टीथ्रेडिंग के बिना, "एक ही समय में" नहीं है।
आमोन

"एक ही समय में" का अर्थ है कि यह एक ही लूप में संग्रह बनाता है और इसमें कोई अन्य लूप शामिल नहीं है।
लो फ्लाइंग पेलिकन

0

आम तौर पर, आप बस एक पुनरावर्ती दृष्टिकोण का उपयोग करेंगे, क्योंकि यह आपको अपने निष्पादन आदेश को ऐसे चारों ओर स्विच करने की अनुमति देता है कि आप पत्तियों से शुरू होने वाली पत्तियों की संख्या की गणना ऊपर की ओर कर सकते हैं। , जब से तुम वर्तमान नोड अद्यतन करने के लिए आपके पुनरावर्ती कॉल का परिणाम का उपयोग करना होगा, यह एक पूंछ पुनरावर्ती संस्करण प्राप्त करने के विशेष प्रयास ले जाएगा। यदि आप उस प्रयास को नहीं करते हैं, तो निश्चित रूप से, यह दृष्टिकोण बस एक बड़े पेड़ के लिए आपके ढेर को विस्फोट कर देगा।

यह देखते हुए कि हमने महसूस किया कि मुख्य विचार पत्तियों पर शुरू होने वाला एक लूपिंग ऑर्डर प्राप्त करना है और जड़ तक वापस जाना है, जो प्राकृतिक विचार मन में आता है वह है पेड़ पर एक टोपोलॉजिकल प्रकार का प्रदर्शन करना । नोड्स का परिणामी क्रम पत्तियों की संख्या को योग करने के लिए रेखीय रूप से निकाला जा सकता है (यह मानते हुए कि आप नोड को सत्यापित कर सकते हैं कि पत्ती है O(1))। टोपोलॉजिकल सॉर्ट की समग्र समय जटिलता है O(|V|+|E|)

मुझे लगता है कि आपके Nनोड्स की संख्या है, जो |V|आम तौर पर (डीएजी नामकरण से) होगी। Eदूसरी ओर का आकार आपके पेड़ की ऊँचाई पर निर्भर करता है। उदाहरण के लिए, एक द्विआधारी पेड़ प्रति नोड ज्यादा से ज्यादा 2 किनारों, इसलिए है O(|E|) = O(2*|V|) = O(|V|)कि इस मामले में है, जो एक समग्र में परिणाम होगा O(|V|)एल्गोरिथ्म। ध्यान दें कि एक पेड़ की समग्र संरचना के कारण, आपके पास ऐसा कुछ नहीं हो सकता है O(|E|) = O(|V|^2)। वास्तव में, चूंकि प्रत्येक नोड में एक अद्वितीय अभिभावक होता है, इसलिए जब आप केवल माता-पिता-संबंधों पर विचार करते हैं, तो प्रति नोड की गणना करने के लिए आपके पास सबसे अधिक एक किनारे हो सकता है, इसलिए पेड़ों के लिए हमारी गारंटी है O(|E|) = O(|V|)। इसलिए, उपरोक्त एल्गोरिथ्म पेड़ के आकार में रैखिक हमेशा होता है।

हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.