京东2017校招笔试编程题2:进制转换、辗转相除法

如下一些内容大部分来自于维基百科:

辗转相除法,又被称为欧几里德(Euclidean)算法, 是求最大公约数的算法。辗转相除法首次出现于欧几里得的《几何原本》(第VII卷,命题i和ii)中,而在中国则可以追溯至东汉出现的《九章算术》。

两个数的最大公约数是指能同时整除它们的最大正整数。辗转相除法的基本原理是:两个数的最大公约数等于它们中较小的数和两数之差的最大公约数。例如,252和105的最大公约数是21(252 = 21 × 12;105 = 21 × 5);因为252 − 105 = 147,所以147和105的最大公约数也是21。在这个过程中,较大的数缩小了,所以继续进行同样的计算可以不断缩小这两个数直至其中一个变成零。这时,所剩下的还没有变成零的数就是两数的最大公约数。由辗转相除法也可以推出,两数的最大公约数可以用两数的整数倍相加来表示,如21 = 5 × 105 + (−2) × 252。这个重要的等式叫做贝祖等式。

辗转相除法法原先只用来处理自然数,但在19世纪,辗转相除法被推广至其他类型的数,如高斯整数和一元多项式。另外,还被用来解决丢番图方程和构造连分数等。

两个数a,b的最大公约数记为GCD(a,b)。a,b的最大公约数是两个数的公共素因子的乘积。如462可以分解成2 × 3 × 7 × 11;1071可以分解成3 × 3 × 7 × 17。462和1071的最大公约数等于它们共有的素因数的乘积3 × 7 = 21。如果两数没有公共的素因数,那么它们的最大公约数是1,也即这两个数互素,即GCD(a,b)=1。另g=GCD(a,b),则a=mg, b=ng,其中m,n均为正整数。由上述分析可知,m,n互素。因为m,n没有公共素因子,GCD(m,n)=1。

辗转相除法是一种递归算法。设k表示步骤数(从0开始计数),算法的计算过程如下。每一步的输入是都是前两次计算的余数rk−1和rk−2。因为每一步计算出的余数都在不断减小,所以,rk−1小于rk−2。在第k步中,算法计算出满足以下等式的商qk和余数 rk:

rk−2 = qk rk−1 + rk

其中rk < rk−1。也就是rk−2要不断减去rk−1直到比rk−1小。

在第一步计算时(k = 0),设r−2和r−1分别等于a和b,第2步(此时k = 1)时计算r−1(即b)和r0(第一步计算产生的余数)相除产生的商和余数,以此类推。整个算法可以用如下等式表示:

a = q0 b + r0
b = q1 r0 + r1
r0 = q2 r1 + r2
r1 = q3 r2 + r3

如果输入参数a小于b,则第一步计算的结果是交换两个变量的值:因为a < b,所以a和b相除得到的商q0等于0,余数r0等于a。所以在运算的每一步中得出的余数一定小于上一步计算的余数(rk一定小于rk−1)。由于每一步的余数都在减小并且不为负数,必然存在第N步时rN等于0,使算法终止,rN−1 就是a和b的最大公约数。其中N不可能无穷大,因为在r0和0之间只有有限个自然数。

辗转相除法的正确性可以用两步来证明。首先,算法的最终结果rN−1同时整除a和b。因为它是一个公约数,所以必然小于或者等于最大公约数g。然后,任何a和b的公约数都能整除rN−1。所以g一定小于或等于rN−1。两个不等式只在rN−1 = g是同时成立。

算法实现:

递归:

function gcd(a, b)
  if a<b
    swap(a,b);  
  if b==0
    then return a;
  else
    return gcd(b, a mod b);
end
循环:

function gcd(a,b)
  if a<b
    then swap(a,b);
  while(b!=0)
  {
    c = a mod b;
    a = b;
    b = c;
  }
  return a;
end

实例:

求一个数X按照2到X-1进制表达,各个位数之和的平均值是多少?所有计算基于十进制进行,结果也用十进制表示为不可约简的分数形式。例如,输入5,求出2、3、4进制的5,将各进制的数每个位加起来,除以3,并且约到最简形式,其中我们用到了辗转相除法,输出7/3.

#include<stdio.h>
#include<stdlib.h>
#include<vector>
#include<iostream>
using namespace std;

int gcd(int a, int b){
	if (a < b)
		swap(a, b);
	if (b == 0)
		return a;
	else return gcd(b, a % b);
}

int convert(int u, int v){   //数u,进制v
	int p = 0;
	vector<int> vec;
	while(u){
		vec.push_back(u % v);
		u /= v;
	}
	for(int i = vec.size() - 1; i >= 0; --i){
		p += vec[i];
	}
	return p;
}
int main(){
	int A;
	cin>>A;
	int sum = 0;
	for(int i = 2; i < A; ++i){
		sum += convert(A,i);
	}
	int k = A - 2;
	if(sum % k == 0) return sum / k;
	else{
		int g = gcd(sum,k);
		int p = sum / g, q = k / g;
		std::cout<<p<<'/'<<q;
	}
	system("pause");
	return 0;
}








  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

fullstack_lth

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

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

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

打赏作者

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

抵扣说明:

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

余额充值