gcd函数(最大公约数)
前言
身为小白的我对此基础知识不牢,所以写一篇文章来记录并巩固一下。最大公约数的逻辑可以运用于很多类似辗转逻辑的操作,是一个很好的模板,需要掌握牢固。后来我发现对于辗转相除法的概念好像很模糊,于是看了一些文章和视频,并结合自己的想法,写下来我的见解。
一、最大公约数是什么?
定义:
最大公约数是指两个或多个整数共有的约数中最大的一个。
字面意思:最大的公共约数(两者都有的约数(或称因数),约数是指能够整除给定数的数,比如12 除 6 能够整除,则6为12的约数)。
二、辗转相除法
1.什么是辗转相除法?
辗转相除法,也称欧几里得算法。这个算法基于一个基本的数学原理:两个整数的最大公约数等于其中较小的数和两数相除余数的最大公约数。
2.举例分析
求14和6的最大公约数。
很明显1、2都是它们的公约数,最大公约数为2,进一步理解公约数:
(1)14可以视为14个1,6可以视为6个1,我们把1视作一个小正方形的边长,则由此可以形成一个矩形:
那么小正方形的边长相当于14和6的公约数。
(2)14可以视为7个2,6可以视为3个2,如图示:
2表示的小正方形的边长,同时2为14和6的最大公约数,那意味着在14×6的矩形中,能用边长最大为2的正方形填满这个矩阵。
注意:矩形里面的小正方形边长都一样,本质就是小正方形的边长为公约数,对于14来说,7×2 = 14,所以是7个边长为2的小正方形。
我们可以将求14和6的最大公约数问题转化为边长最大的相同正方形来填满14×6的矩形。
分析:
对于14×6矩阵,先用边长为6的正方形来填充,最多填两个边长为6的小正方形(14/6 = 2……2),还有余数2,继续以2为小正方形边长进行填充。依次类推直到填满这个矩阵,最后的小正方形的边长就为最大公约数。
问题:
(1)为什么要先以6作为小正方形的边长呢?
我们要求14和6和最大公约数,小正方形的边长表示的就是14和6的公约数,由图可以看出,14×6最大容纳的正方形边长为两个数中较小的数。
(2)当14/6还余2时,为什么要以2为小正方形边长呢?
当14/6还余有数时,说明6不是最大公约数并且最大公约数比6小,这时可能会有疑惑:比6小的数有5,4,3,2,1,为什么要以2为小正方形的边长继续下去呢。
此刻我们要明确约数的定义,约数:是指能够整除给定数的数。那么我们应该将14表示成一个数×公约数的式子,6也是要表示成一个数×公约数的式子。而上面的式子还多了2,那就要把2融进6的部分,也就是要把6表示成与2有关的式子,这样2就能加进去合并起来,成为一条因式,而以2为边长的正方形是剩余空间中边长最大正方形(因为我们要求的是最大公约数),并且只需要3个这个的正方形便能填满14×6矩阵,则6表示与2有关的式子由图可以看出是6 = 2×3,则14 = 2×3×2 + 2 = 7×2,并且6 = 2×3,所以最大公约数为2。
总结:
求两个数a和b的最大公约数,就是求当余数为0(此时a×b矩形被填满了),那么最后一个正方形的边长,就是最大公约数。
3.实现步骤
1.给定两个整数a,b,规定a >b
2.操作: r = a%b;a = b; b = r;
3.如果b为0,本质上b此时的值代表它们余数的值,如果余数为0,则除尽了,则a为最大公约数,因为此时a的值本质是b的值。// 3其实就是个判断操作
4.如果b不为0,重复进行2,3操作。
4.代码实现
int gcd(int a, int b) {
if(b > a) {// 保证a是大数,b是小数
a ^= b;// 交换两数的值
b ^= a;
a ^= b;
}
while(b > 0) {// 终止条件就是b == 0
int r = a % b;
a = b;
b = r;
}
return a;
}
三、小试牛刀
连分数
题目描述 连分数是一种表达式:
其中 a0,a1,…,an是非负整数。
给定一个分数 xy( x,y是正整数),请将它展开成一个带分数。
输入
第一行包含一个整数 T ( 1≤T≤103),表示测试用例的数量。 每个测试用例的唯一一行包含两个整x,y(1≤x,y≤109),表示分数 xy。 可以保证 gcd(x,y)=1。
输出
对于每个测试用例,输出一行:首先是表示续分数高度的整数 n,然后是表示 a0,…,an的整数 n+1 。
您的解决方案应保证 0≤n≤100, 0≤ai≤109. 如果有多个有效解,只需输出其中一个即可。
样例
题解:
仔细观察样例我们发现(虽然不知道连分数的定义,但可以通过看示例找思路):
(1)105/38 = 2 + XXX,则XXX相当于:105%38 = 29比38。
(2)分子分母倒过来(÷1), 为38/29,继续(1)的操作。
(3)停止的条件,到最后我们发现,当分子为1时,我们进行第二步操作,分母会得到一个整数,此时就不需要进行操作了,这就是结束的条件:分子为1。
代码实现:
void solve() {
int x, y;
cin >> x >> y;
vector<int> a;
while(y > 1) {// 终止条件为y == 1
int r = x%y;
a.push_back(x/y);
x = y;
y = r;
}
a.push_back(x);
int len = a.size();
cout << len - 1 << " ";
for(int i = 0; i < len; i++) {
cout << a[i] << " ";
}
cout << endl;
}
可以看到运用的逻辑都是相似的。
注意:这里不用规定x和y哪个是大数,因为这个x/y的比值会跟着你的转变而改变,这样就会错误。
结语
gcd的逻辑可以适用于很多其他问题的处理,也就是辗转相除的逻辑,深刻掌握后可以运用到更多类似问题的解决。