(26.1) 次のプログラムは九九の表を出力する。
(26.2) 二重ループについて説明する。 まず外側のループから始まる。 最初は i が 1 で、 外側のループの中カッコにはいる。 するとそこに内側のループがあり、 j が 1 から 9 までこの内側のループを回る。 それが済んで改行を出力すると外側のループの中カッコの中がおわる。 そこで外側のループの再初期化にゆき、 i が 1 だけ増えて 2 になる。 これを i が 9 になるまでくり返す。
#include <stdio.h> main() { int i, j; for (i = 1; i <= 9; i++) { for (j = 1; j <= 9; j++) { printf(" %d", i*j); } printf("\n"); } }
(27.1) アラレ(霰)数とは、初項を自然数とし、次の漸化式で定義される数列である。
この数列は、初項がいかなる自然数であってもそのうち 1, 4, 2, 1, 4, 2, ... というループに落ち着くであろうと予想されているそうだ。 (「霰」とは、空から降るあの「あられ」である。)
(27.2) 次のプログラムは、ユーザが入力した数から始めて 1 になるまで、 この数列を出力する。 for の小カッコの中は二つのセミコロンで三つに区切られるが、 何もない部分は空白にしておくことができる。 下の例では継続条件しかない。
#include <stdio.h> /* アラレ数 */ main() { int n; printf("自然数を入れてください.\n"); scanf("%d", &n); for ( ; n > 1; ) { if (n % 2 == 0) { /* n が偶数のとき */ n = n / 2; } else { /* n が奇数のとき */ n = 3 * n + 1; } printf("%d ", n); } printf("\n"); }(こういった場合、while を知っていればそちらを使うほうがはるかに自然である。)
(27.3) 自然対数の底 e を e = 1/0! + 1/1! + 1/2! + 1/3! + ... として計算するプログラムの、配列を使わない書き方。
#include <stdio.h> /* 自然対数の底 e の計算 */ main() { int n; double sum, factorial; sum = 1; factorial = 1; for (n = 1; n < 23; n++) { factorial = factorial * n; sum = sum + 1 / factorial; printf("第 %2d 項までの和は %.53f です.\n", n, sum); } }
(27.4) int 型と double 型をいっしょに使うときはこのように宣言する。 順序は逆でも構わない。 %2d は「二ケタの幅で表示せよ」の意味、 %.53f は「小数点以下 53 ケタで表示せよ」の意味である。 このプログラムでは double 型と int 型との間で四則演算を行なっている。 その場合、結果は double 型になる。 くわしくは K&R2 §2.7, A6 を参照のこと。
(27.5) ※ 計算は第 22 項までで打ち切っているが、これは試行錯誤をくり返しながら決めたもの。 「収束したら打ち切る」とすることもできなくはないが。
(27.6) 次は、素因数分解のプログラム。 三重ループになっている。 素因数が見つかるまで 2, 3, 4, ... と順に割ってみる素朴なやり方なので、 素因数分解される数が一億ぐらいになるとちょっと時間がかかる場合がある。
(ここまでの知識だけを使って“強引に”書いてみたプログラムなので、 あまりうまくないと思う。 読んで理解できなくても心配する必要はないし、 まねするのがいいかどうかもわからない。)
#include <stdio.h> #define FROM 1 /* この数から */ #define TO 100 /* この数まで */ main() { int n, m, p, i; for (n = FROM; n <= TO; n++) { /* n を素因数分解する */ printf("%d =", n); for (m = n; m != 1; m = m/p) { /* m を割る最小の素数を p に入れる */ p = 0; /* 0 は「まだはいっていない」の印 */ for (i = 2; p == 0; i++) { /* 2, 3, 4, ... と順に割ってゆく */ if (m % i == 0) { /* 割り切れたら */ p = i; /* p に格納 */ } } printf(" %d", p); } printf("\n"); } }
(27.7) エラトステネスのふるいで 10000 以下の素数を全て求めるプログラム。
#include <stdio.h> /* エラトステネスのふるいで 10000 以下の素数を全て求める */ #define KESITENAI 0 /* 消してない */ #define KESITA 1 /* 消した */ int a[10000+1]; /* 配列 a[0], ... , a[10000] を宣言 */ main() { int n, i; for (n = 2; n <= 10000; n++) { /* 配列の初期化 */ a[n] = KESITENAI; } for (n = 2; n <= 100; n++) { /* ふるいにかける */ for (i = 2; n * i <= 10000; i++) { /* n の倍数のうち */ a[n*i] = KESITA; /* 10000 以下のものを消す */ } } for (n = 2; n <= 10000; n++) { /* 結果の出力 */ if (a[n] == KESITENAI) { printf("%d は素数.\n", n); } else { printf("%d は合成数.\n", n); } } }
(27.8) ※ 出力が大量になり、最初の部分がよく見えない場合などは、 「./a.out」の代わりに「./a.out | more」とする。 一画面分だけ出力されると「--続ける--」が出て止まるので、 スペースバーを押せば次の一画面分に進む。 Enter なら一行分だけ進む。 次に進まないでプロンプトに戻りたい場合は「q」または Ctrl+C を押す。
(27.9) ※ 上のプログラムは、第二のループで合成数の倍数も消しているので、 まだまだ効率をあげる余地がある。 しかし、出力が始まったときには第二のループは終わっているわけで、 このくらいの計算はコンピュータにとってはあっという間である。
(27.10) 自然数 a, b の最大公約数 d = gcd(a, b) の求め方や、 ax + by = d を満たす整数 x, y の求め方は代数学で習ったと思う。 a が 19, b が 5 の場合、次のように上から表を埋めてゆく。
i a[i] b[i] r[i] q[i] x[i] y[i] 0: 19 5 4 3 1: 5 4 1 1 2: 4 1 0 4 3: 1 0 0 0 1 0ここで r は余り、q は商である。 x, y は、一番下はすぐわかるので、埋めておいた。 ここを、下から上へと埋めてゆきたい。
ai+1 = bi, bi+1 = ri = ai - biqi を ai+1xi+1 + bi+1yi+1 = d に代入すると bixi+1 + (ai - biqi)yi+1 = d となる。 これを ai , bi について整理して aiyi+1 + bi(xi+1 - qiyi+1) = d を得る。 よって xi = yi+1, yi = xi+1 - qiyi+1 でよい。 (ほかの選び方もある。)
これに従って、x[i], y[i] を下から埋めてゆく。
i a[i] b[i] r[i] q[i] x[i] y[i] 0: 19 5 4 3 -1 4 1: 5 4 1 1 1 -1 2: 4 1 0 4 0 1 3: 1 0 0 0 1 0のようになる。x = -1, y = 4 が答えである。
#include <stdio.h> #define N 50 int a[N]; int b[N]; int q[N]; int r[N]; int x[N]; int y[N]; main() { int i; scanf("%d", &a[0]); scanf("%d", &b[0]); for (i = 0; b[i] != 0; i++) { q[i] = a[i] / b[i]; r[i] = a[i] % b[i]; a[i+1] = b[i]; b[i+1] = r[i]; } a[i+1] = 0; x[i] = 1; y[i] = 0; for (i-- ; i >= 0; i--) { x[i] = y[i+1]; y[i] = x[i+1] - q[i] * y[i+1]; } printf(" i a[i] b[i] r[i] q[i] x[i] y[i]\n"); for (i = 0; a[i] != 0; i++) { printf("%2d:%8d %8d ", i, a[i], b[i]); printf("%8d %8d ", r[i], q[i]); printf("%8d %8d\n", x[i], y[i]); } }
(27.11) ※ N を 50 にしたのは、 このコンパイラが扱える int 型の範囲ではこれで(たぶん)十分であることを別に確かめたからである。
(27.12) ※ C言語の規格では、負の数を含む割り算の余りは一通りに決まってはいない。 (-10) % 3 は -1 かも知れないし、2 かも知れないのである。 よって、上のプログラムを負の数も扱えるように改良することは (ここでは)やめておいた。
全部やらなくても構いません。 むずかしさはまちまちです。
1 から 1 まで足すと 1 です. 1 から 2 まで足すと 3 です. .... 1 から 10 まで足すと 55 です.と出力されるようにせよ。
ツルとカメを合わせて何匹いますか? 5 足は何本ですか? 16 ツル 0 匹、カメ 5 匹のとき足は 20 本で、これは答えではありません。 ツル 1 匹、カメ 4 匹のとき足は 18 本で、これは答えではありません。 ツル 2 匹、カメ 3 匹のとき足は 16 本で、これが答えです。 ツル 3 匹、カメ 2 匹のとき足は 14 本で、これは答えではありません。 ツル 4 匹、カメ 1 匹のとき足は 12 本で、これは答えではありません。 ツル 5 匹、カメ 0 匹のとき足は 10 本で、これは答えではありません。
| 1 2 ... 9 ---+--------...---- 1 | 2 | : : 9 |のように被乗数・乗数を左と上に出力するようにしてみよ。
0 は偶数です. 1 が奇数です. 2 は偶数です. ... 9 は奇数です. 10 は偶数です.のように出力されるプログラムを書け。
for (i = 0; i < 10000; i++) { for (j = 0; j < 10000; j++) { sum = sum + (i + j); /* 意味のない適当な計算 */ } }はどのくらい時間がかかるか? 回数を変えていろいろ調べてみよ。
(29.1) 思いつくままに書いたので、難易度はまちまちである。 いままでの知識で書けるかどうかよく確かめていないものもある。 なお、配列が出てくるものは、 大きさを N とし、N は #define を利用して簡単に変更できるようにするとよい。
(29.2)