Python算法100例-4.4 最大公约数

完整源代码项目地址,关注博主私信'源代码'后可获取

1.问题描述

求任意两个正整数的最大公约数(Greatest Common Divisor,GCD)。

2.问题分析

如果有一个自然数a能被自然数b整除,则称a为b的倍数,b为a的约数。几个自然数公有的约数,叫作这几个自然数的公约数。公约数中最大的一个公约数,称为这几个自然数的最大公约数。

根据约数的定义可知,某个数的所有约数必不大于这个数本身,几个自然数的最大公约数必不大于其中任何一个数。要求任意两个正整数的最大公约数即求出一个不大于这两个数中的任何一个,但又能同时整除两个整数的最大自然数。

3.算法设计

思路有两种,第一种,采用穷举法按从小到大(初值为1,最大值为两个整数当中较小的数)的顺序将所有满足条件的公约数列出,输出其中最大的一个;第二种,按照从大(两整数中较小的数)到小(最小的整数1)的顺序求出第一个能同时整除两个整数的自然数,即为所求。下面对第二种思路进行详细说明。

无论按照从小到大还是从大到小的顺序寻找最大公约数,最关键的是找出两数中较小的数。对于输入的两个正整数m和n,相同的数据可能因为输入顺序不同导致变量m、n中存储数据的大小不同,如m=8、n=4与m=4、n=8,但无论变量中值的大小顺序怎么样最后的结果应该是相同的。为了避免相同数据因输入顺序不同而出现不同的结果,也为了使程序具有一般性,对于每次输入的值先进行大小排序,规定变量m中存储大数、变量n中存储小数。

两个变量所存储内容互换需要借助一个中间变量来完成,若采用将第二个变量的值赋给第一个变量,然后再将第一个变量的值赋给第二个变量的方法是错误的,因为经过第一次赋值(即第二个变量的值赋给第一个变量)后,第一个变量中存储的内容被替换掉,原来的值已经找不到了,所以再次赋值时并没有把第一个变量中原来的值赋给第二个变量,而是将改变之后的值(即第二个变量的值)赋给了第二个变量。正确的代码如下:

if m < n:                                            # 比较大小,使得m中存储大数,n中存储小数
    # 交换m和n的值
    temp = m
    m = n
    n = temp

两个数的最大公约数有可能是其中较小的数,故在按从大到小的顺序寻找最大公约数时,循环变量i的初值从较小的数n开始依次递减,去寻找第一个能同时整除两个整数的自然数,并将其输出。需要注意的是,虽然判定条件是i>0,但在找到第一个满足条件的i值后,循环没必要继续进行,如25和15,最大公约数是5,对于后面的4、3、2、1便没必要再去执行,但此时判定条件仍然成立,要结束循环只能借助break语句。

# 按照从大到小的顺序寻找满足条件的自然数
i = n 
while i > 0:
    if m % i == 0 and n % i == 0:
        # 输出满足条件的自然数并结束循环
        print("%d 和 %d 的最大公约数是: %d" %(m, n, i))
        break
    i -= 1

4.确定程序框架

程序的流程图如图所示。

在这里插入图片描述

5.完整的程序

根据上面的分析,编写程序如下:

# 最大公约数

if __name__ == "__main__":
    print("请输入两个整数")
    m = int(input("m = "))
    n = int(input("n = "))
    print(f'输入的m={m},n={n}')
    if m < n:                                            # 比较大小,使得m中存储大数,n中存储小数
        # 交换m和n的值
        temp = m
        m = n
        n = temp

    i = n                 # 按照从大到小的顺序寻找满足条件的自然数
    while i > 0:
        if m % i == 0 and n % i == 0:
            # 输出满足条件的自然数并结束循环
            print("%d 和 %d 的最大公约数是: %d" %(m, n, i))
            break
        i -= 1


请输入两个整数
输入的m=8,n=4
8 和 4 的最大公约数是: 4

第一种思路,按照从小到大的顺序穷举两数公约数的程序代码如下:

# 最大公约数

if __name__ == "__main__":
    print("请输入两个整数")
    m = int(input("m = "))
    n = int(input("n = "))
    print(f'输入的m={m},n={n}')
    # 比较两个数的大小,进行交换
    if m < n:
        temp = n
        n = m
        m = temp

    for i in range(1, n):
        if m % i == 0 and n % i == 0:
            k = i                                           # 将当前情况下的最大公约数存储在k中
    print("%d 和 %d 的最大公约数是:%d" %(m, n, k))

请输入两个整数
输入的m=56,n=72
72 和 56 的最大公约数是:8

此算法第一步也需要对两个变量的值进行大小比较,使得变量m中存储大数,n中存储小数。程序中需要注意的是,最后输出结果时不能直接输出变量i的值,因循环结束时循环变量i的值为n,并不一定是要求的最大公约数。为了使求得的公约数在穷举过程中被记录,可以将满足条件的i值暂存到变量k中,使得k中始终存储当前情况下的最大公约数。

6.问题拓展

早在公元前300年左右,欧几里得就在他的著作《几何原本》中给出了求最大公约数高效的解法——辗转相除法。辗转相除法用到的原理很简单,假设用f(x,y)表示x和y的最大公约数,取k=x/y,b=x%y,则x=ky+b,如果一个数能够同时整除x和y,则必能同时整除b和y;而能够同时整除b和y的数也必能同时整除x和y,即x和y的公约数与b和y的公约数是相同的,其最大公约数也是相同的,则有f(x,y)=f(y,x%y)(y>0),如此便可把原问题转换为求两个更小数的最大公约数,直到其中一个数为0,剩下的另外一个数就是两者最大的公约数。

例如,12和30的公约数有:1、2、3、6,其中6就是12和30的最大公约数。

欧几里德算法,其思想可概括如下:

1)用较大的数m除以较小的数n,得到的余数存储到变量b中,b=m%n。

2)上一步中较小的除数n和得出的余数b构成新的一对数,并分别赋值给m和n,继续做上面的除法。

3)若余数为0,其中较小的数(即除数)就是最大公约数。否则重复步骤1和步骤2。

以求288和123的最大公约数为例,操作如下:

288÷123=2余42

123÷42=2余39

42÷39=1余3

39÷3=13

所以3就是288和123的最大公约数。

在进行辗转相除之前同样要确定两数中的大数和小数,将其分别存放在不同变量中。

# 最大公约数——辗转相除法

if __name__ == "__main__":
    print("请输入两个整数")
    m = int(input("m = "))                                  # m存储较大数
    n = int(input("n = "))                                  # n存储较小数
    print("%d 和 %d 的最大公约数是: " % (m, n), end="")
    # 比较两个数的大小,进行交换,使得m是最大数,n是最小数
    if m < n:
        temp = n
        n = m
        m = temp
    b = m % n                                                       # b存储m 和 n取模得到的余数
    while b != 0:
        m = n                                                       # 原来的小数作为下次运算时的大数
        n = b                                                       # 将上一次的余数作为下次相除时的小数
        b = m % n
    print("%d" %n)


请输入两个整数
4 和 8 的最大公约数是: 4

7.拓展训练

求一个最小的正整数,这个正整数被任意n(2≤n≤10)除都是除不尽的,而且余数总是n-1。例如,被9除时的余数为8。要求设计一个算法,不得使用枚举与“除2,除3,…,除9,除10”有关的命令,求出这个正整数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

飘逸高铁侠

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值