《编程之美》学习笔记——2.7最大公约数问题

28 篇文章 0 订阅
13 篇文章 0 订阅

一、问题

  求两个正整数的最大公约数。如果两个正整数都很大,有什么简单的算法吗?

问题分析:

什么是最大公约数? (参考维基百科:http://en.wikipedia.org/wiki/Greatest_common_divisor)

最大公约数Greatest Common Divisor (GCD); greatest common factor (gcf);highest common factor (hcf);greatest common measure (gcm);highest common divisor (hcd)

In mathematics, the greatest common divisor (gcd) of two or more integers, when at least one of them is not zero, is the largest positive integer that divides the numbers without a remainder.

符号:gcd(a,b)

例子:

The number 54 can be expressed as a product of two integers in several different ways:
54 * 1 = 27 * 2 = 18 * 3 = 9 * 6
Thus the divisors of 54 are:
1, 2, 3, 6, 9, 18, 27, 54.
Similarly the divisors of 24 are:
1, 2, 3, 4, 6, 8, 12, 24.
The numbers that these two lists share in common are the common divisors of 54 and 24:
1, 2, 3, 6.
The greatest of these is 6. That is the greatest common divisor of 54 and 24. One writes:
gcd(54,24) = 6.

拓展:什么是最小公倍数?(参考维基百科:http://en.wikipedia.org/wiki/Least_common_multiple)

最小公倍数lowest common multiple(LCM);least common multiple;smallest common multiple

In arithmetic and number theory, the least common multiple of two integers a and b, usually denoted by LCM(a, b), is the smallest positive integer that is divisible by both a and b.Since division of integers by zero is undefined, this definition has meaning only if a and b are both different from zero.

符号:lcm(a,b)

例子:

What is the LCM of 4 and 6?
Multiples of 4 are:
4, 8, 12, 16, 20, 24, 28, 32, 36, 40, 44, 48, 52, 56, 60, 64, 68, 72, 76, ...
and the multiples of 6 are:
6, 12, 18, 24, 30, 36, 42, 48, 54, 60, 66, 72, ...
Common multiples of 4 and 6 are simply the numbers that are in both lists:
12, 24, 36, 48, 60, 72, ....
So, from this list of the first few common multiples of the numbers 4 and 6, their least common multiple is 12.

最大公约数和最小公倍数的关系?

If a and b are both nonzero, the greatest common divisor of a and b can be computed by using least common multiple (lcm) of a and b:


通过上面这个公式,我们在求两个数的最小公倍数时,可以先求出这两个数的最大公约数,再利用上面的公式求出它们的最小公倍数。

二、解法

版本一:辗转相除法 / 欧几里德算法 —— 求余

算法原理:两个整数的最大公约数等于其中较小的数和两数相除余数的最大公约数。(gcd(a, b) = gcd(b, a mod b),可自行证明)通过这个方法,把两个整数不断缩小至其中一个为零,剩下没变成零的数就是两个数的最大公约数。

  算法实现我们考虑用较小的数和两数相除余数作为两个新的数求最大公约数(主要利用了求模运算)。

分析:当两个整数比较大时,采用求模运算(调用除法),是非常昂贵的开销。

算法C实现如下:(需要考虑的有:第一步循环中算法会从两个数中获取较小的数赋值给n,因此不需要提前判断m和n的大小,这部分原来是有加的,后面进行分析并把它优化掉了;while语句循环直到两个数其中一个为零,另一个非零数即为解;除数不能为零)

/**
 * @brief get greatest common divisor(gcd) for value m and n.
 *
 * @param[in]      m  first value
 * @param[in]      n  second value
 *
 * @return gcd value
 */
TYPE gcd(TYPE m, TYPE n)
{
    /// m and n range limits
    assert(m >= 0 && n >= 0);
    /// euclidean algorithm
    TYPE temp;
    while (n != 0) {
        temp = m % n;
        m = n;
        n = temp;
    }
    return m;
}
 

版本二:更相减损法(术)  —— 做差

  算法原理:两个整数的最大公约数等于其中较小的数和两数相差的最大公约数。这里利用的是两数相差,而不是辗转相除法的两数求余,但两者实际非常相似。

  我们考虑用较小的数和两数相差作为两个新的数求最大公约数(主要利用了减法运算)。

更相减损法与辗转相除法对比:对于大整数而言,相比辗转相除法的两数求余运算,单次时减法运算效率会更高。但两个正整数相差较大时,减法运算的执行次数要必求余运算执行次数多很多。

算法C实现如下(这里做了下优化,内嵌一个while循环用求两数相差时,多次做差,使尽可能减少大while循环执行次数):

/**
 * @brief get greatest common divisor(gcd) for value m and n.
 *
 * @param[in]      m  first value
 * @param[in]      n  second value
 *
 * @return gcd value
 */
TYPE gcd(TYPE m, TYPE n)
{
    /// m and n range limits
    assert(m >= 0 && n >= 0);
    /// subtraction
    while (m != n) {
        while (m > n)
            m -= n;
        temp = m;
        m = n;
        n = temp;
    }
    TYPE temp;
    return m;
}

 

版本三:辗转相除法 / 欧几里德算法 ——递归求余 (来自《编程之美》)

  这个版本与版本一都是用辗转相除法,原理中含有递归的思想,所以可以通过递归算法实现,这时算法实现必版本一算法实现简单得多。

算法C实现:

/**
 * @brief get greatest common divisor(gcd) for value m and n.
 *
 * @param[in]      m  first value
 * @param[in]      n  second value
 *
 * @return gcd value
 */
TYPE gcd(TYPE m, TYPE n)
{
    /// m and n range limits
    assert(m >= 0 && n >= 0);
    /// euclidean algorithm by recursion
    return (!n) ? m : gcd(n, m % n);
}

版本四:更相减损法(术) ——递归做差 (来自《编程之美》)

  这个版本与版本二都是用更相减损法(术),原理中含有递归的思想,所以可以通过递归算法实现,这时算法实现必版本二算法实现简单得多。

算法C实现:

/**
 * @brief get greatest common divisor(gcd) for value m and n.
 *
 * @param[in]      m  first value
 * @param[in]      n  second value
 *
 * @return gcd value
 */
TYPE gcd(TYPE m, TYPE n)
{
    /// m and n range limits
    assert(m >= 0 && n >= 0);
    if (m < n)
        return gcd(n, m);
    else if (n == 0)
        return m;
    else
        return gcd(m - n, n);
}

版本五: Stein's algorithm(参考维基百科及《编程之美》)

原理:

1.如果y=k*y1,x=k*x1。那么有f(x,y) = k*f(x1,y1);

2.如果x = p*x1,假设p是素数,并且y%p != 0,那么f(x,y)=f(p*x1,y)=f(x1,y)。

  由于除2和乘2运算都可以采用移位运算,同时我们已知2是质数,结合上面的两个结论,我们可以引出这种新的方法:

对于gcd(a,b)来说:

Both a and b are even.
In this case 2 is a common factor. Divide both a and b by 2, double d, and continue.
a is even and b is odd.
In this case 2 is not a common factor. Divide a by 2 and continue.
a is odd and b is even.
Like the previous case 2 is not a common factor. Divide b by 2 and continue.
Both a and b are odd.
Without loss of generality, assume that for a and b as they are now, a ≥ b. In this case let c = (a − b)/2. Then gcd(a,b) = gcd(a,c) = gcd(b,c). Because b ≤ a it is usually easier (and computationally faster) to determine gcd(b,c). If computing this algorithm by hand, gcd(b,c) may be apparent. Otherwise continue the algorithm until c = 0. Note that the gcd of the original a and b is still d times larger than the gcd of the odd a and odd b above.

分析:这个算法既避免了辗转相除法中求余运算开销,也避免了更相减损法中较多的减法执行次数,增加了判断次数,但是使用的与运算和移位运算效率都比较高。算法通过移位运算来代替乘2和除2运算(拓展的,可利用与运算来实现除2求余运算)。

算法C实现如下:(这里判断数字奇偶性用与操作,通过判断数字二进制表示时最低位时1还是0来判断数字是奇数还是偶数;同时使用移位运算来代替乘2和除2运算;算法以递归的方式实现)

/**
 * @brief get greatest common divisor(gcd) for value m and n.
 *
 * @param[in]      m  first value
 * @param[in]      n  second value
 *
 * @return gcd value
 */
TYPE gcd(TYPE m, TYPE n)
{
    /// m and n range limits
    assert(m >= 0 && n >= 0);
    if (m < n)
        return gcd(n, m);
    else if (n == 0)
        return m;
    if (m & 1) {
        if (n & 1)
            return gcd(n, (m - n) >> 1);
        else
            return gcd(m, n >> 1);
    } else {
        if (n & 1)
            return gcd(m >> 1, n);
        else
            return gcd(m >> 1, n >> 1) << 1;
    }
}

版本六: Stein's algorithm(非递归)

算法原理与版本五算法一致,版本六采用了非递归方式的实现,减少了递归调用的开销。

算法C实现:

/**
 * @brief get greatest common divisor(gcd) for value m and n.
 *
 * @param[in]      m  first value
 * @param[in]      n  second value
 *
 * @return gcd value
 */
TYPE gcd(TYPE m, TYPE n)
{
    /// m and n range limits
    assert(m >= 0 && n >= 0);
    /// binary method
    TYPE temp;
    TYPE shift_value = 0;
    while (n != 0) {
        if (m < n) {
            temp = m;
            m = n;
            n = temp;
        }
        if (m & 1) {
            if (n & 1) {
                temp = (m - n) >> 1;
                m = n;
                n = temp;
            } else {
                n >>= 1;
            }
        } else {
            if (n & 1) {
                m >>= 1;
            } else {
                shift_value++;
                m >>= 1;
                n >>= 1;
            }
        }
    }
    return m << shift_value;
}

其他方法:

   穷举法(暴力破解法):可以利用最大公约数的定义,初始化一个和这两个数中较小的数相同的数,不断对它进行减操作并判断这两个数能否都被它整除(求余为零)来找到最大公约数。这个方法也适用于最小公倍数。

   因质分解法:分解并记录两个数共有的因数,最后这些因数的乘积便是最大公约数。

   扩展欧几里德算法(见于维基百科)。

拓展:

  求解多个数的最大公约数及最小公倍数时,可把上面的算法拓展到多个数的实现。

总结:

  最小公倍数可以利用公式和最大公约数来求得,但需注意对于两个同样为零的数,最小公倍数是没有定义的。

  递归算法需要考虑递归调用的开销,但是实现代码相对简洁。

  Stein算法巧妙利用了求解最小公约数过程中的一些性质,结合移位运算高效实现。

  这些算法都指定了输入为正整数或零,实际上要考虑下负整数的情况,要注意的是负数求余在不同的编译器上实现很有可能不一样(可移植性问题)。

备注:

1.转载请注明出处:http://blog.csdn.net/chensilly8888/

2.全文源码均开源(在UBUNTU + GCC4.8.2下编译并测试通过),可下载或查看:https://github.com/chenxilinsidney/funnycprogram/tree/master/beauty_of_programming/gcd_lcm

3.有写错或写漏之处请指正,谢谢!

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
cda备考学习学习笔记——基础知识篇(二)主要涉及了计算机科学与技术领域的基本概念和知识。 首先,它介绍了计算机网络的基础知识。网络是将多台计算机通过通信链路连接起来,使它们能够相互通信和共享资源的系统。笔记中详细介绍了网络的组成、拓扑结构和通信协议等重要内容。 其次,笔记还解释了计算机系统的基本组成。计算机系统由硬件和软件两部分组成,其中硬件包括中央处理器、存储器、输入输出设备等,而软件则分为系统软件和应用软件。笔记详细介绍了各种硬件和软件的功能和作用。 此外,笔记还对数据库管理系统进行了介绍。数据库管理系统是一种用于管理和组织数据的软件系统,它能够实现数据的存储、检索和新等操作。笔记中详细介绍了数据库的概念、结构和操作等内容。 最后,笔记还包括了算法和数据结构的基础知识。算法是解决问题的一系列步骤和规则,而数据结构则是组织和存储数据的方式。笔记中介绍了常用的算法和数据结构,如排序算法、树和图等。 总之,通过学习CDA备考学习笔记中的基础知识篇(二),我们能够好地理解计算机网络、计算机系统、数据库管理系统以及算法和数据结构等相关概念和知识。这些基础知识对于我们深入研究计算机科学与技术领域是非常重要的,也为我们日后的学习和工作奠定了坚实的基础。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值