数据结构和算法-数学问题-最大公约数

2021-09-26 11:09:08 浏览数 (1)

本文为joshua317原创文章,转载请注明:转载自joshua317博客 https://www.joshua317.com/article/140

一、最大公约数说明

最大公约数,也称最大公因数、最大公因子,指两个或多个整数共有约数中最大的一个。

如果有一个自然数a能被自然数b整除,则称a为b的倍数,b为a的约数。约数和倍数都表示一个整数与另一个整数的关系,不能单独存在。比如,只能说8是某数的倍数,2是某数的约数,而不能孤立地说8是倍数,2是约数。

几个自然数公有的约数,叫做这几个自然数的公约数。其中最大的一个,叫做这几个数的最大公约数。比如,12、16的公约数有1、2、4,其中最大的一个是4,4是12与16的最大公约数,一般记为(12,16)=4。12、15、18的最大公约数是3,记为(12,15,18)=3。

二、实现最大公约数的算法

2.1 辗转相除法,又称欧几里德算法

辗转相除法又称欧几里德算法,是用来求两个正整数最大公约数的算法。它是古希腊数学家欧几里得在其著作《The Elements》中最早描述了这种算法,所以被命名为欧几里得算法。

定理:两个正整数的最大公约数等于其中较小的那个数和两数相除余数的最大公约数。最大公约数(Greatest Common Divisor)缩写为GCD。

gcd(a,b) = gcd(b,a mod b) (不妨设a>b 且r=a mod b ,r不为0)

欧几里得的辗转相除算法是计算两个自然数最大公约数的传统算法,对于多个自然数可以执行多次辗转相除法来得到最大公约数。辗转相除法的执行过程如下:

代码语言:javascript复制
(1)对于已知两自然数a、b,假设a>b;
(2)计算a除以b,将得到的余数记为r;
(3)如果r=0,则b为求得的最大公约数,否则执行下面一步;
(4)将b的值保存到a中,将r的值保存到b中,
重复执行步骤(2)和(3),直到r=0,便得到最大公约数。

流程图如下:

2.2 Stein算法

Stein算法是一种计算两个数最大公约数的算法,是针对欧几里德算法在对大整数进行运算时,需要试商导致增加运算时间的缺陷而提出的改进算法。

欧几里德算法是计算两个数最大公约数的传统算法,无论从理论还是从实际效率上都是很好的。但是却有一个致命的缺陷,这个缺陷在素数比较小的时候一般是感觉不到的,只有在大素数时才会显现出来。一般实际应用中的整数很少会超过64位(当然已经允许128位了),对于这样的整数,计算两个数之间的模是很简单的。对于字长为32位的平台,计算两个不超过32位的整数的模,只需要一个指令周期,而计算64位以下的整数模,也不过几个周期而已。但是对于更大的素数,这样的计算过程就不得不由用户来设计,为了计算两个超过64位的整数的模,用户也许不得不采用类似于多位数除法手算过程中的试商法,这个过程不但复杂,而且消耗了很多CPU时间。致使辗转相除法效率变低。对于现代密码算法,要求计算128位以上的素数的情况比比皆是,设计这样的程序迫切希望能够抛弃除法和取模。

接下来我们来分析下Stein算法的原理:

gcd是greatest common divisor(最大公约数)的缩写,用gcd( x,y ) 表示x和y的最大公约数。然后有一个事实需要了解:一个奇数的所有约数都是奇数。研究一下最大公约数的性质,我们发现, gcd( k*x,k*y ) = k*gcd( x,y ) 。说它好,是因为它非常符合化小的思想。我们取 k=2 ,则有 gcd( 2*x,2*y ) = 2 * gcd( x,y )。这使我们很快联想到将两个偶数化小的方法。那么一奇一个偶以及两个奇数的情况如何化小呢?

一奇一偶:

设有2x和y两个数,其中y为奇数。因为y的所有约数都是奇数,所以 a = gcd(2*x,y) 是奇数。根据2*x是个偶数不难联想到,a应该是x的约数。我们来证明一下:(2*x)%a=0,设2*x=n*a,因为a是奇数,2*x是偶数,则必有n是偶数。又因为 x=(n/2)*a,所以 x%a=0,即a是x的约数。因为a也是y的约数,所以a是x和y的公约数,有 gcd(2*x,y) <= gcd(x,y)。因为gcd(x,y)明显是2*x和y的公约数,又有gcd(x,y) <= gcd(2*x,y),所以 gcd(2*x,y) = gcd(x,y)。至此,我们得出了一奇一偶时化小的方法。

两个奇数:

设有两个奇数x和y,似乎x和y直接向小转化没有什么太好的办法,我们可以绕个道,把x和y向偶数靠拢去化小。不妨设x>y,我们注意到x y和x-y是两个偶数,则有 gcd( x y,x-y ) = 2 * gcd( (x y)/2,(x-y)/2 ),那么 gcd( x,y ) 与 gcd( x y,x-y ) 以及 gcd( (x y)/2,(x-y)/2 ) 之间是不是有某种联系呢?为了方便我设 m=(x y)/2 ,n=(x-y)/2 ,容易发现 m n=x ,m-n=y 。设 a = gcd( m,n ),则 m%a=0,n%a=0 ,所以 (m n)%a=0,(m-n)%a=0 ,即 x%a=0 ,y%a=0 ,所以a是x和y的公约数,有 gcd( m,n )<= gcd(x,y)。再设 b = gcd( x,y )肯定为奇数,则 x%b=0,y%b=0 ,所以 (x y)%b=0 ,(x-y)%b=0 ,又因为x y和x-y都是偶数,跟前面一奇一偶时证明a是x的约数的方法相同,有 ((x y)/2)%b=0,((x-y)/2)%b=0 ,即 m%b=0 ,n%b=0 ,所以b是m和n的公约数,有 gcd( x,y ) <= gcd( m,n )。所以 gcd( x,y ) = gcd( m,n ) = gcd( (x y)/2,(x-y)/2 )。

整理一下,对两个正整数 x>y :

代码语言:javascript复制
1.均为偶数 gcd(x,y) = 2gcd(x/2,y/2);
2.均为奇数 gcd(x,y) = gcd((x y)/2,(x-y)/2);
2.x奇y偶  gcd(x,y) = gcd(x,y/2);
3.x偶y奇  gcd(x,y) = gcd(x/2,y) 或 gcd(x,y)=gcd(y,x/2);

Stein算法不采用除法和取模运算,而是采用整数的移位和最普通的加减法。这样,在计算超过64位的整数时,算法执行效率非常高。Stein算法的执行过程如下:

代码语言:javascript复制
(1)对于已知两自然数a、b,a和b谁大谁小没有要求;
(2)首先判断a或b的值,如果a=0,b就是最大公约数;如果b=0,a就是最大公约数,从而可以直接完成计算操作。如果a和b均不为0,则执行下一步。
(3)判断a,b的大小,m保存大的数,n保存小的数,比如a>b,完成如下赋值:m=a、n=b。
(4)判断m和n是否为偶数,若都是偶数,记录下公约数为2,同时使m=m/2,n=n/2,。
(5)若m是偶数,n是奇数,使m=m/2,递归调用。
(6)若n是偶数,m是奇数,使n=n/2,递归调用。
(7)若m和n都是奇数,使m=(m n)/2,n=(m-n)/2,递归调用。
以上述算法的执行过程中,反复用到除2和乘2的操作。其实乘2只需将二进制整数左移一位,而除2只需要将二进制整数右移一位即可,这样程序的执行效率更高。

2.3 更相减损术算法,又称辗转相减法,尼考曼彻斯法

转相减法是一种简便的求出两数最大公约数的方法。

辗转相减法(求最大公约数),即尼考曼彻斯法,其特色是做一系列减法,从而求得最大公约数。例如 :两个自然数35和14,用大数减去小数,(35,14)->(21,14)->(7,14),此时,7小于14,要做一次交换,把14作为被减数,即(14,7)->(7,7),再做一次相减,结果为0,这样也就求出了最大公约数7

更相减损法最早是出自《九章算术》的一种求最大公约数的算法,它原本是为约分而设计的,但它适用于任何需要求最大公约数的场合。又名“更相减损术”,辗转相减法,等值算法,尼考曼彻斯法,被广泛应用在数学和计算机上。

它的基本原理就是:大数减小数,直到两数相等时,即为最大公约数。

辗转相减法的执行过程如下:

代码语言:javascript复制
(1)对于已知两自然数a、b;
(2)如果a>b,以较大的数减较小的数,接着把所得的差a-b赋值给a,与较小的数b继续比较继续相减;
(3)如果a<b,以较大的数减较小的数,接着把所得的差b-a赋值给b,与较小的数a继续比较继续相减;
(4)直到a=b时,最大公约数就出来了

2.4 短除法

短除法是求最大公约数的一种方法,它也可用来求最小公倍数。求几个数最大公约数的方法,开始时用观察比较的方法,即:先把每个数的因数找出来,然后再找出公因数,最后在公因数中找出最大公约数。后来,使用分解质因数法来分别分解两个数的因数,再进行运算。之后又演变为短除法。短除法运算方法是先用一个除数除以能被它除尽的一个质数,以此类推,除到商是质数为止。

短除法求最大约数,先用这几个数的公约数连续去除,一直除到所有的商互质为止,然后把所有的除数连乘起来,所得的积就是这几个数的最大公约数。例如,求24、60的最大公约数。(24、60)=2×2×3=12

无论是短除法,还是分解质因数法,在质因数较大时,都会觉得困难。这时就需要用新的方法。

它的基本原理就是:用这几个数的公约数连续去除,一直除到所有的商互质为止,然后把所有的除数连乘起来,所得的积就是这几个数的最大公约数

短除法的执行过程如下:

代码语言:javascript复制
(1)对于已知两自然数a、b;
(2)设定j=1,i=2为初始值,a,b同时对i取模运算,如果取模都为0,j=j*i;直到数据

2.5 质因数分解法

分解质因数只针对合数。(分解质因数也称分解素因数)求一个数分解质因数,要从最小的质数除起,一直除到结果为质数为止。分解质因数的算式叫短除法,和除法的性质相似,还可以用来求多个数的公因式。

把每个数分别分解质因数,再把各数中的全部公有质因数提取出来连乘,所得的积就是这几个数的最大公约数。例如:求24和60的最大公约数,先分解质因数,得24=2×2×2×3,60=2×2×3×5,24与60的全部公有的质因数是2、2、3,它们的积是2×2×3=12,所以(24、60)=12。

短除法和质因数分解法原理类似,就不在赘述

三、求最大公约数编程实现

代码语言:javascript复制
package com.joshua317;

import java.util.Arrays;
import java.util.Scanner;

public class Main {

    public static void main(String[] args) {
        int a, b;
        /**
        System.out.println("最大公因数求解");
        System.out.println("请输入第1个整型数据");
        Scanner scanner = new Scanner(System.in);
        a = scanner.nextInt();
        System.out.println("请输入第2个整型数据");
        b = scanner.nextInt();
         */

        a = 24;
        b = 60;

        int gcd = gcd5(24,60);
        System.out.println("最大公约数为: "   gcd);
    }

    //欧几里德算法-迭代实现
    public static int gcd(int a, int b)
    {
        int r;
        r = a % b;
        while (r != 0) {
            a = b;
            b = r;
            r = a % b;
        }
        return b;
    }

    //欧几里德算法-递归实现
    public static int gcd1(int a, int b)
    {
        int r = a % b;
        if (r == 0) {
            return b;
        }
        a = b;
        b = r;
        return gcd1(a, b);
    }

    //Stein算法-递归实现
    public static int gcd2(int a, int b)
    {
        int m, n;
        if (a >= b) {
            m = a;
            n = b;
        } else {
            m = b;
            n = a;
        }

        if (n == 0) {
            return m;
        }
        if (m%2 == 0 && n%2==0) {
            return 2 * gcd2(m/2, n/2);
        }
        if (m%2 == 0) {
            return gcd2(m/2, n);
        }

        if (n%2 == 0) {
            return gcd2(m, n/2);
        }

        return gcd2((m n)/2, (m-n)/2);
    }

    //辗转相减法 迭代实现
    public static int gcd3(int a, int b)
    {
        while (a == b) {
            if (a - b > 0) {
                a -= b;
            } else {
                b -= a;
            }
        }
        return a;
    }

    //辗转相减法 递归实现
    public static int gcd4(int a, int b)
    {
        if (a == b) {
            return a;
        } else if (a > b) {
            return  gcd4(a-b, b);
        } else {
            return  gcd4(b-a, a);
        }
    }

    public static int gcd5(int a, int b)
    {
        int i, j = 1;
        for (i = 2; i <= a && i <= b; i  )
        {
            while (a % i == 0 && b % i == 0)
            {
                j = j * i;
                a = a / i; b = b / i;
            }
        }
        return j;
    }

}

本文为joshua317原创文章,转载请注明:转载自joshua317博客 https://www.joshua317.com/article/140

0 人点赞