Processing math: 100%

2016年7月23日土曜日

ユークリッドの互除法による約分プログラム


問題
次の分数は約分できますか。できるのであれば、約分してもとっとも簡単な分数で表しなさい
\frac{10033}{12877}
ユークリッドやなーって感じの問題が数学検定1級の練習問題で出てきた
そういえば
ユーグリットの互除法は、プログラムだと再起関数の例題でよく見かけるが、実際組んだことなかったので
プログラムでこの問題を解いてみることにする

ユークリッドの互除法とは

自然数abに対し、abで割ったあまりをrとおくと、下記等式が成り立つ gcm(a,b)=gcm(b,r) gcm(n1,n2)とはn1n2の最大公約数を意味する


これで何で最大公約数が簡単に求まるのか。実際に本問題の最大公約数を求めてみる
gcm(12877,10033)=gcm(10033,2844)(\because12877\div10033 = 1\times 10033 + 2844)
gcm(10033,2844)=gcm(2844,1501)(\because10033\div2844 = 3\times 2844 + 1501)
gcm(2844,1501)=gcm(1501,1343)(\because2844\div1501 = 1\times 1501 + 1343)
gcm(1501,1343)=gcm(1343,158)(\because1501\div1343 = 1\times 1343 + 158)
gcm(1343,158)=gcm(158,79)(\because1343\div158 = 8\times 158 + 79)
gcm(158,79)\ne gcm(79,0)(\because158\div79 = 2\times 79 + 0)
※最後が\neなのは「0」は自然数に含まれないためイコールにならない

繰り返すごとに数字が小さくなっているのがわかる
これは割ったあまりを次の最大公約数を求めるために使うから、一般的な割り算の考え方(割られる数>割る数>あまり)より
繰り返すたびに数字が小さくなっていく

実装

再帰関数が使えるプログラム言語なら超簡単。2つの引数の最大公約数を求める関数はこんな感じ
  1. /**
  2. * 2つの引数の最大公約数(gcm)をユーグリットの互除法で求める.
  3. * @param n1 対象の数字1
  4. * @param n2 対象の数字2
  5. * @return ret 最大公約数
  6. */
  7. int gcm(int n1, int n2) {
  8. int ret = n1 % n2;
  9. return (ret == 0) ? n2 : gcm(n2, ret);
  10. }
この関数を使って本問題を解くと
  1. /**
  2. * 約分問題を解くプログラム
  3. * gcm.cpp
  4. */
  5. #include<iostream>
  6. #include<cmath>
  7.  
  8. /** 分子. */
  9. #define NUMERATOR 10033
  10. /** 分母. */
  11. #define DENOMINATOR 12877
  12.  
  13. int gcm(int, int); // 最大公約数を求める関数(内容省略)
  14.  
  15. int main(){
  16. int numerator = NUMERATOR; // 分子
  17. int denominator = DENOMINATOR; // 分母
  18. int gcm_num = gcm(numerator, denominator); // 最大公約数
  19.  
  20. if(gcm_num == 1) {
  21. // 約分できない場合
  22. // 最大公約数「1」は互いに素の関係だから約分できない
  23. std::cout << " 約分できません " << std::endl;
  24.  
  25. } else {
  26. // 約分できる場合
  27. // 約分する
  28. numerator /= gcm_num;
  29. denominator /= gcm_num;
  30.  
  31. // 分母、分子間の線の長さを計算(対数を使って桁数取得)
  32. int line = (int)log10(fmax(numerator, denominator)) + 1;
  33.  
  34. // 解表示
  35. std::cout << numerator << std::endl;
  36. for(int i = 0; i < line; i++) std::cout << "-";
  37. std::cout << std::endl << denominator << std::endl;
  38. }
  39. return 0;
  40. }

実行結果

$ g++ gcm.cpp
$ ./a.out
127
---
163
約分できることがわかった
ハイライトの箇所(31,32行目)はちょっと特殊な計算で線の長さを求めてる(対数による桁数計算方法)
これについては本問題と関係ないし、また後日説明しようかな

0 件のコメント:

コメントを投稿