2015-12-31 34 views
0

我正在写一个方法,它接受一个检查其值的变量,并递归地调用它自己以获取其中的所有属性。还有集合属性和集合中的集合。递归方法的良好设计模式?

问题是,当变量是一个非常复杂的对象,递归失败我的应用程序堆栈溢出错误,我明白。

我的问题是当我面对这样的情况时,避免错误的最佳设计是什么。

btw我正在做这个审计。

+1

请向我们展示您的代码并解释它。 –

+0

管理递归深度(包括不使用递归)是如何避免C#中的这个问题;该堆栈被限制为一些“相对较小的有限大小”。但是,它也可能只是在实现中没有正确终止的问题。无论如何,“蹦床”可以用于基于堆栈的语言(如C#)中的尾调用递归算法。 – user2864740

+0

另外,请参阅http://stackoverflow.com/questions/4513438/c-sharp-recursion-depth-how-deep-can-you-go,http://stackoverflow.com/a/11785640/2864740 – user2864740

回答

5

大多数递归方法可以写为Depth First Search像这样:

DFS递归方式:

public Result recursiveMethod(Node current, Result currentResult) 
{ 
    // --- aggregate result 
    currentResult = Aggregate(currentResult, currrent.value); 

    // --- returns children nodes from the current one 
    var children = GetChildren(current); 

    foreach(var child in children) 
    { 
     // --- recursive call and store new result 
     currentResult = recursiveMethod(child, currentResult); 
    } 

    return currentResult; 
} 

DFS非递归(迭代)的方式:

public Result loopMethod(Node init) 
{ 

    var result = default(Result); 
    var inputs = new Stack<Node>(); 
    inputs.Push(init); 

    while(inputs.Any()) 
    { 
     var current = inputs.Pop(); 

     // --- aggregate result 
     result = Aggregate(result, current.value); 

     // --- returns children nodes from the current one 
     var children = GetChildren(current); 

     // --- push new nodes onto stack (you can think of this as the "recursive call") 
     foreach(var child in children) 
     { 
      inputs.Push(child); 
     }    
    } 

    return result; 
} 

这是也有帮助的是,您可以使用迭代方式实现Breadth First Search,通过将“Stack”替换为“Queue”(并使用“Enqueue”/“Dequeue”来代替“推”/“流行”)。使用递归实现BFS将非常困难。

编辑:

@阿列克谢的回答让一些伟大的意见在你为什么会遇到一个堆栈溢出异常的一些潜在原因。总之,请确保以下各项为真:

  1. 你的递归(或环)必须进步。换句话说,它应该明显达到目的。

  2. 导致永无止境的递归的最常见的错误之一是让一个已经访问过的节点滑入,最终导致永久循环。使用Set来存储访问节点是确保不会发生这种情况的好方法,但请记住,存在一定的内存开销。

底线:

做如此大规模的迭代是要花费你 - 无论是在栈资源和/或在内存中。问问自己这是否是完成这项任务的正确的地点和策略。此外,如果您对数据结构有一些了解(无论是非常深入还是非常广泛),您可以通过明智地选择DFS或BFS来优化内存优化。

+2

“循环方式”称为_iterative_。 :-) – The111

+1

谢谢@ The111!这听起来更好:) – souldzin

+0

嗨,我喜欢你的实现寿命里面有一个堆栈。那么这对线程堆栈有何不同?它没有任何限制吗?谢谢! – boyang

1

尽管有时递归比循环更清晰,但我认为如果它溢出了你的栈,那么你的使用一个循环。

或者,您可以检查自己调用了多少次。就像这样:

private int recursionCount = 0; 
public void method() { 
    recursionCount++ 
    if (recursionCount > 10000) { 
     //do something to handle this situation 
     //maybe return 
    } 
    method(); 
} 

我的意思是,如果你要递归了这么多次,它可能是一个错误或设计错误。重新思考问题并重新设计。

+0

在这种情况下你如何使用循环?给定一个变量,找出它的所有属性。 – boyang

+0

如果你不发布你的代码,我们将如何帮助你? – Sweeper

+0

那么如果你了解递归并且已经在递归中进行了编程(并且我认为你做了),那么你就会知道这不是特定代码行的问题。 – boyang

2

如果您的递归函数与国有企业的失败,有三种可能的原因:

  1. 你的对象图是太大了。这是可能的,但不太可能。为了解决这个问题,你应该重写你的函数来使用Stack<T>和一个循环。这样您将使用堆而不是调用堆栈来将路径存储回原始对象。
  2. 你的函数有语义错误。确保首先写入非递归案例,并确保每次都肯定可以访问它。可能存在一些细微的错误,例如所有对象(包括它们自己)上存在的属性。
  3. 您的对象图有周期。我认为这是最可能的原因。您从对象A开始,并在更深层次上再次找到A。为了解决这个问题,你需要使用遍历对象的缓存并将其从调用传递到调用,HashSet<T>将工作正常。检查你的当前对象是否存在于散列集中将成为一个额外的非递归情况。
+0

好吧,看起来你可能只是回答我的问题@SoulDZIN。所以System.Collections.Stack在堆上工作? – boyang

+0

@boyang是的,所有引用类型都分配在堆上。调用堆栈包含函数调用框架和函数本地(包括指向引用类型的指针)。 – Alexey

+1

@boyang在这种情况下使用'堆',特别是来自旧的C术语,是误导性的,因为这个问题不是(严格)与分配对象的位置(在.NET中很大程度上可忽略)相关。名称Stack表示抽象数据类型的类型 - 例如。List,Stack(“push”,“pop”),队列,字典 - 并且与堆栈帧(和StackOverflowException)或“堆”无关。 – user2864740