2008-12-30 21 views
5

मैं यह पता लगाने की कोशिश कर रहा हूं कि एक पेड़ नोड में एक फ़ंक्शन को कैसे कार्यान्वित किया जाए जो उसके सभी वंशज पत्तियों (चाहे प्रत्यक्ष या अप्रत्यक्ष) लौटाता हो। हालांकि, मैं एक कंटेनर नहीं पारित करना चाहता हूं जिसमें पत्ती के नोड्स को बार-बार रखा जा सकेगा (पेड़ बड़ा हो सकता है), इसके बजाय मैं पेड़ के माध्यम से फिर से उत्पन्न करने के लिए जनरेटर का उपयोग करना चाहता हूं। मैंने कुछ दृष्टिकोणों की कोशिश की है लेकिन उनमें से कोई भी अब तक काम नहीं कर रहा है। यह एक सबसे करीब मैं एक संभव समाधान करने के लिए आ गया है:जनरेटर का उपयोग कर वृक्ष संरचना के माध्यम से कैसे पुनरावृत्ति करें?

public interface ITreeNode 
    { 
     IEnumerable<ITreeNode> EnumerateLeaves();    
    } 

    class Leaf : ITreeNode 
    { 
     public IEnumerable<ITreeNode> EnumerateLeaves() 
     { 
      throw new NotImplementedException(); 
     } 
    } 

    class Branch : ITreeNode 
    { 
     private List<ITreeNode> m_treeNodes = new List<ITreeNode>(); 

     public IEnumerable<ITreeNode> EnumerateLeaves() 
     { 
      foreach(var node in m_treeNodes) 
      { 
       if(node is Leaf) 
        yield return node; 
       else 
        node.EnumerateLeaves(); 
      } 
     } 
    } 

लेकिन यह या तो काम नहीं कर रहा। मैं क्या गलत कर रहा हूं? कॉलिंग की तरह लगता है। यदि समान फ़ंक्शन में उपज स्टेटमेंट होता है तो अनन्य रूप से काम नहीं करेगा।

किसी भी मदद की बहुत सराहना की जाएगी। अग्रिम में धन्यवाद।

संपादित करें: मैं एक शाखा या तो पत्ते या बच्चों के रूप में शाखाओं, इसलिए प्रत्यावर्तन हो सकता है उल्लेख करना भूल गया।

+0

इस एक .NET सवाल नहीं है:

यहाँ एक जनरेटर पर एक प्रयास है जो दोनों क्रम और स्मृति के उपयोग में कुशल है? –

+0

हां - retagged। प्रोग्रामिंग साइट पर 'प्रोग्रामिंग' टैग अनावश्यक है। =) –

उत्तर

7

का तरीका यहां बताया Branch.EnumerateLeaves को लागू करना चाहिए है:

public IEnumerable<ITreeNode> EnumerateLeaves() 
{ 
    foreach(var node in m_treeNodes) 
    { 
     if(node is Leaf) 
      yield return node; 
     else 
     { 
      foreach (ITreeNode childNode in node.EnumerateLeaves()) 
       yield return childNode; 
     } 
    } 
} 
+0

यह तब तक काम नहीं करेगा जब शाखा में बच्चों के रूप में एक और ब्रैच हो सकता है, और इसलिए मैंने रिकर्सन का उपयोग किया। – Trap

+0

मुझे जवाब संपादित करने दें। –

+0

वहां, यह चाल चलाना चाहिए, अब यह रिकर्सन का सही ढंग से उपयोग करेगा। –

3

lassevk सही है - कि विधि के साथ एक संभावित मुद्दा यह है कि रिकर्सिवली enumerables में बुला हे उपज कर सकते हैं (एन^2) प्रदर्शन है। यदि यह एक मुद्दा है, तो आपको इसके बजाय रिकर्सन को कारक बनाना चाहिए और एक आंतरिक ढेर का उपयोग करना चाहिए।

public IEnumerable<ITreeNode> EnumerateLeaves() 
{ 
    Stack<ITreeNode> workStack = new Stack<ITreeNode>(m_treeNodes); 

    while(workStack.Count > 0) { 
     var current = workStack.Pop(); 
     if(current is Leaf) 
      yield return current; 
     else { 
      for(n = 0; n < current.m_treeNodes.Count; n++) { 
       workStack.Push(current.m_treeNodes[n]); 
      } 
     } 
    } 
} 

यह रिकर्सन के बिना एक ही कार्य करना चाहिए।

संपादित करें: Daniel Plaisted रिकर्सिवली बुला ूगणकों के साथ एक बड़ा समस्या, एक post on the MSDN Blogs regarding iterators में माथुर के बारे में एक टिप्पणी में तैनात। धन्यवाद डैनियल। =)

एक और संपादित करें: हमेशा याद रखें कि कोड सादगी प्रदर्शन की तुलना में अधिक महत्वपूर्ण हो सकता है, खासकर अगर आप अपने कोड बनाए रखने के लिए अन्य लोगों की उम्मीद है। यदि आप अपने पेड़ को बहुत बड़ा होने की उम्मीद नहीं करते हैं, तो उसके उत्तर में उल्लिखित रिकर्सन विधि lassevk का उपयोग करें।

+0

जीसी रिकर्सन के साथ सबसे बड़ी समस्या नहीं है, यह तथ्य है कि प्रत्येक पुनरावृत्ति पर इसे शीर्ष से गणक के "पेड़" को नीचे जाना है। यह रनटाइम नाटकीय रूप से बढ़ सकता है। Http://blogs.msdn.com/wesdyer/archive/2007/03/23/all-about-iterators.aspx –

+0

यह बहुत सच है - हालांकि मैं यह नहीं सोच सकता कि इसे कैसे वाक्यांशित किया जाए। लिंक के लिए धन्यवाद - बहुत उपयोगी। =) –

1

अन्य उत्तरों सही हैं, लेकिन एक पुनरावर्ती कॉल के साथ एक फ़ोरैच लूप के अंदर उपज रिटर्न होने का पैटर्न ओ जैसे पेड़ के माध्यम से फिर से चलने का समय देगा (नोड्स की संख्या * नोड की औसत गहराई) । समस्या के बारे में अधिक जानकारी के लिए this blog post देखें।

class Node 
{ 
    List<Node> _children; 

    public bool IsLeaf { get { return _children.Count == 0; } } 

    public IEnumerable<Node> Children { get { return _children; } } 

    public IEnumerable<Node> EnumerateLeaves() 
    { 
     if (IsLeaf) 
     { 
      yield return this; 
      yield break; 
     } 

     var path = new Stack<IEnumerator<Node>>(); 

     path.Push(Children.GetEnumerator()); 

     while(!path.Empty) 
     { 
      var cur = path.Pop(); 
      if (cur.MoveNext()) 
      { 
       path.Push(cur); 
       if (cur.IsLeaf) 
       { 
        yield return cur; 
       } 
       else 
       { 
        path.Push(cur.Children.GetEnumerator()); 
       } 
      } 

     } 
    } 

} 
+0

क्या आप समझा सकते हैं कि यह ओ (एन^2) से बेहतर कैसे है? मैं इसे देख नहीं रहा हूँ। –

+0

यह ओ (एन लॉग एन) है। आपके द्वारा प्रदान किए गए लिंक में, रिकर्सन गहराई एन थी। एक पेड़ में, यह एक औसत मामले में लॉग एन है। – recursive

+0

मैंने इसे यह बताने के लिए अद्यतन किया कि रिकर्सिव विधि का रन टाइम ओ है (नोड्स की संख्या * नोड की औसत गहराई)। इस में केवल ओ (नोड्स की संख्या) का रनटाइम होना चाहिए, जो एरिक के समान है। यह सैद्धांतिक रूप से कम स्मृति उपयोग है। –