簡體   English   中英

擴展歐幾里得算法 - 有非遞歸版本嗎?

[英]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.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM