欧几里得算法如何工作?
我刚刚在我的讲义中发现了这个算法来计算最大公约数:
public static int gcd( int a, int b ) {
while (b != 0) {
final int r = a % b;
a = b;
b = r;
}
return a;
}
所以r是将b除以a时的余数(得到模)。然后将b赋值给a,将余数赋值给b,并返回a。我一辈子都看不出这是如何运作的!
然后,显然这个算法并不适用于所有情况,然后必须使用这个算法:
public static int gcd( int a, int b ) {
final int gcd;
if (b != 0) {
final int q = a / b;
final int r = a % b; // a == r + q * b AND r == a - q * b.
gcd = gcd( b, r );
} else {
gcd = a;
}
return gcd;
}
我不明白这背后的原因。我通常会使用递归并且擅长 Java,但这却让我困惑。请帮忙?
I just found this algorithm to compute the greatest common divisor in my lecture notes:
public static int gcd( int a, int b ) {
while (b != 0) {
final int r = a % b;
a = b;
b = r;
}
return a;
}
So r is the remainder when dividing b into a (get the mod). Then b is assigned to a, and the remainder is assigned to b, and a is returned. I can't for the life of my see how this works!
And then, apparently this algorithm doesn't work for all cases, and this one must then be used:
public static int gcd( int a, int b ) {
final int gcd;
if (b != 0) {
final int q = a / b;
final int r = a % b; // a == r + q * b AND r == a - q * b.
gcd = gcd( b, r );
} else {
gcd = a;
}
return gcd;
}
I don't understand the reasoning behind this. I generally get recursion and am good at Java but this is eluding me. Help please?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
Wikipedia 文章包含了解释,但立即找到它并不容易(而且,过程+证明不要总是回答“为什么它有效”的问题)。
基本上可以归结为这样一个事实:对于两个整数 a、b(假设 a >= b),总是可以写成 a = bq + r,其中 r < b.
如果 d=gcd(a,b) 那么我们可以写 a=ds 和 b=dt。所以我们有 ds = qdt + r。由于左侧可被 d 整除,右侧也必须可被 d 整除。由于 qdt 可被 d 整除,因此结论是 r 也必须能被 d 整除。
总结一下:我们有 a = bq + r,其中 r < b 和 a、b 和 r 都可以被 gcd(a,b) 整除。
由于a>=b> r,我们有两种情况:
为什么会出现这样的减少呢?因为 r < b.所以我们正在处理的数字肯定更小。这意味着在达到 r = 0 之前我们只需应用这种归约有限次。
现在, r = a % b 这有望解释您所拥有的代码。
The Wikipedia article contains an explanation, but it's not easy to find it immediately (also, procedure + proof don't always answer the question "why it works").
Basically it comes down to the fact that for two integers a, b (assuming a >= b), it is always possible to write a = bq + r where r < b.
If d=gcd(a,b) then we can write a=ds and b=dt. So we have ds = qdt + r. Since the left hand side is divisible by d, the right hand side must also be divisible by d. And since qdt is divisible by d, the conclusion is that r must also be divisible by d.
To summarise: we have a = bq + r where r < b and a, b and r are all divisible by gcd(a,b).
Since a >= b > r, we have two cases:
Why is this a reduction? Because r < b. So we are dealing with numbers that are definitely smaller. This means that we only have to apply this reduction a finite number of times before we reach r = 0.
Now, r = a % b which hopefully explains the code you have.
它们是等价的。首先要注意的是第二个程序中的
q
根本没有使用。另一个区别只是迭代与递归。至于为什么它有效,上面链接的维基百科页面很好。第一个插图特别有效地直观地传达了“为什么”,下面的动画则说明了“如何”。
They're equivalent. First thing to notice is that
q
in the second program is not used at all. The other difference is just iteration vs. recursion.As to why it works, the Wikipedia page linked above is good. The first illustration in particular is effective to convey intuitively the "why", and the animation below then illustrates the "how".
鉴于从未使用过“q”,我看不出您的普通迭代函数和递归迭代函数之间有什么区别...除非
尝试将其应用于非整数,在什么情况下该算法会失败?
(另请参阅 http://en.wikipedia.org/wiki/Euclidean_algorithm 了解更多详细信息)
given that 'q' is never used, I don't see a difference between your plain iterative function, and the recursive iterative function... both do
Barring trying to apply this to non-integers, under which circumstances does this algorithm fail?
(also see http://en.wikipedia.org/wiki/Euclidean_algorithm for lots of detailed info)
这是一篇有趣的博客文章:Tominology。
其中讨论了欧几里得算法背后的许多直觉,它是用 JavaScript 实现的,但我相信,如果有人愿意,将代码转换为 Java 并不困难。
Here is an interesting blog post: Tominology.
Where a lot of the intuition behind the Euclidean Algorithm is discussed, it is implemented in JavaScript, but I believe that if one want's there is no difficult to convert the code to Java.
这里 是我发现的一个非常有用的解释。
对于那些懒得打开它的人来说,它是这样说的:
考虑一下当你必须找到 (3084,1424) 的 GCD 时的例子。假设 d 是 GCD。这意味着 d | 3084 和d | 1424(使用符号“|”表示“除”)。
由此可见 d | (3084 - 1424)。现在我们将尝试尽可能地减少这些可被 d 整除的数字(在本例中为 3084 和 1024),以便我们达到 0 作为数字之一。请记住,GCD (a, 0) 是 a。
自 d | (3084 - 1424),因此 d | ( 3084 - 2(1424) )
这意味着 d | 236.
提示:(3084 - 2*1424 = 236)
现在忘记最初的数字,我们只需要求解 d,我们知道 d 是整除 236、1424 和 3084 的最大数字。所以我们使用较小的两个数字继续,因为它会将问题收敛到 0
。 1424 和d 236 意味着 d | (1424 - 236)。
所以,d | ( 1424 - 6(236) ) => d | 8.
现在我们知道 d 是能整除 8、236、1424 和 3084 的最大数。再取较小的两个,我们有
d | 236 和d | 8,这意味着 d | (236 - 8)。
所以,d | ( 236 - 29(8) ) => d | 4.
可被 d 整除的数字列表再次增加并收敛(数字越来越小,接近 0)。现在,d 是能整除 4、8、236、1424、3084 的最大数。
采取相同的步骤,
d | 8 和 d | 4 意味着 d | (8-4)。
所以,d | ( 8 - 2(4) ) => d | 0.
可被 d 整除的数字列表现在为 0、4、8、236、1484、3084。
(a, 0) 的 GCD 始终为 a。因此,只要两个数字之一为 0,另一个数字就是原始两个数字以及中间所有数字的 gcd。
这正是您的代码正在做的事情。您可以将终止条件识别为 GCD (a, 0) = a。
另一步是找到两个数字的余数,并选择该数字和前两个数字中较小的一个作为新数字。
Here is a very useful explanation that I found.
For those too lazy to open it, this is what it says :
Consider the example when you had to find the GCD of (3084,1424). Lets assume that d is the GCD. Which means d | 3084 and d | 1424 (using the symbol '|' to say 'divides').
It follows that d | (3084 - 1424). Now we'll try to reduce these numbers which are divisible by d (in this case 3084 and 1024) as much as possible, so that we reach 0 as one of the numbers. Remember that GCD (a, 0) is a.
Since d | (3084 - 1424), it follows that d | ( 3084 - 2(1424) )
which means d | 236.
Hint : (3084 - 2*1424 = 236)
Now forget about the initial numbers, we just need to solve for d, and we know that d is the greatest number that divides 236, 1424 and 3084. So we use the smaller two numbers to proceed because it'll converge the problem towards 0.
d | 1424 and d | 236 implies that d | (1424 - 236).
So, d | ( 1424 - 6(236) ) => d | 8.
Now we know that d is the greatest number that divides 8, 236, 1424 and 3084. Taking the smaller two again, we have
d | 236 and d | 8, which implies d | (236 - 8).
So, d | ( 236 - 29(8) ) => d | 4.
Again the list of numbers divisible by d increases and converges (the numbers are getting smaller, closer to 0). As it stands now, d is the greatest number that divides 4, 8, 236, 1424, 3084.
Taking same steps,
d | 8 and d | 4 implies d | (8-4).
So, d | ( 8 - 2(4) ) => d | 0.
The list of numbers divisible by d is now 0, 4, 8, 236, 1484, 3084.
GCD of (a, 0) is always a. So, as soon as you have 0 as one of the two numbers, the other number is the gcd of original two and all those which came in between.
This is exactly what your code is doing. You can recognize the terminal condition as GCD (a, 0) = a.
The other step is to find the remainder of the two numbers, and choose that and the smaller of the previous two as the new numbers.