C言語のプログラミング入門 2014 練習問題略解 2章 例題 2.1 int a,b; double c; scanf("%d %d", &a, &b); c = a; printf("%d / %d^3 = %lfUn", a, b, c/(b*b*b)); // あるいは printf("%d / %d^3 = %lfUn", a, b, (double)a/(b*b*b)); 例題 2.2 int a, b, c; scanf("%d %d %d", &a, &b, &c); printf("平均値は %.3lfUn", (a+b+c)/3.0); 例題 2.3 double x, y; int m; scanf("%lf %lf", &x, &y); m = (int)(x / y); printf("%lf / %lf = %d + %lfUn", x, y, m, x/y-m); 例題 2.4 int a, b, c, d; d = 100*a + 10*b + c; printf("%d + %d + %d = %d, x2 = %dfUn", a, b, c, d, 2*d); 例題 2.5 int a, a10, a100; printf("4 桁の整数 "); scanf("%d", &a); printf("%d の逆順は", a); a1 = a % 10; a = a / 10; a10 = a % 10; a = a / 10; a100 = a % 10; a = a / 10; printf(" %d%d%d%dfUn", a1, a10, a100, a); 1 例題 2.6 int h, m, s, ss; printf("時、分、秒 "); scanf("%d %d %d", &h, &m, &s); ss = 360*h + 60*m + s; printf("%d 時間 %d 分 %d 秒は %d 秒、それは", h, m, s, ss); s = ss % 60; m = (ss / 60) % 60; h = ss / 3600; printf(" %d 時間 %d 分 %d 秒Un", h, m, s); 練習 2.1 実行結果 (1)「a = 1, b = 20; e = 20, d = 1」 (2) 「e = 1, d = 20; a = 20, b = 1」 練習 2.2 略 練習 2.3 ヘロンの公式 #include <math.h> double a, b, c, s; printf("三辺の長さ "); scanf("%lf %lf %lf", &a, &b, &c); s = (a+b+c) / 2; printf("三角形の面積は %lfUn", sqrt(s*(s-a)*s-b)*(s-c))); 練習 2.4 算術平均、幾何平均、調和平均 #include <math.h> double x, y, z; printf("三つの数 "); scanf("%lf %lf %lf", &x, &y, &z); printf("%lf, %lf, %lf の算術平均は %lf、幾何平均は %lf、", (x+y+z)/3, pow(x*y*z, 1.0/3)); printf("調和平均は %lfUn", 3/(1/x+1/y+1/z)); 練習 2.5 べき乗の計算 #include <math.h> double x, y; printf("二つの数 "); scanf("%lf %lf", &x, &y); printf("%lf^%lf を pow で計算すると %lf、exp-log で計算すると %lf、", pow(x,y), exp(y*log(x))); 2 問題 2.1 四捨五入 double a, b; printf("数(小数点以下 4 桁以上) scanf("%lf", &a); b = (int)(a*100) * 0.01 printf("%lf ~ %lfUn", a, b); "); 問題 2.2 四捨五入 double a, b; printf("2 数 "); scanf("%lf %lf", &a, &b); printf("%lf / %lf の整数部分は %dUn", a, b, (int)(a/b)); 問題 2.3 BMI double x, y; printf("身長と体重 "); scanf("%lf %lf", &x, &y); printf("身長 %lf センチ、体重 %lf キロの人の BMI は %.1lfUn", x, y, y/x/x*10000); 問題 2.4 マラソンのラップタイム double kilo, speed; int lapm, laps, h, m, s, goal; printf("通過距離と最新 5 キロの所要時間(分と秒) "); scanf("%lf %d %d", &kilo, &lapm, &laps); printf("スタートからの経過時間(時、分、秒) "); scanf("%d %d %d", &h, &m, &s); speed = (60*lapm + laps) / 5; goal = (int)((3600*h + 60*m + s) + (42.195-kilo) * speed + 0.5); printf("ゴ ー ル 予 想 タ イ ム は %d 時 間 %d 分 %d 秒 で すUn", goal/3600, (goal/60)%60, goal%60); 問題 2.5 学籍番号のチェックディジット int year, id, m1, m2, m3, m4, m5, m6, M, K; printf("入学年度と通し番号 "); scanf("%d %d", &year, &id); m2 = year%10; m1 = 7; m3 = 8; m4= id/100; m5 = (id/10)%10; m6 = id%10; 3 M = 2*m1 + 3*m2 + 4*m3 + 5*m4 + 6*m5 + 7*m6; K = M%11; printf("入学年度 %d、通し番号 %d の人のチェックディジット付き", year, id); printf("学籍番号は 1X%02dC%03d-%dUn", year%100, id, (11-K)%10); 4 3章 練習 3.1 条件式 (1) x==0 || x==1 (2) z !=0 (3) a>1 || a<-1 または a*a>1 x<=2 (6) x-y==1 || x-y==-1 または (4) a!=0 && b!=0 (5) 0 <=x && (x-y)*(x-y)==1 (7) x==y && y==z (8) x>y && y>z (9) x>y && x>z (10) x*y<0 練習 3.2 max {x, 0} if(x<0) x = 0; 練習 3.3 試験の評点 int ten; printf("試験の点数 "); scanf("%d", &ten); if(ten >= 90) printf("点数は %d 点、評価は A ですUn", ten); else if(ten >= 80) printf("点数は %d 点、評価は B ですUn", ten); else if(ten >= 70) printf("点数は %d 点、評価は C ですUn", ten); else if(ten >= 60) printf("点数は %d 点、評価は D ですUn", ten); else printf("点数は %d 点、評価は F ですUn", ten); 練習 3.4 一次方程式 double a, b; printf("一次方程式の係数(ax + b = 0) "); scanf("%lf %lf", &a, &b); if(a != 0) printf("方程式の解は %lfUn", -b/a); else if(b == 0) printf("方程式は不定Un"); else printf("方程式は不能Un"); 練習 3.5 うるう年 int year, leap=0; printf("西暦年号 "); scanf("%d", &year); if(year%4 == 0) { leap = 1; if(year%100 == 0 && year%400 != 0) leap = 0; } if(leap == 1) printf("%d 年はうるう年ですUn", year); else printf("%d 年はうるう年ではありませんUn", year); 5 練習 3.6 switch 文の書き換え if(k == 0) { printf("それはあんまりだ! (k=%d)Un", k); } else if(k==1 || k==2) { printf("継続は力なり! (k=%d)Un", k); } else { printf("運動もしていますか? (k=%d)Un", k); } 練習 3.7 入力された数の合計 int x, sum=0; do { printf("整数 "); scanf("%d", &x); if(x != 0) sum += s; else { printf("前回 0 入力からの合計は %d でしたUn", sum); sum = 0; } } while(1); 練習 3.8 数の比較 double a, b; do { printf("2数 "); scanf("%lf %lf", &a, &b); if(a == b) break; if(a>b) printf("%lf は %lf より大きいUn", a, b); else printf("%lf は %lf より大きいUn", b, a); } while(1); printf("%lf と %lf は等しいUn", b, a); 練習 3.9 さいころ数列 int n; n = 100; printf("さいころの目:Un"); while(n > 0) { printf(" %d", 6*rand()/(RAND MAX+1)); n = n-1; } printf("Un"); 6 練習 3.10 賭けの記録 int sum, n, coin; sum = 0; n = 0; do{ n = n+1; coin = 2*rand()/RAND MAX+1); if(2*coin-1 == 1) { // if(coin == 1) でも同じ sum = sum+1; printf("%d 回目の勝負は勝ちUn", n); } else { sum = sum-1; printf("%d 回目の勝負は負けUn", n); } } while(n < 10); if(sum = 0) printf("5 勝 5 敗の引き分けでしたUn"); else if(sum < 0) printf("%d 勝 %d 敗の負け越しでしたUn", (10+sum)/2, (10-sum)/2); else printf("%d 勝 %d 敗の勝ち越しでしたUn", (10+sum)/2, (10-sum)/2); 問題 3.1 2 次方程式を解く double a, b, c, D; printf("3 つの係数 (ax^2+bx+c=0) "); scanf("%lf %lf %lf", &a, &b, &c); if(a != 0) { D = b*b - 4*a*c; if(D > 0) { printf("2 実解:%lf, %lfUn", -(b+sqrt(D))/2/a, -(b-sqrt(D))/2/a); } else if(D == 0) { printf("重解:%lfUn", -b/2/a); } else printf("共役複素解:%lf%lfiUn", -b/2/a, sqrt(-D)/2/a); } else { if(b != 0) { printf("一次方程式で解:%lfUn", -c/b); } else { if(c != 0) printf("解は存在しない(不能)Un"); else printf("何でも解(不定)Un"); } } 問題 3.2 suica の精算 int zan, fee; while(1) { printf("残額と運賃 "); 7 zan); scanf("%d %d", &zan, &fee); if(fee > zan) { printf("運賃は %d 円、カード残額は %d 円、料金不足です、精算してくださいUn", fee, } else if(zan >= 1000 && zan-fee < 1000) { printf("運賃は %d 円、カード残額は %d 円、1000 円未満になりましたUn", fee, zan-fee); } else printf("運賃は %d 円、カード残額は %d 円Un", fee, zan-fee); } 問題 3.3 自動販売機のつり銭 1. データ入力(金種ごとに入力させる、品物は 2 種に限定)エラーチェック 2. 残額を計算 3. つり銭を最小枚数にするために、大きい金種から、枚数を計算する int item, pay, change, m1000, m500, m100, m50, m10; while(1) { do{ // データ入力、入力エラーチェック(金額不足、指定エラー) printf("入金額 (1000 円,500 円,100 円,50 円,10 円の枚数(5 つの数字) "); scanf("%d %d %d %d %d", &m1000, &m500, &m100, &m50, &m10); pay = 1000*m1000 + 500*m500 + 100*m100 + 50*m50 + 10*m10; do { printf("品物 (1:120, 2:150) "); scanf("%d", &item); if(item == 1 || item == 2) break; printf("1か2だけです、入力し直し: "); } while(1); if(item == 1 && pay < 120 || item == 2 && pay < 150) { printf("金額が足りません、お返ししますのでやり直してください:"); continue; } break; } while(1); // 釣り銭の金種計算 change = pay - 120; if(item == 2) change -= 30; m1000 = change/1000; change -= 1000*m1000; m500 = change/500; change -= 500*m500; m100 = change/100; change -= 100*m100; m50 = change/50; change -= 50*m50; m10 = change/10; change -= 10*m10; printf("おつりは "); if(m1000 > 0) printf("1000 円札 %d 枚、", m1000); if( m500 > 0) printf("500 円玉 %d 枚、", m500); if( m100 > 0) printf("100 円玉 %d 枚、", m100); 8 } if( m50 > 0) printf("50 円玉 %d 枚、", m50); if( m10 > 0) printf("10 円玉 %d 枚、", m10); printf(" です。ありがとうございました。Un"); 問題 3.4 駐車場の料金 1. 入庫日時分から出庫日時分までの滞在時間を入力、エラーチェック 2. 30 分単位で切り上げたものを計算し 3. 3 時間半までは 30 分がいくつあるか数えて 260 倍 4. 1 日以上の場合は、端数を切り上げ計算して、5 日以下とそれを超える場合に分けて計算 int day1, day2, hour1, hour2, min1, min2, day, fee; double stay; while(1) { do{ // データ入力、入力エラーチェック printf("入庫した日と時刻(時間と分) "); scanf("%d %d %d", &day1, &hour1, &min1); printf("現在の日と時刻(時間と分) "); scanf("%d %d %d", &day2, &hour2, &min2); stay = 2*(hour2-hour1) + (min2-min1)/30.0 + (day2-day1)*24*2; if(stay != (int)stay) stay = (int)stay+1; // 30 分単位で切り上げ stay = stay/2; if(stay > 0) break; printf("入力が間違っています。入力し直してくださいUn"); } while(1); // 時間帯ごとに料金計算(3.5 時間まで、1 日まで、5 日まで、...) if(stay <= 3.5) { fee = (int)(stay / 0.5) * 260; } else if(stay <= 24) { fee = 2060; } else if(stay <= 120) { day = (int)(stay / 24); if(stay/24 != day) day = day+1; // 24 時間単位で切り上げ fee = 2060 * day; } else { day = (int)(stay / 24); if(stay/24 != day) day = day+1; // 24 時間単位で切り上げ fee = 10300 + 520 * (day-5); } day = (int)(stay / 24); printf("%d 日 %.1lf 時間の駐車料金は %d 円です。", day, stay-24*day, fee); printf(" ありがとうございました。Un"); } 9 問題 3.5 西暦年月日の曜日計算 1. 入力月日から、平年だとして、通算日数を計算する 2. 入力年がうるう年で、指定月が 3 月以降ならば、閏日を通算日数に加算 3. 1601 年元旦を基準日とすると、入力年元旦の曜日のずれは、基準年から前年までの年数と閏年数の合 計を 7 で割ったあまりである、ということを使って、閏年数を計算する。 4. 前年までの年数と閏年数の合計に、指定日の同年元旦からの通算日数を加えたもの int year, mon, day, years, days, weekday, leap; while(1) { printf("西暦年月日(1601 年以降) "); scanf("%d %d %d", &year, &mon, &day); // その年の通算日数の計算 days = day; switch(mon) { case 12: days = days + 30; case 11: days = days + 31; case 10: days = days + 30; case 9: days = days + 31; case 8: days = days + 31; case 7: days = days + 30; case 6: days = days + 31; case 5: days = days + 30; case 4: days = days + 31; case 3: days = days + 28; case 2: days = days + 31; } // その年がうるう年で 3 月以降ならば閏日を算入 leap = 0; if(year%400 == 0) leap = 1; else if(year%4 == 0 && year%100 != 0) leap = 1; if(leap == 1 && mon> 2) days = days + 1; // 1601 年元旦を基準に、前年までの閏年数を数える years = year - 1601; years = years + years/4 - years/100 + years/400; // 1601/1/1 からの経過日数を 7 で割ったあまりを計算 weekday = (years + days) % 7; switch(weekday) { case 0: printf("日曜日ですUn"); break; case 1: printf("月曜日ですUn"); break; case 2: printf("火曜日ですUn"); break; case 3: printf("水曜日ですUn"); break; case 4: printf("木曜日ですUn"); break; case 5: printf("金曜日ですUn"); break; case 6: printf("土曜日ですUn"); break; } } 10 4章 練習 4.1 西暦年月日の曜日計算のアルゴリズム 全体の方針: • 基準日からの通算日数を 7 で割ったあまりを使えば、指定日の曜日は基準日の曜日から計算することが できる。 • 基準日と指定日の間に挟まれる 1 年は、曜日計算だけならば 1 日(平年の場合)あるいは 2 日(うるう 年の場合)しかないものと考えても良い。 大方針では条件分岐はないので、仕事のまとまりを長方形で囲み、順番を付けるだけ。 • 指定年の元旦と基準年の元旦の曜日のずれを計算するために、その間に含まれる閏年の数を a、指定年 までの年数を b とする。a + b を 7 で割ったあまりが「ずれ」を表す。 • 指定年の元旦からの通算日数 c は、平年の日数を数え、3 月以降でうるう年ならば 1 を加算する、とい うことで計算できる • a + b + c を 7 で割ったあまりが基準日の曜日とのずれである。 アルゴリズム 1. 西暦年 year 月 mon 日 day を入力する 2.(年内の通算日数の計算)平年のつもりで、その年の元旦から指定日までの通算日数を days とする。 days は mon=1,2,3,... に応じて、0,31,59,... に day を加えたものとして計算できる。 3.(うるう年の判定)year が 400 の倍数ならば leap=1、さもなければ、year が 4 で割り切れ、100 で割 り切れなければ leap=1、さもなければ leap=0 とする。 4. days に leap を加えたものを新たに days とする。 5.(基準年から前年までのうるう年の回数)year-1601 を years とする。years に含まれる 4 の倍数 (years/4)から 100 の倍数(years/100)を引き、400 の倍数(years/400) を足したものを leaps とす ると、leaps はうるう年の回数になる。 6. days と years と leaps を足したものを 7 で割ったあまりが 0 ならば日曜日、1 ならば月曜日、. . . で ある。 練習 4.2 つり銭計算のアルゴリズム 1. 金種ごとの支払い枚数を入力する(m1000 , m500 , m100 , m50 , m10 とする)。 2. 商品番号 item を入力する(1:120 円、2:150 円)。 3.(支払い金額の計算)1000m1000 + 500m500 + 100m100 + 50m50 + 10m10 を pay に代入する。 4.(エラーチェック)item=1 で pay<120 または、item=2 で pay<150 ならば再入力を促す。 5.(つり銭金額)pay から商品金額を引いたものを change に代入する。 6.(つり銭の金種計算)change/1000 の整数部分を m1000 とし、change から 1000m1000 を引いたものを 新たな change とする。同様にして、順番に m500 , m100 , m50 , m10 を計算する。 11 練習 4.3 約数のリストアップ 結果がポツポツと表示されるようになる。 b = 2; while(b*2 <= a) { if(a%b == 0) printf(" %d", b); b = b+1; } 練習 4.4 7 桁の最小の素数 実習のプログラムで、データを入力する代わりに do ... while 構文の中で a=1000001 から順番に素数 かどうかをチェックし、見つかったところで break すればよい。7 桁の最小の素数は 1000003。 a = 1000001; while(1) { b = 2; while(b*b <= a) { if(a%b == 0) { printf("%d は素数ではないUn", a); break; } b = b+1; } if(b*b > a) { printf("%d は素数Un", a); break; } a = a + 2; } 練習 4.5 アルゴリズム 3 if(a == 2 || a == 3) { printf("%d は素数Un", a); continue; } if(a%6 != 1 && a%6 != 5) { printf("%d は素数ではないUn", a); continue; } b = 5; while(b*b <= a) { if(a%b == 0) { printf("%d は素数ではないUn", a); break; } 12 b = b+2; } if(b*b > a) printf("%d は素数Un", a); 練習 4.6 10000 以上で最小の双子の素数 printf("下限の数 ? "); scanf("%d", &m); a = (m/6)*6 + 5; do{ b = 5; while(b*b <= a+2) { if(a%b == 0 || (a+2)%b == 0) break; b = b+2; } if(b*b > a+2) { printf("%d と %d は双子の素数Un", a, a+2); break; } a = a + 6; } while(1); 練習 4.7 証明問題 a の最小素因数を b とすると、a/b の最小素因数は b 以上である。なぜならば、a/b の最小素因数が b より 小さければ、a = b × a b は b より小さい素因数を持つことになり、b の定義に矛盾する。 練習 4.8 省略 練習 4.9 同じ素因数はべきの形で表示 b = 2; k = 0; while(b*b <= a) { if(a%b == 0) { do{ k = k+1; a = a/b; } while(a%b == 0); if(k > 0) { if(k > 1) printf(" %d^%d", b, k); else printf(" %d", b); k = 0; } } b = b+1; } 13 if(a > 1) printf(" %d", a); printf("Un"); 「b = b+1;」の代わりに「if(b == 2) b = b+1; else b = b+2;」とすればよい。 練習 4.10 3 数の最大公約数 int a,b,c,d; while(1) { printf("3 数 "); scanf("%d %d %d", &a, &b, &c); printf("%d と %d の最大公約数は ", a, b); do { d = a % b; if(d == 0) break; a = b; b = d; } while(1); printf("%d です。%d と %d の最大公約数は ", b, b, c); do { d = c % b; if(d == 0) break; c = b; b = d; } while(1); printf("%d です。これが答え。Un", b); } 練習 4.11 2 数の最小公倍数 int a,b,c,d; while(1) { printf("2 数 "); scanf("%d %d", &a, &b); printf("%d と %d の最大公約数は ", a, b); c = a * b; do { d = a % b; if(d == 0) break; a = b; b = d; } while(1); printf("%d、最大公約数は %d です。Un", b, c/b); } 練習 4.12 省略 14 練習 4.13 漸化式その1 xn = xn−1 + (−1)n−1 n int n,s,b,k; while(1) { printf("数 "); scanf("%d", &n); s = 0; b = 1; k = 1; while(k <= n) { s = s + b * k; b = -b; k = k + 1; } printf("%d 項までの和は %d ですUn", n, s); } 練習 4.14 漸化式その 2 zn /zn−1 ≈ 1 となりそうなので、zn の式で zn = zn−1 = z とすると、z(1 + z) − 1 = 0 という方程式が得 √ ) ( られる。これを解くと、z = −1 + 5 /2 = 0.618 が収束値。 double z, w; int n; z = 1; n = 1; do { w = 1 / (1+z); printf("z(%d) = %lf, z(%d)/z(%d) = %lfUn", n+1, w, n+1, n, w/z); z = w; n = n+1; } while(n <= 20); 練習 4.15 8191n mod 10000 下 2 桁には完全な規則性があるが、上位桁はランダムに変動しているように見える。 int a=8191, M=10000, x, n; x = a; n = 1; do { printf("%6d", x); if(n%10 == 0) printf("Un"); 15 x = x*a % M; n = n+1; } while(n <= 100); 問題 4.1 フィボナッチ数列 xn = xn−1 + xn−2 なので、xn は xn−1 の 1 点何倍くらいになりそう。実際 xn /xn−1 を計算してみると 1.6 倍くらいなので、x40 ≈ 1.640 ≈ 108 くらいか? int n, f0, f1, f, m; printf("項の数 "); scanf("%d", &n); f0 = 1; f1 = 1; m = 1; do { m = m+1; f = f0 + f1; f0 = f1; f1 = f; printf(" %3d: %10d", m, f); } while(m < n); 問題 4.2 フィボナッチ数列の性質 xn × xn+1 は n = 40 くらいだとオーバーフローするので、double 型で計算すると良い。 int n, f0, f1, f, m, sum; double sum2; n = 40; f0 = 1; f1 = 1; m = 1; sum = 2; sum2 = 2; do { m = m+1; f = f0 + f1; f0 = f1; f1 = f; sum = sum + f; sum2 = sum2 + (double)f*f; printf(" %3d: %10d, %10d, %20.0lf, %20.0lfUn", m, f, sum+1, sum2, (double)f*f0); } while(m < n); printf("Un"); f0 = 1; f1 = 1; m = 1; 16 sum = 1; sum2 = 1; do { m = m+1; f = f0 + f1; f0 = f1; f1 = f; if(m%2 == 0) { sum2 = sum2 + f; printf(" %3d: %10d, %10dUn", m, f, sum+1); } else { sum = sum + f; printf(" %3d: %10d, , %10.0lfUn", m, f, sum2); } } while(m < n); printf("Un"); 問題 4.3 タイルの敷き詰め問題 たてと横の長さの最大公約数を一辺の長さとする正方形で敷き詰めるのがよい。 int tate, yoko, a, b, c; printf("たてとよこの長さ "); scanf("%d %d", &tate, &yoko); a = tate; b = yoko; c = a % b; while(c > 0) { a = b; b = c; c = a % b; } printf("%d x %d は一辺 %d の正方形 %d 枚で敷き詰められます。Un", tate, yoko, b, (tate/b)*(yoko/b)); 問題 4.4 ペラン数 pn が n で割り切れるならば n は素数のようにみえる。実際、n < 271441 まではそうなることが分かって いる。また、n が素数ならば pn が n で割り切れることも分かっている。 int p1, p2, p3, p, n, m; printf("上限値 "); scanf("%d", &m); n = 2; p1 = 2, p2 = 0, p3 = 3; do{ p = p2 + p3; p3 = p2, p2 = p1, p1 = p; // 初期値設定 // 一つずつずらす 17 n = n+1; if(p % n == 0) printf("n = %d, p(n) = %dUn", n, p); } while(n <= m); 問題 4.5 完全数 1000000 までの完全数は 6, 28, 496, 8128 の 4 つのみ。5 番目に小さい完全数は 33550336。 int nn, n, m, sum, found; printf("上限値 "); scanf("%d", &n); nn = 6; found = 0; while(nn <= n) { sum = 1; m = 2; do { if(nn % m == 0) sum = sum + m + nn/m; if(sum > nn) break; m = m+1; } while(m*m < nn); if(m*m == nn) sum = sum + m; if(sum == nn) { // 完全数発見 found = found+1; m = 2; printf("%d = 1", nn); do { if(nn % m == 0) printf(" + %d", m); m = m+1; } while(m <= nn/2); printf("Un"); } nn = nn+1; } printf("%d までに、完全数が %d 個見つかりましたUn", n, found); } 問題 4.6 ピタゴラス数:a2 + b2 = c2 32 + 42 = 52 なので、62 + 82 = 102 が成り立つ。それはカウントしないとすると、 (a, b, c) = (3, 4, 5), (5, 12, 13), (8, 15, 17), (7, 24, 25), (20, 21, 29), (12, 35, 37), (9, 40, 41) int a, b, c, aa, cc, m, n; double bb; int n; printf("上限値 "); scanf("%d", &n); c = 5; 18 do{ a = c-1; do{ bb = sqrt((double)c*c - a*a); // 3 番目の数は自動的に決まる b = (int)bb; if(bb == b && a+b+c <= n) { aa = a; cc = c; m = cc % aa; // 最大公約数が1の組み合わせに限る while(m > 0) { cc = aa; aa = m; m = cc % aa; } if(aa == 1 || b%aa != 0) printf("%d^2 + %d^2 = %d^2Un", b, a, c); } a = a-1; } while(a*a >= c*c/2); c = c+1; } while(c < n/2); 19 5章 練習 5.1 0 から n − 1 までの整数表示 「%5d」のように、桁数指定のオプションを使う。10 個おきに改行するためには、割り算のあまりを利用 する。 for(i=0; i<n; i++) { printf(" %5", i); if((i+1)%10 == 0) printf("Un"); } printf("Un"); 練習 5.2 for 構文の練習 for(i=3; i<=30; i+=3) for(i=-1; i>=-5; i--) for(i=1; i<30; i++) と if(i%3==0) continue; の組み合わせ 練習 5.3 2k の表示 int 型で計算すると、232 から結果がおかしくなる。double 型で計算して、整数部分だけを表示させるこ ともできる。そうすると、257 くらいまで正確に計算できる。 double M; m = 1; M = 1; k = 1; printf("%2d, %15d, %20.0lfUn", k, m, M); for(k=2; k<=n; k++) { m = 2*m; M = 2*M; printf("%2d, %15d, %20.0lfUn", k, m, M); } 練習 5.4 for( ; ; ) 意味 77 ページの図の A,P,B には何の制約もないので、空であってもかまわない。そうすると、C だけを(限り なく)繰り返すことが分かる。 練習 5.5 for 構文と while 構文の比較 for(k=(n/2)*2; k>0; k-=2) ... k = (n/2)*2; while(k>0) { printf(...); k -= 2;} 練習 5.6 奇数の和 20 while(1) { printf("数 "); scanf(%d", &n); if(n <= 0) break; sum = 0; for(i=1; i<=n; i+=2) sum += i; printf("%d 以下の奇数の総和は %d ですUn", n, sum); } printf("作業終わりUn"); 練習 5.7 表の出力 配列 a[i] は int 型とする。 for(i=0; i<n; i++) { printf(" %8d", a[i]); if((i+1)%5 == 0) printf("Un"); } printf("Un"); 練習 5.8 逆順に表示 for(i=n-1; i>=0; i--) { printf(" %8d", a[i]); if((n-i)%5 == 0) printf("Un"); } printf("Un"); 練習 5.9 配列の前送り a0 = a[0]; for(i=1; i<n; i++) a[i-1] = a[i]; a[n-1] = a0; 練習 5.10 配列の後ろ送り a0 = a[n-1]; for(i=n-1; i>0; i--) a[i] = a[i-1]; a[0] = a0; 練習 5.11 入力エラーのチェック while(1) { printf("Un 何番目から何番目までを表示しますか(1 以上 16 以下) 21 "); scanf("%d %d", &na, &nb); if(na < 0 || nb > 16 || na > nb) { printf("入力が正しくありません、もう一度入力してください。Un"); continue; } 練習 5.12 行列の行和を計算する。 int a[10][10]; for(i=1; i<=3; i++) for(j=1; j<=4; j++) scanf("%d", &a[i][j]); for(i=1; i<=3; i++) { a[i][5] = 0; for(j=1; j<=4; j++) a[i][5] += a[i][j]; } for(i=1; i<=3; i++) { for(j=1; j<=4; j++) printf(" %5d", a[i][j]); printf(" : %5dUn", a[i][5]); } 練習 5.13 行列の和 int a[10][10], b[10][10], c[10][10]; (入力省略) for(i=1; i<=m; i++) { for(j=1; j<=n; j++) c[i][j] = a[i][j] + b[i][j]; for(i=1; i<=m; i++) { for(j=1; j<=n; j++) printf(" %4d", a[i][j]; printf(" "); for(j=1; j<=n; j++) printf(" %4d", b[i][j];U printf(" "); for(j=1; j<=n; j++) printf(" %4d", c[i][j];U printf("Un"); } 練習 5.14 2 項係数 int c[11][11]; c[0][0] = 1; for(n=1; n<=10; n++) { 22 c[n][0] = 1; for(k=1; k<n; k++) c[n][k] = c[n-1][k-1] + c[n-1][k]; c[n][n] = 1; } for(n=1; n<=10; n++) { for(k=0; k<=10-n; k++) printf(" "); for(k=0; k<=n; k++) printf("%4d", c[n][k]); printf("Un"); } 練習 5.15 省略 練習 5.16 巡回数列 for(i=0; i<n; i++) { for(j=n-i; j<2*n-i; j++) printf("%3d", j%n); printf("Un"); } 練習 5.17 総和が一定の 2 数の組 for(i=1; i<n; i++) { for(j=1; j<=n-i; j++) printf("(%d,%d) ", i, j); 問題 5.1 降順、昇順 int a, b, k; do { printf("2 numbers "); scanf("%d %d", &a, &b); if(a > b) for(k=a; k>=b; k--) printf(" %d", k); if(a < b) for(k=b; k>=a; k--) printf(" %d", k); printf("Un"); } while(a != b); 問題 5.2 for 文を使った素数の判定 int b, a; while(1) { printf("number ? "); scanf("%d", &a); for(b=2; b*b <= a; b++) { if(a%b == 0) { printf("%d は素数ではないUn", a); 23 break; } } } if(b*b > a) printf("%d は素数Un", a); 問題 5.3 約数を大きさの順に表示(実習 16 の書き換え) int b, a, q[10], k; printf("number ? "); scanf("%d", &a); k = 0; b = 2; while(b*b < a) { if(a%b == 0) { printf(" %d", b); q[k] = a/b; k = k+1; } b = b+1; } if(b*b == a) printf(" %d", b); for(b=k-1; b>=0; b--) printf(" %d", q[b]); printf("Un"); 問題 5.4 オイラーの公式 int n, b, a; for(n=0; n<=40; n++) { a = n*n + n + 41; for(b=2; b*b <= a; b++) { if(a%b == 0) { printf("%d は素数ではないUn", a); break; } } if(b*b > a) printf("f(%d) = %d は素数Un", n, a); } 問題 5.5 行列とベクトルの積 int n, m, i, j; double a[10][10], b[10], c[10]; // データ入力(省略) for(i=1; i<=m; i++) { c[i] = 0; 24 for(j=1; j<=n; j++) c[i] = c[i] + a[i][j]*b[j]; } printf("こたえ:"); for(i=1; i<=m; i++) printf("%.3lf ", c[i]); printf("Un"); 問題 5.6 行列同士の積 int n, m, p, i, j, k; double a[10][10], b[10][10], c[10][10]; // データ入力(省略) for(i=1; i<=m; i++) for(j=1; j<=p; j++) { c[i][j] = 0; for(k=1; k<=n; k++) c[i][j] += a[i][k]*b[k][j]; } printf("こたえ:"); for(i=1; i<=m; i++) { for(j=1; j<=p; j++) printf(" %6.2lf", c[i][j]); printf("Un"); } 問題 5.7 正方行列のべき乗 int n, m, i, j, k, h; double a[10][10], b[10][10], c[10][10]; // データ入力(省略) printf("何乗? "); scanf("%d", &n); for(i=1; i<=m; i++) for(j=1; j<=m; j++) b[i][j] = a[i][j]; for(h=2; h<=n; h++) { for(i=1; i<=m; i++) for(j=1; j<=m; j++) { c[i][j] = 0; for(k=1; k<=m; k++) c[i][j] += a[i][k]*b[k][j]; } printf("Un%d 乗:Un", h); for(i=1; i<=m; i++) { for(j=1; j<=m; j++) printf(" %6.2lf", c[i][j]); printf("Un"); } for(i=1; i<=m; i++) 25 } for(j=1; j<=m; j++) b[i][j] = c[i][j]; 26 6章 a[0] から入力するには、カウンター n の初期値を −1 とすればよい。 練習 6.1 int 型数同士の割り算 キャストの (double) は、sum/n が括弧でくくられているので、その計算結果に対して適用される。した がって、sum/n が整数でない場合は、小数点以下が切り捨てられてしまう。 練習 6.2 平均身長の計算 double sum, a; printf("データ数 "); scanf("%d", &n); printf("%d 人分の身長データ ", n); sum = 0; for(i=1; i<=n; i++) { scanf("%lf", &a); sum = sum+a; } printf("%d 人の身長の平均は %lfUn", n, sum/n); 練習 6.3 最大値を計算するアルゴリズム 1. a1 を仮の最大値として、変数 big に代入する。i = 2 とする。 2. i > n ならば、big が最大値、アルゴリズム終了。 3. big を ai と比べて、ai が大きければ big を ai で置き換える。 4. i + 1 を新たな i としてステップ2へ戻る。 データが 1 個しかない場合を考えると、ステップ 1 の後にすぐステップ 3 を実行するのは正しくない。 練習 6.4 仮の最大値の初期値 すべてのデータがマイナスだったときに、正しい結果が得られない。プログラムをデバッグするとき、全部 マイナスの数を入力するということは気が付かないことが多いので、見つかりにくいエラー。 練習 6.5 最大値の要素の位置 maxindex = 1; for(i=2; i<=n; i++) if(a[i] > a[maxindex]) maxindex = i; // maxindex が最大値を含む配列の添え字番号 練習 6.6 相対順位 scanf("%d", &k); rank = 1; 27 for(i=1; i<=n; i++) if(a[i] >a[k]) rank = rank+1; // rank が a[k] の相対順位 練習 6.7 簡易棒グラフ 棒グラフだけでなく、階級値、度数も同時に表示させたい。その場合は「%8.2f」のように、フォーマット 指定子のオプションを使って、表示桁数をそろえる必要がある。ここでは、最大度数はせいぜい 50 程度と仮 定している。章末演習問題の 4 番を参照のこと。 c0 = 0; H = 0.1; m = 20; // 度数分布 for(i=0; i<=m+1; i++) dosu[i] = 0; for(k=1; k<=n; k++) { if (a[k] < c0) dosu[0]++; else { i = (int)((a[k] - c0) / H) + 1; if(i > m+1) i = m+1; dosu[i]++; } } // 度数分布の表示 for(i=0; i<=m; i++) { printf(" - %8.2lf : %2d |", c0+i*H, dosu[i]); for(j=0; j<dosu[i]; j++) printf("*"); printf("Un"); } printf("%8.2lf - : %2d |", c0+m*H, dosu[m+1]); for(j=0; j<dosu[m+1]; j++) printf("*"); printf("Un"); 練習 6.8 一様乱数 12 個の和 実習 34 のプログラムのデータ入力部分を次の 3 行で置き換えればよい。簡易棒グラフは、練習 6.8 のプロ グラムを使う。 for(i=1; i<=n; i++) { a[i] = 0; for(j=0; j<12; j++) a[i] += rand()/(RAND MAX+1.0)-0.5; } 練習 6.9 ボイジャーの位置 36 年間を秒数に直して 17 倍すればよい。アバウトな計算なので、1 年を 365 日とする(36 ∗ 365 ∗ 24 ∗ 3600 ∗ 17)。193 億キロメートルになり、int 型の計算ではオーバーフローしてしまう。 28 練習 6.10 for の制御変数に double 型変数を使った場合 表示されるのは、1.000000 から 0.000000 までの 11 行。for 構文の終了条件は「x > 0 である限り」なので、 最後の 1 行が余計。これはシステムの判定ミスではない。printf 文のフォーマット指定子として「"%.20lf"」 という小数点以下 20 桁を表示するオプションを使って調べてみると、11 行目は「0.00000000000000013878」 と表示される。コンピュータで 0.1 は真の 0.1 に比べて無限小数を有限に打ち切ったときに生じる微小な誤差 があり、それを 10 回足してもちょうど1にはならない。フォーマット指定子の「%lf」は小数点以下 6 桁しか 表示しないので、それ以下の桁を四捨五入した結果、最終行はゼロと表示されるが、真のゼロではなかった、 というのが、結果の真相である。 問題 6.1 トリム平均値 int n, i; double a[100], max, min, sum, trim; printf("データの個数 "); scanf("%d", &n); printf("データ "); for(i=1; i<=n; i++) scanf("%lf", &a[i]); max = min = sum = a[1]; for(i=2; i<=n; i++) { sum += a[i]; if(a[i] > max) max = a[i]; if(a[i] < min) min = a[i]; } trim = (sum - max - min) / (n-2); printf("トリム平均値は %lfUn", trim); 問題 6.2 2 番目に大きな数 int n, i, x, max, next, im, in; printf("int 型データを2つ "); scanf("%d %d", &max, &next); n = 2; im = 1, in = 2; if(max < next) { x = max, max = next, next = x; im = 2, in = 1; } do{ printf("データ "); scanf("%d", &x); n = n+1; if(x > max) { next = max, max = x; in = im, im = n; printf("最大値は x(%d) = %d, 2 番目は x(%d) = %dUn", im, max, in, next); } else if(n > next) { 29 next = x; in = n; printf("最大値は x(%d) = %d, 2 番目は x(%d) = %dUn", im, max, in, next); } else printf("最大値は x(%d) = %d, 2 番目は x(%d) = %dUn", im, max, in, next); } while(1); 問題 6.3 成績順位付け int n, i, j; int mk[50][10]; // 1: 学籍番号、2:数学、3:英語、4:情報、5:合計点、6:順位 // データ入力(省略) // 合計点の計算 for(i=1; i<=n; i++) mk[i][5] = mk[i][2] + mk[i][3] + mk[i][4]; // 順位の計算 for(i=1; i<=n; i++) { mk[i][6] = 1; for(j=1; j<=n; j++) if(mk[j][5] >mk[i][5]) mk[i][6]++; } printf(" id rank total math eng progUn"); for(i=1; i<=n; i++) { printf("%4d %4d %5d (%4d %4d %4d)Un", mk[i][1], mk[i][6], mk[i][5], mk[i][2], mk[i][3], mk[i][4]); } 問題 6.4 度数分布の簡易棒グラフ int n, i, j, m, max; int x[2000], dosu[40]; double sc; // データ入力(省略) for(i=1; i<=10; i++) dosu[i] = 0; for(i=1; i<=n; i++) dosu[x[i]]++; // 最大値を見つける(縮尺率の計算) max = dosu[1]; for(i=2; i<=10; i++) if(dosu[i] > max) max = dosu[i]; sc = 50.0 / max; if(max < 50) sc = 1; // 簡易グラフの作成 for(i=1; i<=10; i++) { printf("%2d %4d |", i, dosu[i]); m = (int)(dosu[i] * sc); 30 } if(dosu[i]*sc != m) m++; for(j=1; j<=m; j++) printf("*"); printf("Un"); 問題 6.5 自動ヒストグラム // 演習問題 6.5 double H, x[10000], max, min, rng, amin, amax, fmax; int k,n,nc,f[100], j, i, MAXHT=50; // データの作成(省略) // 範囲の計算 max = x[1]; min = x[1]; for(k=2; k<=n; k++) { if(x[k] > max) max = x[k]; if(x[k] < min) min = x[k]; } rng = max - min; // クラス幅 H の計算 nc = (int)(2 * log(n) / log(2) + 1.5); H = pow(10.0, (int)log10(rng/nc)); while(rng/H >= 2*nc) H = 5*H; // 補正した最小値、最大値、クラスの数を計算する amin = (int)(min/H) * H; amax = (int)(max/H+0.999) * H; nc = (int)((amax - amin) / H) + 1; printf ("scale: [%lf,%lf], ==> [%lf,%lf], H = %lf, nc = %dUn",min,max,amin,amax,H,nc); // 配列データの度数分布を計算する for (k=0; k<=nc+1; k++) { f[k] = 0; } // f[0],f[m+1] は、はずれ値をカウントする for (k=1; k<=n; k++) { if (x[k] < amin) j = 0; else { j = (int)((x[k] - amin) / H) + 1; if (j > nc) j = nc+1; } f[j]++; } // 度数分布とヒストグラムの描画 fmax = f[0]; for (k=0; k<=nc+1; k++) { if (f[k] > fmax) fmax = f[k]; 31 } fmax = MAXHT / fmax; if(fmax > 1) fmax = 1; printf ("[%.3f,%.3f], ==> [%.3f,%.3f], 区 間 幅:%.3f, ク ラ ス 数:%dUn", min, max, amin, amax, H, nc); printf ("度数分布とヒストグラム:Un "); for (k=0; k<=nc+1; k++) { printf (",%10.3lf] (%4d) |",amin+k*H,f[k]); if (f[k] > 0) { i = (int)(f[k] * fmax + 0.5); if(f[k] > 0 && i == 0) i = 1; for (j=0; j<i; j++) printf ("*"); } printf ("Un"); } 32 7章 練習 7.1 10 進数の各桁の数字 scanf("%d", &n); nn = n; printf("%d = ", n); m = 0; do{ b[m] = n%10; n = n/10; if(n == 0) break; m = m+1; } while(1); for(i=m; i>=0; i--) printf("%d ", b[i]); printf("Un"); 練習 7.2 逆順の数との和 練習 7.1 に続けて次を追加する。 n = b[0]; for(i=1; i<=m; i++) n = 10*n + b[i]; printf("逆順は %d, 両者の和は %dUn", n, nn+n); 練習 7.3 省略 練習 7.4 奇数だけのふるい int prime[1500], tab[6000], n, k, i, m; // 10000 までの素数表 n = 1000; for(k=3; k<n; k+=2) tab[k/2] = k; // エラトステネスのふるい(改良版) k = 3; while(k*k < n) { for(i=k*k; i<n; i+=2*k) tab[i/2] = 0; k += 2; } 練習 7.5 素数表 練習問題 7.4 の続き // 素数をピックアップ prime[1] = 2; 33 m = 1; for(k=3; k<n; k+=2) { if(tab[k/2] != 0) prime[++m] = k; } // 素数表の表示 for(i=1; i<=m; i++) { printf("%5d", prime[i]); if(i%10 == 0) printf("Un"); if(i%50 == 0) printf("Un"); } 練習 7.6 省略 練習 7.7 mod M の違い ( b ) a mod M mod N = ab mod (M N ) が 成 り 立 つ の で 、M = 100, 1000, 10000, 100000 と す る と 、 25, 625, 5625, 65625 のように、表示される範囲では同じ数になる。 なお、M = 100000 の場合は、実習 39 のプログラムではオーバーフローするために計算できないので、 double 型で計算する必要がある。 練習 7.8 不正入力データのチェック √ int 型の掛け算は 2ˆ31 以上だとオーバーフローするので、M < 231 < 46341 でなければいけない。 do { printf("2つの正整数 a,b と、46340 以下の正整数 M を入力してください:"); scanf("%d %d %d", &a, &b, &M); if(a <= 0 || b <= 0) { printf("a, b は正整数を入力してください。Un"); continue; } if(M > 46340) { printf("M は 46340 以下の正整数を入力してください。Un"); continue; } } while(1); 練習 7.9 省略 練習 7.10 59x mod(1716) = 1 となる x はいくつ? 59x − 1716y = 1 を満たす x, y を求める問題と読み替えれば、拡張ユークリッド互除法の問題とな る。その結果、x = −349, y = −12 という一つの答え(不定方程式だから他にも答えはある)が見つかる。 59x = 1716y +1 の両辺に 59×1716 を足すと、59(x+1716) = 1716(y +59)+1 となるので、x+1716 = 1367 を新たな x、y + 59 = 47 を新たな y とすれば、それが求める答えになる。すなわち、59 に 1367 を掛けたも のを 1716 で割ったあまりは1になる。 34 問題 7.1 2 進小数表示 int k; double x; while(1) { printf("1未満の数 "); scanf("%lf", &x); printf("%lf は 2 進数で 0.", x); while(x > 0) { k = (int)(2*x); printf("%d", k); x = 2*x - k; } printf("UnUn"); } 問題 7.2 8 進数 int oct[20], a, m, i, b; printf("数 "); scanf("%d", &a); printf("%d を 8 進法 (%o) で表すと ", a, a); m = 0; while(a > 0) { oct[m++] = a % 8; a /= 8; } for(i=m-1; i>=0; i--) printf("%d", oct[i]); b = oct[m-1]; for(i=m-2; i>=0; i--) b = b*8 + oct[i]; printf(" , 検算結果 %dUnUn", b); 問題 7.3 16 進数 int hx[20], a, m, i, b; char hex[] = "0123456789ABCDEF"; printf("数 "); scanf("%d", &a); printf("%d を 16 進法 (%p) で表すと ", a, a); m = 0; while(a > 0) { hx[m++] = a % 16; a /= 16; } for(i=m-1; i>=0; i--) printf("%c", hex[hx[i]]); b = hx[m-1]; for(i=m-2; i>=0; i--) b = b*16 + hx[i]; 35 printf(" , 検算結果 %dUnUn", b); 問題 7.4 素数の個数 int prime[1500], tab[6000], n, k, i, j, m; int a, no, ne, nd, kp, h, count; // 10000 までの素数表作成(練習問題 7.5 を流用) printf("いくつから "); scanf("%d", &a); no = (a/2)*2+1; // 最初の奇数(3 以上) if(no == 1) no = 3; nd = 10000; ne = (a+nd+1) / 2 * 2; // エラトステネスのふるい、準備 for(i=no; i<=ne; i+=2) tab[(i-no)/2] = i; // ふるい落とし kp = 2; while(prime[kp]*prime[kp] <= ne) { h = (no+prime[kp]-1) / prime[kp] * prime[kp]; if(h%2 == 0) h += prime[kp]; // スタート地点は奇数 if(h == prime[kp]) h = prime[kp]*prime[kp]; // 2 乗数より小さい場合 for(j=h; j<=ne; j+=2*prime[kp]) tab[(j-no)/2] = 0; kp++; } // 素数の個数をカウント count = 0; if(a <= 2) count = 1; for(i=no; i<=ne; i+=2) { // 0 を除いて残された数をカウント if(tab[(i-no)/2] != 0) { count++; printf(" %5d", i); } } printf("Un%d から %d までの素数の個数は %dUn", a, a+nd, count); 問題 7.5 na 以上 nb 以下の素数の個数を数える int prime[1500], tab[10000], n, k, i, j, h, m; int na, nb, nc, nd, ne, no, kp, count; // 10000 までの素数表作成(省略) printf("いくつからいくつまで scanf("%d %d", &na, &nb); no = (na/2)*2+1; if(no == 1) no = 3; count = 0; if(na <= 2) count = 1; "); // 最初の奇数(3 以上) // 素数の個数(2 は別カウント) 36 nd = 10000; // 一度に「ふるい落とす」範囲 // エラトステネスのふるいを繰り返し使う for(nc=no; nc<=nb; nc+=nd) { ne = nc + nd - 1; if(nb < ne) ne = nb; for(i=nc; i<=ne; i+=2) tab[(i-nc)/2] = 1; kp = 1; while(prime[kp]*prime[kp] <= ne) { h = (nc+prime[kp]-1) / prime[kp] * prime[kp]; if(h%2 == 0) h += prime[kp]; // スタート地点は奇数 if(h == prime[kp]) h = prime[kp]*prime[kp]; // 2 乗数より小さい場合 for(j=h; j<=ne; j+=2*prime[kp]) tab[(j-nc)/2] = 0; kp++; } for(i=nc; i<=ne; i+=2) { // 残された数をカウント if(tab[(i-nc)/2] == 1) count++; } printf("Un%d から %d までの素数の個数は %dUn", na, ne, count); } 37 8章 練習 8.1 大きいものを最後に アルゴリズム 19 を書き換えるだけ。慎重に。 アルゴリズムL(m) a1 , a2 , ..., am の最大値と am を交換する 1. i = 1, j = 2 とする。 2. aj > ai ならば j を新たな i とする。 3. j + 1 を新たな j として、j ≤ m ならばステップ 2 へ。 4. am と ai の中身を入れ替える。 for(m=n; m>1; m--) { i = 1; for(j=2; j<=m; j++) if(a[j] > a[i]) i = j; aa = a[i]; a[i] = a[m]; a[m] = aa; } 練習 8.2 降順 139 ページの実習 41 のプログラムとほとんど同じ(ステップ2の「<」を「>」に変える) 練習 8.3 別のバブルソート アルゴリズム 21 の変形(バブルソート、昇順) 1. m = n とする。 2. i = m − 1 とする。 3. もし ai > ai+1 ならば ai と ai+1 を入れ替える。 4. i − 1 を新たな i として、もし、i ≥ 1 ならばステップ 3 へ。 5. m − 1 を新たな m として、もし m ≥ 2 ならばステップ 2 へ。 for(m=n; m>=2; m--) { for(i=m-1; i>=1; i--) { if(a[i] > a[i+1]) aa = a[i], a[i] = a[i+1], a[i+1] = aa; } } if 文の条件が成立した後、三つの実行文が「,」で区切られているが、これは、for 文で出てきたカンマ演 算子(77 ページ)と同じで、三つの実行文全体で一つの命令文のような役割を果たす。したがって、条件が満 たされた場合は、 「;」までの三つの実行文を実行する。二つの変数の中身を入れ替える場合は、三つの命令文 が必ずセットで出てくるので、そのつながりを意識してこのような書き方をすることがある。カンマ演算子を 38 使わずに「;」で区切る場合は、三つの実行文を「{ }」で囲んでコードブロックにしなければいけない。 練習 8.4 課題提出者リスト while(1) { printf("学籍番号(3 桁)"); scanf("%d", &k); n = n + 1; a[n] = k; for(m=1; m<n; m++) { c =a[m+1]; for(i=m; i>0; i--) { if(c >= a[i]) break; a[i+1] = a[i]; } a[i+1] = c; } printf("課題提出者:"); for(i=1; i<=n; i++) printf(" %3d", a[i]); printf("Un"); } 練習 8.5 挿入法(降順) アルゴリズム 22 の書き換え。ただし、アルゴリズム 22 は添え字が1から始まっているので、少し注意が必 要。データが 4 つしかない場合に、どうなるか、全部書いてみれば分かる。 アルゴリズム 挿入法ソートにより降順に並べ替える 1. m = 0 とする。 2. c = am+1 , i = m とする。 3. もし、c ≤ ai ならばステップ 5 へ、さもなければ、ai+1 = ai とする。 4. i − 1 をあらたな i とする。もし i ≥ 0 ならばステップ 3 へ。 5. ai+1 = c とする。 6. m + 1 をあらたな m として、もし m ≤ n − 2 ならばステップ 2 へ戻る。 for(m=0; m<n-1; m++) { c =a[m+1]; for(i=m; i>=0; i--) { if(c <= a[i]) break; a[i+1] = a[i]; } a[i+1] = c; } 39 練習 8.6 挿入法マージ アルゴリズム 挿入法によるマージ 1. k = 1 とする。 2. c = bk , i = n とする。 3. もし、c ≥ ai ならばステップ 5 へ、さもなければ、ai+1 = ai とする。 4. i − 1 をあらたな i とする。もし i ≥ 1 ならばステップ 3 へ。 5. ai+1 = c とする。 6. k + 1 をあらたな k として、もし k ≤ m ならばステップ 2 へ戻る。 練習 8.7 縒り合わせ法マージ アルゴリズム 縒り合わせ法マージにより、昇順に並んでいる二つの配列データ a1 , a2 , ..., an ; b1 , b2 , ..., bm を昇順に並べる。 1. an+1 , bm+1 に 231 − 1 を代入する。 2. i = 1, j = 1, k = 1 とする。 3. ai ≤ bj ならば、ck = ai として、i + 1 を新たな i とし、ステップ 5 へ 4. ai > bj ならば、ck = bj として、j + 1 を新たな j とする。 5. k + 1 を新たな k とし、k ≤ n + m ならばステップ 3 へ。 練習 8.8 ストップコードが使えない場合の縒り合わせ法マージ 次の 2 行を 4 行目の後に挿入する。 if(i > n) {c[k] = b[j]; continue;} if(j > n) {c[k] = a[i]; continue;} 練習 8.9 省略 練習 8.10 シェーカーソート left = 1; right = n; dir = 1; while (left < right) { if (dir == 1) { for (j=left, i=left; i<right; i++) { if (a[i] > a[i+1]) { j = i; aa = a[i]; a[i] = a[i+1]; a[i+1] = aa; } } right = j; } else { for (j=right, i=right; i>left; i--) { 40 if (a[i] < a[i-1]) { j = i; aa = a[i]; a[i] = a[i-1]; a[i-1] = aa; } } } } dir = -dir; for (k=1; k<=n; k++) { printf("%d ",a[k]); } printf("Un"); 練習 8.11 省略 練習 8.12 シェルソート アルゴリズムのステップ 2 は「j = 1 から m まで L = ⌊(n − j)/m⌋(ただし、⌊·⌋ は切り捨ての記号)とし て、. . . 」の間違い。それに習って、ヒントの「L=n-1-j)/m」は「L=n-j」の間違い。 m = n; while(m > 1) { m /= 2; printf ("m=%dUn",m); for (j=1; j<=m; j++) { L = (n-j)/m; // L 個おきの数列をソート //(m が小さくなるにつれ、順番になっている部分列が増えるので、挿入法) for (k=j+m; k<=n; k+=m) { for (i=k; i>=j+m; i-=m) { if (a[i-m] > a[i]) { b = a[i]; a[i] = a[i-m]; a[i-m] = b; for (h=1; h<=n; h++) printf ("%3d ",a[h]); printf ("Un"); } else break; // 入れ替えが起きなかったら、その先はもう並んでいる } } } } 問題 8.1 BMI 順に整列 41 int i,j, k, n; double taikaku[100][10], h; // データ入力(省略) // BMI の計算 for(i=1; i<=n; i++) taikaku[i][4] = taikaku[i][3] / taikaku[i][2] / taikaku[i][2] * 10000; // BMI の大きいもの順に並べ替え for(i=1; i<n; i++) { k = i; for(j=i+1; j<=n; j++) { if(taikaku[j][4] > taikaku[k][4]) k = j; } if(k != i) { for(j=1; j<=4; j++) { // i 行目と k 行目の入れ替え h = taikaku[k][j]; taikaku[k][j] = taikaku[i][j]; taikaku[i][j] = h; } } } printf("BMI の大きいもの順に整列Un"); for(i=1; i<=n; i++) printf("%4.0lf %6.1lf %6.1lf %6.1lfUn", taikaku[i][1], taikaku[i][2], taikaku[i][3], taikaku[i][4]); 問題 8.2 ランダム置換 int i, j, k, h, n, tb[50], rd[50]; srand((unsigned int)time(NULL)); while(1) { printf("数 "); scanf("%d", &n); // 数列の準備、乱数設定 for(i=1; i<=n; i++) { tb[i] = i; rd[i] = rand(); } // 乱数を大きさの順に整列 for(i=1; i<n; i++) { k = i; for(j=i+1; j<=n; j++) { if(rd[j] > rd[k]) k = j; } if(k != i) { h = rd[k]; rd[k] = rd[i]; rd[i] = h; h = tb[k]; tb[k] = tb[i]; tb[i] = h; } } printf("%d 個のランダム置換:"); for(i=1; i<=n; i++) printf(" %d", tb[i]); 42 printf("Un"); } 問題 8.3 ビンゴカードもどき int i, j, k, h, tb[100], rd[100]; srand((unsigned int)time(NULL)); while(1) { // 通し番号と乱数を入力 for(i=1; i<100; i++) { tb[i] = i; rd[i] = rand(); } // 乱数を大きさの順に整列 for(i=1; i<99; i++) { k = i; for(j=i+1; j<=99; j++) { if(rd[j] > rd[k]) k = j; } if(k != i) { h = rd[k]; rd[k] = rd[i]; rd[i] = h; h = tb[k]; tb[k] = tb[i]; tb[i] = h; } } // 25 個だけランダム順列を整列 for(i=1; i<25; i++) { k = i; for(j=i+1; j<=25; j++) { if(tb[j] < tb[k]) k = j; } if(k != i) { h = tb[k]; tb[k] = tb[i]; tb[i] = h; } } // 指定の方式で整列 printf("ランダム数字カードUn"); for(i=1; i<=25; i++) { if(i == 13) printf(" 0"); else printf("%3d", tb[i]); if(i%5 == 0) printf("Un"); } printf("UnUn"); scanf("%d", &i); } 43 9章 練習 9.1 double dmin() 条件式の不等号は、等号が付いても付かなくても同じ。テストプログラムは、いろいろなケースに対して繰 り返しチェックできるように、while(1) { ... } で囲んで無限ループにすると良い。以下、テストプログ ラムを証約する。 double dmin(double x, double y { if(x < y) return x; return y; } int main() { double a,b; while(1) { printf("2 つの実数 "); scanf("%lf %lf", &a, &b); printf("%lf と %lf のうちの小さいのは %lf ですUn", x, y, dmin(x,y)); } } 練習 9.2 つり銭、int change() int change(int price, int pay) { if(pay >= price) return pay - price; return 0; } 練習 9.3 BMI 小数点以下 1 桁未満をゼロにするには、10 倍したものの小数点以下を四捨五入して整数にした後、それを 10 で割ればよい。 double BMI(double height, double weight) { int bmi; bmi = weight / height / height * 100000 + 0.5; return (double)bmi / 10; } 練習 9.4 直角三角形の斜辺 double hypotenuse(double a, double b { return sqrt(a*a + b*b); } 44 練習 9.5 偏差値 int hensachi(int ten, double heikin, double hensa { return (int)((ten - heikin) / hensa *10 + 0.5) + 50; } 練習 9.6 3 項演算子 c = (a<b) ? a : b; 練習 9.7 double sum() double sum(double x[], int n) { double s=0; int i; for(i=1; i<=n; i++) s += x[i]; return s; } 練習 9.8 最大値、最小値、範囲 int range(int x[], int n) { int i, xa, xb; xa = x[1]; xb = x[1]; for(i=2; i<=n; i++) { if(x[i] < xa) xa = x[i]; if(x[i] > xb) xb = x[i]; } x[n+1] = xa; x[n+2] = xb; return xb - xa; } 練習 9.9 平均値 double average(int x[], int n) { double s=0; int i; for(i=1; i<=n; i++) s += x[i]; return s / n; } 練習 9.10 内積 45 double innerproduct(double x[], double y[], int n) { int i; double s=0; for(i=1; i<=n; i++) s += x[i] * y[i]; return s; } 練習 9.11 つり銭の金種 問題 3.3 と同じ int change(int price, int int c = pay - price; if(c < 0) c = 0; chg[0] = c/500; c -= chg[1] = c/100; c -= chg[2] = c/50; c -= chg[3] = c/10; c -= chg[4] = c/5; chg[5] = c -5*chg[4]; } pay, int chg[]) { 500*chg[0]; 100*chg[1]; 50*chg[2]; 10*chg[3]; 練習 9.12 ベクトルの逆順 void reverse(int x[], int z[], int n) { int i; for(i=1; i<=n; i++) z[i] = x[n-1+i]; } 練習 9.13 テストの判定 void hyoka(int ten) { if(ten < 60) printf("点数は %d 点、不合格です。Un", ten); else { printf("点数は %d 点、合格です。Un", ten); if(ten >= 90) printf("大変優秀です。 } } 練習 9.14 ベクトルの和 void vectorsum(int x[], int y[], int z[], int n) { int i; for(i=1; i<=n; i++) z[i] = x[i] + y[i]; } 46 練習 9.15 偏差ベクトル 練習問題 9.8 の関数 average() を使う。 double deviate(double x[], double hensa[], int n) { double a = average(x,n); int i; for(i=1; i<=n; i++) hensa[i] = x[i] - a; return a; } 練習 9.16 省略 練習 9.17 省略 仮引数は呼び出したプログラムに影響を及ぼさないので、10 行目の表示は変わらない。 練習 9.18 変数の入れ替え void swap(int *x, int *y) { int z = *x; *x = *y; *y = z; } 練習 9.19 時分秒変換 void henkan(int n, int *hh, int *mm, int *ss) { *hh = n / 3600; *mm = (n % 3600) / 60; *ss = n % 60; } 練習 9.20 2 次方程式の解 演習問題 3.1 と同じ。4 行目の「重解からば」は「重解ならば」の間違い。 nijihouteisiki(double a, double b, double c, double *x,double *y) { double D; if(a == 0) return 0; D = b*b - 4*a*c; if(D > 0) { *x = -(b+sqrt(D))/2/a; *y = -(b-sqrt(D))/2/a); return 2; } else if(D == 0) { 47 } *x = -b/2/a; return 1; } else *x = -b/2/a; *y = sqrt(-D)/2/a); return 3; } 「else」は(直前に return 文があるので)書かなくてもよい。 練習 9.21 ポインタ型の「+1」の意味 ポインタは変数を「ポイント」しており、「+1」は「次の変数」の意味で、ポインタ変数の数値そのものに 1 を足すという意味ではない。「次の変数」は、int 型の場合は 4 バイト先、double 型は 8 バイト先にあるの で、ポインタの数値には、それぞれ 4 あるいは 8 が足される。 練習 9.22 バブルソートのポインタ版 昇順とすれば、139 ページのプログラムと同じ void bubblesort(int *a, int n) { int m, i, c; for(m=n; m>=2; m--) { for(i=1; i<m; i++) { if(*(a+i) > *(a+i+1)) { c = *(a+i); *(a+i) = *(a+i+1); *(a+i+1) = c; } } } } 練習 9.23 挿入法マージ 昇順とする。マージする数列は昇順に並んでいるものとする。140 ページのプログラムと同じ。 void merge(int *a, int n, int *b, int m) { int k, n, i, c; for(k=1; k<=nb; k++) { m = n + k - 1; c = *(b+k); for(i=m; i>0; i--) { if(c >= *(a+i)) break; *(a+i+1) = *(a+i); } *(a+i+1) = c; } } 48 練習 9.24 平均点の列を追加 関数 scoring の中に付け加える、という課題です。 for(k=1; k<=m; k++) { A[0][k] = 0; for(i=1; i<=n; i++) A[0][k] += c[i][k]; A[0][k] /= n; } 練習 9.25 行列の 2 つの行の入れ替え x,y は 1 以上 n 以下の整数であることは仮定する。 void irekae(int A[][10], int m, int n, int x, int y) { int j, aa; for(j=1; j<=n; j++) {aa = A[x][j]; A[x][j] = A[y][j]; A[y][j] = zz;} } 問題 9.1 最大の約数 int MaxFactor(int n) { int k=2; while(k*k < n) { if(n % k == 0) return n/k; k++; } if(k*k == n) return k; return 1; } int main() { int n; while(1) { printf("number "); scanf("%d", &n); printf("%d の最大約数は %dUn", n, MaxFactor(n)); } } 問題 9.2 m 以上 n 以下の整数乱数生成 int RandBetween(int m, int n) { return rand() * (n-m+1) / (RAND MAX+1) + m; } int main() { 49 int a, b, i; srand((unsigned int)time(NULL)); while(1) { printf("numbers "); scanf("%d %d", &a, &b); for(i=0; i<100; i++) printf(" %d", RandBetween(a,b)); printf("Un"); } } 問題 9.3 行列の和 void madd(int A[][10], int B[][10], int C[][10], int m, int n) { int i, j; for(i=1; i<=m; i++) for(j=1; j<=n; j++) C[i][j] = A[i][j] + B[i][j]; } int main() { int n, m, i, j; int a[10][10], b[10][10], c[10][10]; while(1) { printf("行列の和:行列の行数と列数 "); scanf("%d %d", &m, &n); for(i=1; i<=m; i++) { printf("1 番目の行列の %d 行目の %d 個:", i, n); for(j=1; j<=n; j++) scanf("%d", &a[i][j]); } for(i=1; i<=m; i++) { printf("2 番目の行列の %d 行目の %d 個:", i, n); for(j=1; j<=n; j++) scanf("%d", &b[i][j]); } madd(a,b,c,m,n); for(i=1; i<=m; i++) { for(j=1; j<=n; j++) printf(" %8d", c[i][j]); printf("Un"); } } } printf("Un"); 問題 9.4 行列の積 double A[10][10], B[10][10], C[10][10]; void mmult(int n, int m, int L) { int i, j, k; 50 for(i=1; i<=n; i++) { for(j=1; j<=L; j++) { C[i][j] = 0; for(k=1; k<=m; k++) C[i][j] += A[i][k] * B[k][j]; } } } int main() { int n, m, L, i, j; while(1) { printf("1つ目の行列の行数と列数 "); scanf("%d %d", &n, &m); for(i=1; i<=n; i++) { printf("%d 行目の %d 個:", i, m); for(j=1; j<=m; j++) scanf("%lf", &A[i][j]); } printf("2つ目の行列の列数(行数は %d) ", m); scanf("%d", &L); for(i=1; i<=m; i++) { printf("%d 行目の %d 個:", i, L); for(j=1; j<=L; j++) scanf("%lf", &B[i][j]); } mmult(n,m,L); printf("結果の行列Un"); for(i=1; i<=n; i++) { for(j=1; j<=L; j++) printf("%8.2lf", C[i][j]); printf("Un"); } printf("Un"); } } 問題 9.5 正方行列のべき乗 void mpow(double a[][10], double c[][10], int n, int m) { int i,j,k,h; double b[10][10]; for(i=1; i<=n; i++) for(j=1; j<=n; j++) c[i][j] = a[i][j]; for(h=2; h<=m; h++) { for(i=1; i<=n; i++) for(j=1; j<=n; j++) { b[i][j] = 0; for(k=1; k<=n; k++) b[i][j] += a[i][k]*c[k][j]; } 51 } for(i=1; i<=n; i++) for(j=1; j<=n; j++) c[i][j] = b[i][j]; } int main() { int n, m, i, j; double a[10][10], c[10][10]; while(1) { printf("行列の次数 "); scanf("%d", &n); for(i=1; i<=n; i++) { printf("%d 行目の %d 個:", i, n); for(j=1; j<=n; j++) scanf("%lf", &a[i][j]); } printf("何乗? "); scanf("%d", &m); mpow(a, c, n, m); printf("%d 乗はUn"); for(i=1; i<=n; i++) { for(j=1; j<=n; j++) printf("%8.2lf", c[i][j]); printf("Un"); } printf("Un"); } } 問題 9.6 カードを 4 つに分ける void sort(int a[], int n); void display(int a[], int n); void shuffle(int a[], int n); // カードを混ぜる(ランダム置換) void shuffle(int a[], int n) { int rd[100], i, j, k, h; for(i=0; i<52; i++) { a[i] = i; rd[i] = rand(); } for(i=0; i<n-1; i++) { k = i; for(j=i+1; j<n; j++) { if(rd[j] > rd[k]) k = j; } if(k != i) { h = rd[k]; rd[k] = rd[i]; rd[i] = h; h = a[k]; a[k] = a[i]; a[i] = h; 52 } } } // 表示する void display(int a[], int n) { int i; char st[]="SHDC"; sort(a,n); for(i=0; i<n; i++) { printf(" %c%2d", st[a[i]/13], a[i]%13+1); } printf("Un"); } // 整列する void sort(int a[], int n) { int i, j, k, h; for(i=0; i<n-1; i++) { k = i; for(j=i+1; j<n; j++) { if(a[j] < a[k]) k = j; } if(k != i) { h = a[k]; a[k] = a[i]; a[i] = h; } } } int main() { int a[52], i; srand((unsigned int)time(NULL)); while(1) { shuffle(a, 52); for(i=0; i<4; i++) { display(a+13*i, 13); } scanf("%d", &i); } } 問題 9.7 実習 44(マージ)のポインタ版 // 整列する void sort(int *a, int n) { int i, j, k, h; for(i=1; i<n; i++) { k = i; for(j=i+1; j<=n; j++) { if(*(a+j) < *(a+k)) k = j; } 53 } if(k != i) { h = *(a+k); *(a+k) = *(a+i); *(a+i) = h; } } int main() { int a[100], b[100], c[100], n, m, i; int *pa, *pb, *pc; srand((unsigned int)time(NULL)); while(1) { printf("2 numbers "); scanf("%d %d", &n, &m); for(i=1; i<=n; i++) a[i] = rand()%100; for(i=1; i<=m; i++) b[i] = rand()%100; sort(a, n); sort(b, m); pa = a; pb = b; pc = c; *(a+n+1) = RAND MAX; *(b+m+1) = RAND MAX; pa++; pb++; pc++; for(i=1; i<=n+m; i++) { if(*pa <= *pb) *pc++ = *pa++; else *pc++ = *pb++; } pc = c+1; for(i=1; i<=m+n; i++) printf(" %2d", *pc++); printf("Un"); } } 54 10 章 練習 10.1 階乗を計算する再帰関数 double fact(int n) { if(n <= 1) return 1; return n * fact(n-1); 練習 10.2 2 項係数の再帰関数 int binomialC (int n, int k) { if(k == 0) return 1; return binomialC(n, k-1) * (n-k+1) / k; } return 文を「return (n-k+1) / k * binomialC(n, k-1);」とするとエラーになる。なぜならば、 n-k+1 は必ずしも k の倍数ではないので、割り算で正しいこたえが得られない。「(double)」を前に付けて キャストしてもよいが、結果が整数になることが分かっているのに、double 型で計算するのは無駄。最初の 例のように、順番を逆にすれば、binomialC(n, k-1) * (n-k+1) は必ず k で割り切れるので、int 型の割 り算で正しい結果が得られる。 練習 10.3 2 項係数の 2 重再帰を使った関数 int binomialC (int n, int k) { if(k == 0 || k == n) return 1; return binomialC(n-1, k-1) + binomialC(n-1, k); } 練習 10.4 多数個の最大公約数 実習 57 で作った、2 つの数の公約数を計算する関数 rgcd を利用する。引数の並びは「int a[], int n」 の間違い。 int gcds(int a[], int n) { int i, g; g = rgdc(a[0], a[1]); for(i=2; i<n; i++) g = rgdc(g, a[i]); return g; } 練習 10.5 クイックソートの時間 55 正確な所要時間を計るには「clock()」関数を使う。最初に呼んだときの clock() の戻り値を記録し、計 測を終えたいときにもう一度 clock() 4を呼んで、その差を経過時間とする。単位はミリ秒。実際に計って みると、データ数をちょっと大きくしただけで、メモリ爆発が起き、比較ができない。この問題は補助配列な しのアルゴリズムを使ったプログラムが必要になるので、次の問題の後にあるのが適切でした。 次のようなテストプログラムを使って所要時間を計測した結果、クイックソートは約 50 ミリ秒、挿入法ソー トは焼く 5000 ミリ秒、となり、だいたい、1:100 位になった。比較回数だけの比較より差は縮まっている。 void insertionsort(int x[], int n); void quicksort(int x[], int n); int main() { int a[100001], n, i, start, stop; while(1) { printf("個数 "); scanf("%d", &n); for(i=1; i<=n; i++) { a[i] = rand(); } start = clock(); insertionsort(a, n); // quicksort(a, n); stop = clock(); for(i=2; i<=n; i++) if(a[i-1] > a[i]) printf("??? } } a[%d]=%d > a[%d]=dUn",i-1,a[i-1],i,a[i printf("所要時間 %d ミリ秒、%d - %dUnUn", stop-start, start, stop); 練習 10.6 予備配列を使わないクイックソート void swap(int *x, int *y) { int z = *x; *x = *y; *y = z; } void quicksort(int x[], int n) { int a; int pa=1, pz=n+1; if(n <= 1) return; a = x[1]; // 基準となる数を待避 while(pa+1 < pz) { while(pa < n && x[pa+1] <= a) {x[pa] = x[pa+1]; pa++;} // 小さいものを前へ送る // 小さいものを探して前へ while(pz > 1 && x[pz-1] > a) pz--; if(pa+1 < pz) swap(&x[pa+1],&x[pz-1]); // 交換 } quicksort(x,pa-1); // 「小」組を並べ替え 56 } x[pa] = a; quicksort(&x[pz-1],n-pz+1); // 「大」組を並べ替え ポインタを使って書き直すと次のようになる。ほとんど同じ。 void quicksort(int *x, int n) { int a; int *pa=x+1, *pz=x+n+1; if(n <= 1) return; a = x[1]; // 基準となる数を待避 while(pa+1 < pz) { while(pa < x+n && *(pa+1) <= a) {*pa = *(pa+1); pa++;} // 小さいものを前へ送る // 大きいものを残してポインタを前へ while(pz > x+1 && *(pz-1) > a) pz--; if(pa+1 < pz) swap(pa+1,pz-1); //交換 } quicksort(x,pa-x-1); // 「小」組を並べ替え *pa = a; quicksort(pz-1,x+n-pz+1); //「大」組を並べ替え } 練習 10.7 省略 練習 10.8 ハノイの塔パズルの時間 漸化式は f (n) = 2f (n − 1) + 1 で与えられる。両辺に 1 を足せば、f (n) + 1 = 2(f (n − 1) + 1) なので、 f (n) + 1 が等比級数になるから、f (n) + 1 = 2n である。 f (48) = 248 − 1 = 2.815 × 1014 秒を年に直すと、3600 × 24 × 365 で割って 8925513、約 900 万年(!) となる。 練習 10.9 一般化エイトクィーンパズル n クィーン問題の解の数を Q(n) とすると、Q(4) = 2, Q(5) = 10, Q(6) = 4, Q(7) = 40, Q(9) = 352 で ある。 int total; void arrayDisplayI(int c[], int n) { int i; for(i=1; i<=n; i++) printf("%5d", c[i]); printf("Un"); } // 利き筋のチェック int put(int Q[], int k, int j) { int i; for(i=1; i<k; i++) { if(j == Q[i]) return 0; 57 if(j+k == Q[i]+i) return 0; if(j-k == Q[i]-i) return 0; } return 1; } // アルゴリズム P(k) void eightqueen(int Q[], int k, int n) { int j; if(k == n+1) { printf("********* 発見 %3d :", ++total); arrayDisplayI(Q,n); return; } for(j=1; j<=n; j++) { if(put(Q,k,j)) { Q[k] = j; eightqueen(Q, k+1, n); } } } int main() { int n, board[20]; while(1) { printf("マスの数 "); scanf("%d", &n); total = 0; eightqueen(board, 1, n); printf("Un 全部で %d 通りの解が見つかりました。UnUn", total); } } 問題 10.1 べき乗のあまり int bigpowerR(int a, int b, int M) { int ab, aa = a * a % M; if(b == 1) return a % M; ab = bigpowerR(aa, b/2, M); if(b%2 == 0) return ab; return ab * a % M; } int main() { int a, b, M; while(1) { printf("a^b mod M "); scanf("%d %d %d", &a, &b, &M); printf("%d^%d mod %d = %dUn", a, b, M, bigpowerR(a,b,M)); } } 58 問題 10.2 フィボナッチ数列 int fibonacciR(int n) { if(n <= 1) return 1; return fibonacciR(n-1) + fibonacciR(n-2); } int main() { int start, stop, n, f[100], i; while(1) { printf("number "); scanf("%d", &n); start = clock(); printf("f(%d) = %dUn", n, fibonacciR(n)); stop = clock(); printf("経過時間:%lfUn", (double)(stop-start)/CLOCKS PER SEC); f[0] = f[1] = 1; for(i=2; i<=n; i++) f[i] = f[i-1] + f[i-2]; printf("f(%d) = %dUn", n, f[n]); printf("経過時間:%lfUn", (double)(clock()-stop)/CLOCKS PER SEC); } } 問題 10.3 クィックソート、ポインタ使用 // 関数マクロ:変数の中身の交換 #define swap(x,y) swapxxx = x, x = y, y = swapxxx; // チェック用:配列表示 void display(int a[], int n) { int i; for(i=0; i<n; i++) printf(" %2d", a[i]); printf("Un"); } // クィックソートの「再帰+ポインタ」を使ったプログラム void quicksort(int *a, int n) { int *pl, *pu, pv, swapxxx; if(n <= 1) return; if(n == 2) { if(*a > *(a+1)) swap(*a, *(a+1)); return; } // 先頭の要素を基準にして、下組(pl-2 以前)と上組(pl 以降)に分ける pv = *a; pl = a+1; pu = a+n-1; while(pl < pu) { while(pl < a+n && *pl < pv) *(pl-1) = *pl++; 59 while(pu > 0 && *pu >= pv) pu--; if(pl < pu) swap(*pl, *pu); } *(pl-1) = pv; display(a,n); quicksort(a, pl-a-1); quicksort(pl, a+n-pl); // 経過チェック用表示(削除可能) // 再帰呼び出し // 再帰呼び出し } int main() { int a[100], n, i; n = 20; for(i=0; i<n; i++) a[i] = rand()%100; display(a,n); quicksort(a,n); display(a,n); system("pause"); } 問題 10.4 ハノイの塔の経過表示プログラム例 int pile[4][10]; void moverec(int i, int j) { int k, h; // printf(" (%d -> %d)", i, j); pile[j][0]++; pile[j][pile[j][0]] = pile[i][pile[i][0]]; pile[i][0]--; for(k=1; k<=3; k++) { printf("%3d | ", pile[k][0]); for(h=1; h<=pile[k][0]; h++) printf("%2d", pile[k][h]); printf("Un"); } printf("Un"); } void hanoi(int n, int i, int j) { if(n == 1) { moverec(i,j); return; } hanoi(n-1, i, 6-i-j); moverec(i,j); hanoi(n-1, 6-i-j, j); } int main() { int n, i; printf("コマの数 "); scanf("%d", &n); for(i=1; i<=n; i++) pile[1][i] = n+1-i; 60 } pile[1][0] = n; pile[2][0] = pile[3][0] = 0; hanoi(n,1,3); printf("Un"); system("pause"); 問題 10.5 エイトクィーンパズルの解、重複チェック Qi = k とは、k 行 i 列にコマが置かれることを表す記号であるとして、{Qi } をエイトクィーンパズルの 一つの解とする。このとき、垂直方向の鏡像(天地をひっくり返すと重なる)は {9 − Qi }、水平方向の鏡像 (左右に回転すると重なる)は {Q9−i }、180 度の回転(180 度回転すると重なる)は {9 − Q9−i } で表され る。また、Qi = k となる i を Rk とすると、45 度の鏡像(右上から左下の対角線を軸に回転すると重なる) { } { } 9 − R9−i 、135 度の鏡像(左上から右下の対角線を軸に回転すると重なる) Ri 、90 度の右回転(90 度左 { } { } 回転すると重なる) R9−i 、270 度の右回転(270 度左回転すると重なる) 9 − Ri 、によって重なる 7 種 類の亜種が存在する可能性がある。 そこで、一つの解が見つかったら、7 種類の亜種も同時に生成して配列に記録し、新たな解候補が得られた ら、記憶されている解の一つ一つをチェックし、それらのすべてに対して一致するものが存在しない場合にの み、真の解と認めて配列に追加する、という操作を繰り返す。 int EQ[1000][10]; void eightqueen(int Q[], int k); int check(int Q[], int j, int k); void symcheck(int Q[]); void setpattern(int Q[], int k); void boarddisplay(int Q[]); void display(int Q[]); // エイトクィーンパズルの再帰解法 void eightqueen(int Q[], int k) { int j; if(k == 9) { printf("EQ : "); display(Q); symcheck(Q); return; } for(j=1; j<=8; j++) { if(check(Q,j,k) == 1) { Q[k] = j; eightqueen(Q, k+1); } } } // 効き筋チェック int check(int Q[], int j, int k) { 61 int i; for(i=1; i<k; i++) { if(j == Q[i]) return 0; if(j+k == Q[i]+i) return 0; if(j-k == Q[i]-i) return 0; } return 1; } // 対称性チェック void symcheck(int Q[]) { int i, k; for(k=1; k<=EQ[0][0]; k++) { for(i=1; i<=8; i++) { if(Q[i] != EQ[k][i]) break; } if(i > 8) return; } setpattern(Q, EQ[0][0]); } // 新しい解の亜種をすべて登録 void setpattern(int Q[], int k) { int i; for(i=1; i<=8; i++) EQ[k+1][i] = Q[i]; for(i=1; i<=8; i++) EQ[k+2][i] = 9-Q[i]; // 天地 for(i=1; i<=8; i++) EQ[k+3][i] = Q[9-i]; // 左右 for(i=1; i<=8; i++) EQ[k+4][9-Q[i]] = 9-i; // スラッシュ for(i=1; i<=8; i++) EQ[k+5][Q[i]] = i; // 逆スラッシュ for(i=1; i<=8; i++) EQ[k+6][9-Q[i]] = i; // 90 度右回転 for(i=1; i<=8; i++) EQ[k+7][9-i] = 9-Q[i]; // 180 度回転 for(i=1; i<=8; i++) EQ[k+8][Q[i]] = 9-i; // 90 度左回転 EQ[0][0] += 8; } // 盤面表示 void boarddisplay(int Q[]) { int i, j, bd[10][10]; for(i=1; i<=8; i++) { for(j=1; j<=8; j++) bd[j][i] = 0; bd[Q[i]][i] = 1; } printf("+---+---+---+---+---+---+---+---+Un"); for(i=1; i<=8; i++) { printf("|"); for(j=1; j<=8; j++) { if(bd[i][j] == 0) printf(" |"); else printf(" O |"); } printf("Un+---+---+---+---+---+---+---+---+Un"); } printf("Un"); 62 } // 解の簡易表示 void display(int Q[]) { int i; for(i=1; i<=8; i++) printf(" %2d", Q[i]); printf("Un"); } int main() { int Q[10] = {0,1,5,8,6,3,7,2,4}, k; EQ[0][0] = 0; eightqueen(Q,1); printf("nonsymmetric patter : %dUn", EQ[0][0]/8); for(k=1; k<=EQ[0][0]; k+=8) display(EQ[k]); for(k=1; k<=EQ[0][0]; k+=8) boarddisplay(EQ[k]); system("pause"); } 63
© Copyright 2024 Paperzz