在一般的程序设计书里面,都有关于求两个数的最大公因数的算法(或者叫做最大公约数)。求两个数的最大公约数算法用的最多的是辗转相除法。
基本思路就是
1 getCommonMutiple(a,b){ //这里假定a 是较大的数,实际编码时要先判断谁大谁小; 2 3 r = a%b; 4 5 if(r != 0){ 6 7 a = b; 8 9 b = r; 10 11 getCommonMutiple(a ,b); 12 13 } 14 15 return b; 16 17 }
但是如何求N个数的最大公因数呢?(N > 1)
在初等数学里,有这么几个定理
1、任何一个整数,都可以分解为素数因子的乘积
比如
24 = 2* 2* 2 * 3;
26 = 2 * 13;
分解时应该从2开始分解起
2、若干个数的最大公因数是共同素数因子的乘积
比如 求 48 60 72 的最大公因数
48 = 2*2*2*2*3
60 = 2*2*3*5
72 = 2*2*2*3*3
共同的素数因子为 2 * 2 * 3 = 12 所以他们的最大公因数就是12
有了这两个知识点之后,我们开始写程序
详见代码
1 /** 2 *Aug 25, 2013 3 *Copyright(c)JackWang 4 *All rights reserve 5 *@Author <a href="mailto:wangchengjack@163.com">JackWang</a> 6 */ 7 package com.example.blog; 8 9 import java.util.ArrayList; 10 import java.util.List; 11 12 /** 13 * 计算机里求最大公因数的方法通常是辗转相除法 14 * 但是这个方法的缺陷在与一次只能求两个数的最大公因数 15 * 如果需要求若干个数的最大公因数呢? 16 * 需求:设计一个算法,可以求出若干个整数的最大公因数 17 * @author Administrator 18 * 19 */ 20 public class CommonMutiple { 21 public static void main(String[] args) { 22 int[] data1 = {26,13}; 23 int[] data2 = {735000,421160,238948}; 24 int[] data3 = {1008,1260,882,1134}; 25 26 int result1 = getCommonMutiple(data1); 27 int result2 = getCommonMutiple(data2); 28 int result3 = getCommonMutiple(data3); 29 30 System.out.println(result1); 31 System.out.println(result2); 32 System.out.println(result3); 33 } 34 /** 35 * 得到最大公倍数的方法 36 * @param data1 传入数组 37 * @return 返回最大公倍数 38 */ 39 private static int getCommonMutiple(int[] data1) { 40 List<List<Integer>> divisorList = new ArrayList<>(); 41 for(int data :data1){ 42 divisorList.add(getDivisors(data)); 43 } 44 return getResult(divisorList); 45 } 46 /** 47 * 得到一个数的全部素数因子 48 * @param data 传入一个整数 49 * @return 返回素数因子 50 */ 51 private static List<Integer> getDivisors(int data) { 52 List<Integer> primeList = new ArrayList<>(); 53 int k = Math.abs(data); 54 int sum = 1; 55 if(!isPrime(data)){ //如果这个数是素数的话,直接返回 56 int len = (int)Math.sqrt(Math.abs(data)); 57 for(int i = 2; i <= len; i++){ //任何一个数都可以分解为素数因子的乘积 58 if(isPrime(i)){ 59 while(data % i == 0){ 60 sum *= i; 61 if(sum <= k){ 62 primeList.add(i); 63 } 64 data = data / i; 65 if(isPrime(data)){ 66 primeList.add(data); 67 sum *= data; 68 break; 69 } 70 } 71 if(sum == k) 72 break; 73 } 74 } 75 }else { 76 primeList.add(data); 77 } 78 return primeList; 79 } 80 81 private static boolean isPrime(int data) { 82 for(int i = 2;i <= Math.sqrt(Math.abs(data)); i++){ 83 if (data % i == 0) 84 return false; 85 } 86 return true; 87 } 88 89 /** 90 * 通过将各个数的素数因子传入该方法,得到最大公因数 91 * 实际就是求一组数的交集 92 * 如果这组数存在最大公因数,则最大公因数必定在素数因子最小的集合里 93 * @param divisorList 94 * @return 95 */ 96 private static int getResult(List<List<Integer>> divisorList) { 97 int result = 1; //存储最终结果,如果返回是1的话,则说明不存在最大公因数 98 for(int element :divisorList.get(0)){ 99 boolean flag = true; 100 for(int j = 1;j < divisorList.size();j++){ 101 if(!divisorList.get(j).contains(element)){ 102 flag = false; 103 break; 104 } 105 divisorList.get(j).remove(divisorList.get(j).indexOf(element)); 106 } 107 if (flag) { 108 result *= element; 109 } 110 } 111 112 113 114 // int k = divisorList.get(0).size(); 115 // int j = 0; //记录最小元素数的脚标 116 // for(int i = 1 ; i < divisorList.size(); i++){ 117 // if (divisorList.get(i).size() <= k) { 118 // k = divisorList.get(i).size(); 119 // j = i; 120 // } 121 // } 122 // 123 // for(List<Integer> list :divisorList){ 124 // divisorList.get(j).retainAll(list); 125 // } 126 // 127 // for (int i = 0; i <divisorList.get(j).size() ; i++) { 128 // System.out.println(divisorList.get(j).get(i)); 129 // 130 // } 131 // 132 // if(divisorList.get(j).size() != 0){ 133 // for (int r : divisorList.get(j)) { 134 // result = result * r; 135 // } 136 // } 137 return result; 138 } 139 }
这里两个较为重要的算法是
1、如何将一个整数分解为素数因子的乘积
2、如何找出若干个素数因子中共同的素数因子
对于第一个算法,对应的代码为
1 private static List<Integer> getDivisors(int data) { 2 List<Integer> primeList = new ArrayList<>(); 3 int k = Math.abs(data); 4 int sum = 1; 5 if(!isPrime(data)){ //如果这个数是素数的话,直接返回 6 int len = (int)Math.sqrt(Math.abs(data)); 7 for(int i = 2; i <= len; i++){ //任何一个数都可以分解为素数因子的乘积 8 if(isPrime(i)){ 9 while(data % i == 0){ 10 sum *= i; 11 if(sum <= k){ 12 primeList.add(i); 13 } 14 data = data / i; 15 if(isPrime(data)){ 16 primeList.add(data); 17 sum *= data; 18 break; 19 } 20 } 21 if(sum == k) 22 break; 23 } 24 } 25 }else { 26 primeList.add(data); 27 } 28 return primeList; 29 }
思路为:先判断传入的数据是否为素数,如果是素数直接加到list中返回即可,否则
从素数2开始分解,如果能整除2,则说明2是一个素因子,继续整除2,如果不能整除了先要判断这个已经是不是一个素数了,如果是的话就要加到list里,这是非常容易忽略的地方,我也是用26这个数据做测试的时候才发现(26 = 2* 13)在判断过程中,要将素数因子乘积存到sum变量里,因为当sum = data时就应该已经分解完成了。
对于第二个算法,代码如下
1 private static int getResult(List<List<Integer>> divisorList) { 2 int result = 1; //存储最终结果,如果返回是1的话,则说明不存在最大公因数 3 for(int element :divisorList.get(0)){ 4 boolean flag = true; 5 for(int j = 1;j < divisorList.size();j++){ 6 if(!divisorList.get(j).contains(element)){ 7 flag = false; 8 break; 9 } 10 divisorList.get(j).remove(divisorList.get(j).indexOf(element)); 11 } 12 if (flag) { 13 result *= element; 14 } 15 } 16 17 return result; 18 }
将素数因子集合作为参数传进来,以第一个list中的元素作为基(最大公因数的素数因子必定存在于任何一个整数的素数因子集合中,一开始我想的是先找出最小size集合再去判断,其实没必要这么做,平均时间复杂度比这个要高(特殊极端情况除外)),依次取出来,如果存在于剩下的每一个list中,说明这个元素是公共的,则将结果保存于result中,一旦哪个list中不存在,则说明不是公共的,这里的flag标记是判断是否存在于每个list中的一个开关,只有遍历完剩下的list,才能加到result的结果中。需要注意的是每一次从一个list中找出一个公共的素数因子,需要将这个元素从list中删除,并且是删除最先出现的。这里有个地方需要注意的是我们一开始就是从最小的素数因子存入的,所以每个list中的因子都是从小到大排好序的。
关键知识点就是这些,这个程序虽然简单,但是却花了我将近3个小时的思考才写出来,可见我的数学和算法知识还有进步的地方,需要多加努力,多写代码!