[英]Extended Euclid algorithm - is there non-recursive version of it?
我實現了擴展歐幾里得算法的以下版本:
long gcdex(const long& a, const long& b, long& x, long& y)
{
if (a == 0) {
x = 0; y = 1;
return b;
}
long x1, y1;
long d = gcdex(b % a, a, x1, y1);
x = y1 - (b / a) * x1;
y = x1;
return d;
}
我不知道如何實現它的非遞歸版本,你能幫我嗎?
任何遞歸算法都可以使用迭代和附加堆棧實現為非遞歸算法。 但這仍然會導致某些算法的可讀性大大降低,並且可能不會提高效率。
我喜歡你的算法版本——它簡短易讀(也許你需要重命名一些變量),它為你提供了算法的最佳復雜性。
可以在沒有堆棧和遞歸的情況下實現擴展的 euklidian 算法:
# Just a wrapper class to represent extended euklidian algorithm result
class GCD_Result:
def __init__(self, gcd, u, v):
self.gcd = gcd
# u and v are the linear combination coefficients
self.u = u
self.v = v
def __str__(self):
return str(self.gcd) + " = " + str(self.u) + " * a + " + str(self.v) + " * b"
def extended_gcd(a, b):
if a == 0:
return GCD_Result(b, 0, 1)
unPrev = 1
vnPrev = 0
unCur = 0
vnCur = 1
while b != 0:
bn = a // b
newB = a % b
a = b
b = newB
# Update coefficients
unNew = unPrev - bn * unCur
vnNew = vnPrev - bn * vnCur
# Shift coefficients
unPrev = unCur
vnPrev = vnCur
unCur = unNew
vnCur = vnNew
return GCD_Result(a, unPrev, vnPrev)
沒有堆棧很難實現這個算法,因為我們通常在退出遞歸調用時進行反向替換。 通過這樣做,我們使我們的算法成為非尾遞歸的。 我的算法所做的是將系數逐步更新為 a 和 b 更新。
正如 Ivaylo Strandjev 所說,任何遞歸算法都可以使用迭代和附加堆棧實現為非遞歸算法。 但是對於一些問題,我們可以使用一些特殊的技巧來實現。 對於這個問題,我們可以借助線性代數計算 x 和 y。
// non-recursive
void gcd_exd_non_rec(int a, int b, int &x, int &y) {
std::vector<std::vector<int>> vec(2);
vec[0] = {1, 0, a};
vec[1] = {0, 1, b};
while (vec[1][2]) {
int q = vec[0][2] / vec[1][2];
std::vector<int> tmp_0 = vec[1];
// just a vector arithmetic: vec[0] - q * vec[1]
std::vector<int> tmp_1 = {vec[0][0] - q * vec[1][0], vec[0][1] - q * vec[1][1], vec[0][2] - q * vec[1][2]};
vec[0] = tmp_0;
vec[1] = tmp_1;
}
x = vec[0][0];
y = vec[0][1];
return;
}
其他解決方案是絕對正確的,但我認為看到轉換有助於理解為什么會這樣,這是一種相當普遍的方法。
這是遞歸實現:
def extended_gcd(a, b):
if b == 0:
return a, 1, 0
(d,m) = divmod(a,b)
(r,x,y) = extended_gcd(b,m)
return (r, y, x - d * y)
最后一個“延續”是結果的線性變換——第一個條目可以是1 * r + 0 * x + 0 * y
。 我們可以將這些系數存儲在一個矩陣中,將所有矩陣存儲到最后,然后使用矩陣乘法應用它們:
import numpy as np
def extended_gcd(a, b):
out = []
while b != 0:
(d,m) = divmod(a,b)
a,b= b,m
out.append(np.array([[1,0,0], [0,0,1], [0, 1, -d]]))
result = [a, 1, 0]
for o in reversed(out):
result = o @ result
return result
但是矩陣乘法也可以用作函數組合,它是關聯的。 所以我們可以一路做乘法:
def extended_gcd(a, b):
out = np.identity(3)
while b != 0:
(d,m) = divmod(a,b)
a,b= b,m
out = out @ [[1,0,0], [0,0,1], [0, 1, -d]]
return out @ [a, 1, 0]
第一行始終是[1,0,0]
,因此刪除該行並簡化給我們留下了
def extended_gcd(a, b):
out = np.identity(2)
while b != 0:
(d,m) = divmod(a,b)
a,b= b,m
out = out @ [[0,1], [1, -d]]
return (a, out[0,0], out[1,0])
這就是其他答案所做的,模內聯矩陣乘法。 這是一個有用的技巧,因為它可以讓你將任何遞歸的“尾線性”函數變成一個循環。
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.