C# 为什么这段代码会产生指数循环。净Lehvenstein距离
所以最近我开始了一个编码项目,试图创建一些代码,以数学方式创建一种描述两个字符串有多相似的方法。在我的研究中,我在网上找到了大量的例子来帮助我创建我想要的代码。 我发现了一个错误,在我运行它的过程中,它正在创建,我称之为,指数循环。它不是一个无限循环,它运行并解决问题,但是我传递给方法的字符串越长,方法运行的时间就越长。代码如下C# 为什么这段代码会产生指数循环。净Lehvenstein距离,c#,.net,recursion,infinite-loop,levenshtein-distance,C#,.net,Recursion,Infinite Loop,Levenshtein Distance,所以最近我开始了一个编码项目,试图创建一些代码,以数学方式创建一种描述两个字符串有多相似的方法。在我的研究中,我在网上找到了大量的例子来帮助我创建我想要的代码。 我发现了一个错误,在我运行它的过程中,它正在创建,我称之为,指数循环。它不是一个无限循环,它运行并解决问题,但是我传递给方法的字符串越长,方法运行的时间就越长。代码如下 public static int LevenshteinDistance(this string source, string target) {
public static int LevenshteinDistance(this string source, string target)
{
Console.WriteLine("Start of method");
if (source.Length == 0) { return target.Length; }
if (target.Length == 0) { return source.Length; }
int distance = 0;
Console.WriteLine("Distance creation");
if (source[source.Length - 1] == target[target.Length - 1]) { distance = 0; }
else { distance = 1; }
Console.WriteLine("Recursive MethodCall");
return Math.Min(Math.Min(LevenshteinDistance(source.Substring(0, source.Length - 1), target) + 1,
LevenshteinDistance(source, target.Substring(0, target.Length - 1))) + 1,
LevenshteinDistance(source.Substring(0, source.Length - 1), target.Substring(0, target.Length - 1)) + distance);
}
所以,对于较小的字符串,这个方法运行得很好,但是,当我开始传入地址或长名称时,它需要很长时间。事实上,我已经完全取消了这个方法,并编写了另一个方法(如果有人想要,我会提供这个方法,但这对问题并不重要),这符合我的目的,但为了解决问题和学习,我试图弄清楚为什么这个方法在递归编码时需要这么长时间。在调试模式下,当我到达这里的递归调用时,我用笔和纸逐步完成了我的代码
return Math.Min(Math.Min(LevenshteinDistance(source.Substring(0, source.Length - 1), target) + 1,
LevenshteinDistance(source, target.Substring(0, target.Length - 1))) + 1,
LevenshteinDistance(source.Substring(0, source.Length - 1), target.Substring(0, target.Length - 1)) + distance);
}
我可以计算出这些部件到底发生了什么
return Math.Min(***Math.Min(LevenshteinDistance(source.Substring(0, source.Length - 1), target) + 1,
LevenshteinDistance(source, target.Substring(0, target.Length - 1))) + 1,***
LevenshteinDistance(source.Substring(0, source.Length - 1), target.Substring(0, target.Length - 1)) + distance);
}
我试着用黑体字和斜体字标出这个部分,我的意思是,它周围有“***”的部分。说到这一部分,我理解了,但是接下来的部分,第三个levenshteinsistance调用,这是第一次迭代,我开始失去对递归的关注,它是如何工作的,以及我的困惑从哪里开始的。这部分
LevenshteinDistance(source.Substring(0, source.Length - 1), target.Substring(0, target.Length - 1)) + distance);
}
我收集的代码一旦到达这个调用,就开始再次执行第一个LevenshteinDistance调用,并在刚刚执行的调用上重复。这就是我困惑的地方。为什么它会再次调用递归调用的第一部分,然后调用第二部分?是什么导致完成代码的时间呈指数级增长
注意:从字面意义上讲,时间可能不是指数级的,运行短字符串比较的时间是亚秒,一旦字符串稍微长一点,它就会从亚秒->约15秒->2:30分钟->35分钟跳转
第二个注意事项:被标记为无限循环的指数循环并不存在,这有点接近它。因为它是递归的,而不仅仅是单一递归(就像你在树视图中使用的那样),这只小狗会传递自己3次递归调用的返回结果 这就是为什么看到指数计时随着字符串的延长而增加 对于大小为
n
和m
的一对字符串(源、目标),您正在对函数进行3次递归调用
LevenshteinDistance(source[0..n - 1], target)
LevenshteinDistance(source, target[0..m - 1])
LevenshteinDistance(source[0..n - 1], target[0..m - 1])
因此,您正在为每个节点创建一个包含3个子节点的树,最小深度为min(n,m)
,最大深度为max(m,n)
因此,在该树的每个级别中,节点数量是上一级别的3倍:
0
|- 1
|- 2
|- 2
|- 2
|- 1
|- 2
|- 2
|- 2
|- 1
|- 2
|- 2
|- 2
等等
因此,对于树中的每个级别k
,您有3k个节点。
因此,算法的复杂度是O(3max(n,m)),这是指数型的。标准递归算法多次计算值 下面是两个大小为3的字符串的小示例,计算顺序如下
D[2, 2] = min(recurse(1, 2), recurse(2, 1), recurse(1, 1) + eq(1, 1))
下面是您得到的3个递归调用
//first recursive call
D[1, 2] = min(recurse(0, 2), recurse(1, 1), recurse(0, 1))
//second recursive call
D[2, 1] = min(recurse(1, 1), recurse(2, 0), recurse(1, 0))
//third recursive call
D[1, 1] = min(recurse(0, 1), recurse(1, 0), recurse(0, 0))
在这里您已经看到,我们有相同值的多个计算
正如你已经发现的那样,复杂性是指数级的。更精确的Θ(3^min(m,n))
但是,可以通过对计算值使用缓存并检查缓存(如果已经计算了值)来克服这一问题。这个方法也会被调用,然后复杂度会变成
Θ(nm)
请注意,每个调用都要进行3次递归调用。我的数学有点不对劲,但大致上每个级别(在递归调用树中)调用3次。级别对应于2个输入字符串之间的最小字符数
对于levenshteindication(“a”,“x”)
调用,您将最终进行4次调用(第一次调用+3次递归调用)
对于LevenshteinDistance(“ab”,“xy”)
调用,您将进行19次调用(第一次调用+3次递归调用,每次递归调用将导致3次以上调用,其中2次调用将在最后一次递归)
(ab,xy)
(a,xy)
(,xy)
(a,x)
(,x)
(a,)
(, )
(,x)
(ab,x)
(a,x)
(,x)
(a,)
(, )
(ab,)
(a,)
(a,x)
(,x)
(a,)
(, )
请注意,调用树中的每一次删除(处理字符串中的最后一个字符)都不会将n减少1,从而导致调用总数介于(3^(n+1)-1)/2到(3^(n+2)-1)/2之间
希望这能充分说明代码的性能
我没有对您的算法或实现进行过太多的分析,但我可以告诉您一些可以提高性能的事情
“错误”不是递归本身,一些最有效的代码是递归方法,但它是您创建的递归类型,它是递归时间越长,输入字符串越长。最终您将遇到一个;)谢谢你,杰里米,我在哪里可以找到一个地方学习如何画一个程序的“树视图”?也许下次遇到类似的问题时,我可以尝试使用这种策略,只要使用调用堆栈,Ctrl+Alt+C,就可以了
(ab, xy)
(a, xy)
(<empty>, xy)
(a, x)
(<empty>, x)
(a, <empty>)
(<empty>, <empty>)
(<empty>, x)
(ab, x)
(a, x)
(<empty>, x)
(a, <empty>)
(<empty>, <empty>)
(ab, <empty>)
(a, <empty>)
(a, x)
(<empty>, x)
(a, <empty>)
(<empty>, <empty>)