1. 程式人生 > >用分解質因數求兩個數字的最大公約數和最小公倍數

用分解質因數求兩個數字的最大公約數和最小公倍數

分解質因數採用Pollard Rho快速因數分解演算法,該演算法描述如下:

輸入一個任意數字n後,從最小的質數k=2開始,按下述步驟完成:

1 如果k恰等於n,則說明分解質因數的過程已經結束,打印出即可。

2 如果n>k,但n能被k整除,則應打印出k的值,並用n除以k的商作為新的正整數n,重複執行第一步。

3 如果n不能被k整除,則用k+1作為k的值,重複執行第一步。

def check_num(func):
    def valid(num):
        if num < 2:
            print('請輸入大於2的數字')
            return None
        else:
            return func(num)
    return valid

@check_num
def get_prime(n):
    c = []
    k = 2
    while True:
        if n == k:
            c.append(k)
            break
        elif n > k and n % k ==0:
            n /= k
            c.append(k)
        else:
            k += 1
    return c

根據質因數求最大公約數:

例如數字24 [2,2,2,3]和數字18 [2,3,3]的最大公約數就是兩者質因數列表的所有公共部分,也就是8 [2,3]

但是不能採用如下的演算法計算:

a = [2,2,2,3]
b = [2,3,3]
c = []
for x in a:
    if x in b:
      c.append(x)
print(c) #[2,2,2,3]

顯然這是錯誤的演算法,會重複計算很多數字。

所以正確的計算方法是,一旦進入了c列表的數字就應該從a列表和b列表中刪除掉,避免重複計算:

a = [2,2,2,3]
b = [2,3,3]
c = []
while len(a):
    x = a[0]
    if x in b:
        c.append(x)
        b.remove(x)
    a.remove(x)
print(c)

例如for迴圈一邊遍歷一邊刪除列表中元素可能會導致列表下標的不正確。所以這裡採用了一種變通的方式,每次都取a列表的第一個元素,如果這個元素恰好也在b列表中,在該元素進入c列表,並從b列表和a列表中刪除;如果這個元素不在b列表中,則直接從a列表中刪除。

可以將上述程式碼整理為函式:

def get_divider(m,n):
    a = get_prime(m)
    b = get_prime(n)
    c = []
    while len(a):
        x = a[0]
        if x in b:
            c.append(x)
            b.remove(x)
        a.remove(x)
    return c

根據質因數求最小公倍數:

根據公式:m * n = 最大公約數 * 最小公倍數,可以推匯出最小公倍數就是兩數之積除以最大公約數

根據質因數列表

24 [2,2,2,3]

18 [2,3,3]

則計算24*18之積實際就是列表做加法[2,2,2,3]+[2,3,3]得到[2,2,2,2,3,3,3]

做除法,就是從[2,2,2,2,3,3,3]去除掉除數的質因數列表即可

例如,24和18的最大公約數是[2,3],則最小公倍數就是從[2,2,2,2,3,3,3]去掉[2,3]

最終得到[2,2,2,3,3]就是最小公倍數。

遺憾的是,python中的列表不支援減法,也就是沒有[2,2,2,2,3,3,3] - [2,3]這樣的操作

所以這裡又需要一些演算法上的變通:

a = [2,2,2,3]
b = [2,3,3]
c = []
#求最小公倍數
for x in a:
    if x in b:
        b.remove(x)
c = a+b
c.sort()
print(c)

因為兩個列表之間只能做加法,因此在做加法之前,先從一個列表中把兩個列表中都有的可以成為公約數的內容從一個列表中刪除掉,然後再讓兩個列表做加法即可。

將上述程式碼整理為函式:

def get_fold(m,n):
    a = get_prime(m)
    b = get_prime(n)
    c = []
    for x in a:
        if x in b:
            b.remove(x)
    c = a+b
    c.sort()
    return c

最終可以將上述程式碼綜合為工具方法,根據需要返回質因數形式或數字形式的最大公約數和最小公倍數:

def get_divfold_bylist(m,n):
    return get_divider(m,n),get_fold(m,n)

def get_divfold(m,n):
    a,b  = get_divfold_bylist(m,n)
    f =lambda x,y:x*y
    return reduce(f,a),reduce(f,b)

完整程式碼如下:

from functools import reduce

def check_num(func):
    def valid(num):
        if num < 2:
            print('請輸入大於2的數字')
            return None
        else:
            return func(num)
    return valid

@check_num
def get_prime(n):
    c = []
    k = 2
    while True:
        if n == k:
            c.append(k)
            break
        elif n > k and n % k ==0:
            n /= k
            c.append(k)
        else:
            k += 1
    return c

def get_divider(m,n):
    a = get_prime(m)
    b = get_prime(n)
    c = []
    while len(a):
        x = a[0]
        if x in b:
            c.append(x)
            b.remove(x)
        a.remove(x)
    return c

def get_fold(m,n):
    a = get_prime(m)
    b = get_prime(n)
    c = []
    for x in a:
        if x in b:
            b.remove(x)
    c = a+b
    c.sort()
    return c

def get_divfold_bylist(m,n):
    return get_divider(m,n),get_fold(m,n)

def get_divfold(m,n):
    a,b  = get_divfold_bylist(m,n)
    f =lambda x,y:x*y
    return reduce(f,a),reduce(f,b)