4 計算機を使った問題解決

計算の理論
19
計算機を使った問題解決
4
自分でプログラムを書きコンピュータに実行させるためには問題をプログラムの記述範囲で解決
できるように仕立て直さなければならない.その仕立て直しをアルゴリズムの確立などという.ア
ルゴリズムとは料理のレシピ、旅に先立ち目的地までの交通手段を勘案した行き先方法の手だての
ようなものである.前者では材料を用意して焼く、煮る、蒸すといった手段をうまく利用してレシ
ピを表す.後者では徒歩、電車、バス、飛行機あるいはレンタカーなどの手段をうまく利用してな
んとか目的地にたどり着く方法を編み出す.どちらも手持ちの駒(手段)は限られている.
アルゴリズムもコンピュータというメモリと処理装置を中心として成り立つ「機械」に問題を解
決させるのであるから所詮そのとるべき手段は限られている.その数は基本的に 3 つしかない.そ
れらを以下に書き並べてみよう.世の中にあるプログラムはほぼすべてこの 3 つの手段が必ず用い
られていると考えてよい.
それらは
1. 逐次処理(Sequential Processing)
2. 選択(Selection)
3. 繰り返し(Iteration)
である.なお項目 1 を除いて他の 2 項目にはいくつかの変形(バリエーション)がある.その変形
に対応してプログラムの記述性を高めるためさまざまな表記法が用意されている.それらの表記法
を見ていこう.
4.1
逐次処理
前節で出てきた例でフィボナッチ数列の計算の例を振り返ろう.
. . . . . .
a=5 ;
b=8 ;
c=a+b ;
print(c) ;
a=b ;
b=c ;
c=a+b ;
print(c) ;
a=b ;
b=c ;
c=a+b ;
print(c) ;
. . . . . .
この例では a=5,b=8 という整定数代入命令からスタートして上から各行(あるいはセミコロン; と;
で挟まれた演算命令)をステップバイステップ(逐次的 or 連続的)に実行していく.前の演算命
令処理が完結しないと次の演算命令の処理が行えない.print(..) という記号が出てくるがその
計算の理論
20
括弧内の変数のその時点での値を印字、そして改行するものであるとする.そうするとこの部分が
忠実に実行されると印字結果は
13
21
34
とフィボナッチ数列の一部が現れてくる.
ここで C による四則演算の記号について今までの部分までを整理するかたちでまとめておこう.
四則演算子
演算子
意味
=
左辺の変数に右辺の値を上書きする
+
+
-
2 項演算子 a − b の意味と単項演算子 −a
*
×
/
÷ もし a,b ともに整数なら a/b も整数
%
剰余;a%b では a,b ともに整数
問題 1 上のプログラム断片は a=b; b=c; c=a+b; となっている.この順序をちょっと替え b=c; a=b; c=a+b;
としたら結果はどのようになるだろうか.
4.2
4.2.1
選択
二者選択
目的地までの行き先案内を端的に表現するのにこの「選択」という記述法があると楽に記述を記
号化できる.例えば
もし(if)飛行機がすでに満席だったら
(then)駅に行って新幹線で行け
(else) 空港へ急げ
などの状況に応じた柔軟な対応である.なお今自分が空港にいるなら上の(else)以降の動作は必
要ないので
もし(if)飛行機がすでに満席だったら
(then)駅に行って新幹線で行け
だけで済むかもしれない.このような今までの(演算)結果に応じてプログラムの動作を変えるこ
とを選択という.二者選択と多重選択があるがとりあえず二者選択について考えていこう.この選
択は鉄道レールのポイントのようにプログラムの実行先を変更させるので分岐という表現をするこ
ともある.C プログラムでの二者選択は以下のかたちをとる.
if (条件)
条件が真の場合の動作
else 条件が偽の場合の動作
計算の理論
21
つまり英語の選択「もし ∼ ならば、あるいは ∼」を表す単語 “if∼else” をプログラムでもそのまま
記号化して利用している.これを if 文と呼ぶ.条件は関係演算子と論理演算子を用いて表す.それ
らは
関係演算子
演算子
意味
>
>
>=
≥
<
<
<=
≤
==
=
!=
̸=
論理演算子
演算子
意味
&&
論理積
||
論理和
!
否定
例えば浮動小数点変数 x が 0.0 以上 1.414214 より小の範囲にあるかどうかの条件は
(x >= 0.0 && x <1.414214)
と表す.もちろん常に論理演算子を絡める必要はなく単純な判断で整数 i を 5 で割ったあまりが 1
ならという条件は
(i%5 == 1)
と表される.いくつか if 文を使ったプログラム例を見てみよう.まずは実数の絶対値をとる場合;
float x;
........
if (x<0) x=-x ;
x が正あるいはゼロであれば何もしない.負の場合符号を反転する.続いて 3 整数の最大値を見つ
けるそれを変数 max に代入するプログラム.if 分岐を 2 つ重ねる手法に注意.
int i,j,k,max ;
.........
if (i>j)
if (i>k) max=i ;
else max=k ;
else
if (j>k) max=j ;
else max=k ;
となる.
問題 2 ある鉄道会社の運賃は 10km までは 150 円、10km 以上 30km までは 10km を越す分につい
て 1km ごとに 20 円増し、30km 以上では km あたり 10 円増しと制定されている.整数変数
k に適当な値を乗車距離(単位は km)として(k=7、k=12、k=24、k = 37 とか)与えた場
合料金を計算するアルゴリズムを書き下せ.
問題 3 グレゴリオ暦によるとある西暦年(year)がうるう年になるかどうかは以下のようである;
year が 4 で割り切れる年はうるう年、ただし year が 100 で割り切れる年は平年、ただし year
が 400 で割り切れたらうるう年.year に適当な西暦年を入れてその年が平年かうるう年かを
判定するアルゴリズムを書き下せ.
計算の理論
4.2.2
4.3
22
多重選択
繰り返し
C プログラムでは繰り返しの表現に 3 種類の方法がある.
do∼while 文 回数を定めない 1 回以上の繰り返し
while 文 回数を定めない 0 回以上の繰り返し
for 文 既定回数の繰り返し
これらも英語の「 している間」という意味に近い言葉をもってきて記号化している.記号化して
プログラミング言語として文法を導入している.これらを見ていこう.
4.3.1
do while による繰り返し
do while 文の形式は
do
文1 ;
while(条件) ;
となる.まず 1 回文 1(演算命令)が実行されその後 while の後の条件により文 1 が引き続き実行
されるかどうか判断される.ここで条件は先の if 文でみたような論理式を関係演算子および論理
演算子で表す.例えばある正の整数値をあたえそれより大きい最初の素数を見つけたらその値を印
字させるプログラムは以下のように擬似的に記述できる;
int m ;
.........
m=初期値 ;
do {
m=m+1 ;
m が素数であるか判定;
} while(m が素数ではない);
print(m) ;
上の while の後の括弧内の条件が m が素数ではない場合 {· · ·} で示された範囲を繰り返す.初期値
設定が 23 であれば繰り返し 6 回目で 29 が現れそれが素数と判定されるはずなのでその後繰り返
さず繰り返し範囲から抜けて直後の print(m) が実行されて 29 という値が印字されるはずである.
【複合文】ここで {· · ·} について注意しておこう.繰り返しや前記の if 文による選択の対象が上例の
ように複数の文で構成される場合がよくある.それらの複数の文集合を {} でくくり 1 つの文(複
合文と呼ぶ)とさせる機能がある.つまり複合文 ={ 文 1; 文 2; 文 3;,· · ·,}
これは繰り返しに限らず if 文でも有効である.例えば以下のような 2 次方程式の解を求めるプロ
グラム、実数解がない場合メッセージを出すようにした.input 手続きで入力された係数 a,b,c を
持つ 2 次方程式 ax2 + bx + c = 0 の成り立つ数値解を求め答えを変数 x1,x2 に入れるものである.
計算の理論
23
input (a,b,c) ;
d=b*b-4.0*a*c ;
if(d>0) {
x1=(-b+sqrt(d))/(2*a) ;
x2=(-b-sqrt(d))/(2*a) ;
} else
if (d==0.0) {
x1=-0.5*b ;
x2=x1 ;
} else {
print("実数解は存在しない") ;
}
√
(d) を計算する手続きであると考え
ておいてほしい.手続き(関数)に関しては後述(4.4 節)を参照してほしい.
のようにである.ここで sqrt(d) なる記述があるがこれは
なお素数の判定が出てきたがその具体的なプログラム記述はどうなるだろうか?それは以下のよ
うなシナリオになる;
約数=2 ;
do {
素数の候補を約数で割る ;
約数=約数+1 ;
} while(余り !=0 && 約数<=素数の候補) ;
if(どの数でも割り切れない)
素数 ;
else
素数ではない ;
while の条件の後半の約数 < 素数の候補というのは必要なく約数 ≤ 平方根(素数の候補)まで調
べれば十分であることを理解しよう、すると C 言語で平方根をとる計算は sqrt() という関数であ
ることを踏まえて以下のように記述できる.なお a=p%d という演算は整数 p を整数 d で割った余
りが a に代入されることだと思い出そう.
int p,s,d,a ;
p=367 ;
d=2 ;
do {
a=p%d ;
d=d+1 ;
} while(a !=0 && d<=sqrt(p));
if(??)
print("素数") ;
else
print("素数ではない") ;
問題 4 上記のプログラム最後の if(??) の??にはどういう論理式が適切だろうか?
計算の理論
4.3.2
24
while 文を使った繰り返し
while 文の形式は
while(条件)
文1 ;
である.while の次に論理式で指定される条件が成り立つ間(真であれば)、文 1 の実行が繰り返
される.最初から条件が成り立たないときは文 1 はけっして実行されないことに注意. 文 1 に複
数の命令文を対応させたいときには {} でくくり複合文化させる.do while 文は最低 1 回は do と
while の間の文が実行される.10,000 円が年率 1%の複利で運用される貯蓄で 2 倍の 20,000 円を越
すのは何年後か知りたい場合プログラムは以下のようになるだろう;
float m,r,max ;
int year ;
year=0;
m=10000.0 ;
max=2*m ;
r=0.01 ;
while( m<max) {
m=m*(1.0+r) ;
year=year+1 ;
}
print(year) ;
print(m) ;
また例えば 0 以上の(正の)整数値を手続き input で 1 つ入力する.何個入力されるかわから
ないがもし負の値に出くわしたら終了とする.入力された整数で最大のものをこの繰り返しの終了
後に print させよという問題.while を使って表現するとどうなるだろうか.
2 つの正の整数の最大公約数 GCD(Greatest Common Divisor)を求めるアルゴリズムとして
ユークリッドの互除法というのが知られている.以下の 2 つの関係式の発見によるものである.与
えられた正の整数を x, y(x > y) として
y > 0 ならば GCD(x, y) = GCD(y, x/y の余り)
y = 0 ならば GCD(x, y) = x
24 と 9 の GCD を求めることを考えると
GCD(24, 9) = GCD(9, 6) = GCD(6, 3) = GCD(3, 0) = 3
この GCD を求めるプログラムは
int x,y,a ;
while(y != 0) {
x/y の余りを求める ;
x を y で置き換える ;
計算の理論
25
y を余りで置き換える ;
}
print(x) ;
問題 5 上のプログラムで while の内の日本語部分を x,y,z を使って C プログラム化(コード化)せ
よ.またこのアルゴリズムは do while の繰り返し手法によるコード化では不具合が起きる可
能性がある.どういう場合か指摘せよ.
4.3.3
for 文を使った繰り返し
for 文を使った既定回数の繰り返し表現とは以下のようなかたちをとる;
for (制御変数リセット); 制御変数による継続条件; 制御変数更新)
文1 ;
文 1 に複数の命令文を対応させたいときには {} でくくり複合文化させる.
• 初期化式には代入文で制御変数をリセットさせる.
• 継続条件は制御変数を用いた論理式で繰り返しの継続条件を記述.
• 再初期化とは文 1 の実行が一度終わり継続条件が成り立ち再度繰り返しが行われるときの制
御変数の更新の仕方を代入文で記述
1 から 10 までの和を求めるには
int i,sum ;
sum=0;
for(i=1;i<=10;i=i+1)
sum=sum+i ;
print(sum) ;
このとき変数 i が制御変数となっている.for 文の最初のブロック i=1 で初期化がなされ、第 2 ブ
ロックより i <= 10 で i のループ終了時での値が評価される.そして繰り返し 1 回ごとの終了に
伴って最後のブロックで指定された i=i+1 がなされ制御変数の更新がなされる.更新は制御変数
の継続条件が真である限り続いていく.
for 文の上に for 文を重ねると 2 次元的なデータ処理が可能になる;
int line,star ;
for(line=1;line<10;line=line+1) {
for(star=0;star<line;star=star+1)
print("*") ;
print("\n") ;
}
として print("*") ; が*をひとつ、print("\n") ; が改行をそれぞれプリントさせる機能がある
とするとこのアルゴリズムの実行後に以下のパターンがスクリーンに現れる;
計算の理論
26
*
**
***
****
*****
******
*******
********
*********
**********
以下の例もプログラム技法を学ぶには示唆に富んでいる
int tate,yoko ;
int seki ;
for(tate=1;tate<10;tate=tate+1) {
for(yoko=1;yoko<10;yoko=yoko+1) {
seki=tate*yoko ;
print(seki) ;
}
print("\n") ;
}
結果は以下のようになるはずである;
1
2
3
4
5
6
7
8
9
2
4
6
8
10
12
14
16
18
3
6
9
12
15
18
21
24
27
4
8
12
16
20
24
28
32
36
5
10
15
20
25
30
35
40
45
6
12
18
24
30
36
42
48
54
7
14
21
28
35
42
49
56
63
8
16
24
32
40
48
56
64
72
9
18
27
36
45
54
63
72
81
問題 6 星を印字するアルゴリズムを改造して以下のような印字にするにはどうする?
*
***
*****
*******
*********
***********
*************
***************
*****************
*******************
計算の理論
27
問題 7 かけ算のアルゴリズムを改造して以下のような出力にするにはどうする?print(” ”) ; は答
えの数字を書く代わりにブランクをプリントするものとする.
1
2
4
3 4 5 6 7
6 8 10 12 14
9 12 15 18 21
16 20 24 28
25 30 35
36 42
49
8
16
24
32
40
48
56
64
9
18
27
36
45
54
63
72
81
計算の理論
4.4
28
モジュール化
f (x) = x2 − C = 0 を解くアルゴリズムを考えよう.ニュートン法と呼ばれるものである.この
√
解は C である.したがってある実数の平方根を求めるのに利用できる.ニュートン法は逐次的に
適当に初期予想値例えば x0 = 1 から出発して
f (xi )
x2i − C
1
=
x
−
= (xi + C/xi )
i
′
f (xi )
2xi
2
√
なる数列を計算させていけばそれはやがて C に収束するという考えを背景にしたものである.こ
の式の意味するアルゴリズムを言葉にすると;繰り返しごとに予想値(xi )と C/xi の平均をとり
xi+1 = xi −
それを次の繰り返しの予想値(xi )としていこうというものである.例えば C として 2 を考えて
みると
予想
2/予想
予想と 2/予想の平均(次回の予想値)
1
2/1=2
(1+2)/2=1.5
1.5
2/1.5=1.33. . .
(1.5+1/33. . .)/2=1.4167
1.4166. . .
2/1.4166. . .=1.4118. . .
(1.4118. . .+1.4166. . .)/2=1.414215. . .
1.414215. . .
···
···
このアルゴリズムを以下の 2 点を気にしながらプログラム化していこう.
1. 数列 xi の表現
2. 収束の判定と繰り返し
項目 2 の収束判定はプログラムを作るものが自分の責任で設定しなければならない.とりあえず毎
回 |x2i − C| を計算させてその値が例えば 0.001 程度になったら十分であると考えておく.次に変数
x0 を導入して x1 を計算させそれをもとに x2、x3 と計算させていくのだろうか.収束判定条件に
もよるがそうすると変数をいくつ導入すればよいのかわからない.とりあえず x99 までと全部で
100 個の変数を導入すればよいだろうか.100 個は天文学的に大きな数ではないがちょっとした計
算でそんなに莫大な変数を導入するのはおかしいし 100 個という数も合理的な値ではない.どうす
れば良いかというと変数は 2 個である十分であるということである.更新される値を入れる変数と
√
現在の値を入れる変数である.擬似的に( 2 を求める)プログラムを記述してみると
C=1.0 ;
現在値=初期値 ;
while(収束しない){
更新値=…(現在値をもとに);
現在値=更新値 ;
}
print(現在値) ;
}
としておけば繰り返しの度に更新値も現在値も更新されていく.それらは途中結果で我々には
その値に興味はない.興味があるのは収束後の最終値である.上の例ではその思想が反映されて
計算の理論
29
いる.さらに while 文中の条件である「収束しない」はどう表現させるかというと | 現在値*現在
値-C| > 0.001 とすれば良いとすぐに気がつくはずである.こうしてプログラムをより C プログラ
ム風にすると以下のようになる;
float epsilon,C,new,now ;
C=2.0 ;
now=C ;
epsilon=0.001 ;
while(fabs(now*now-C)>epsilon) {
new=(now+C/now)*0.5 ;
now=new ;
}
print(now) ;
while 文中に fabs(…) というものがあるがこれは実数値の絶対値をとる手続きである.つまり
fabs という手続きは () 内で指定された式(あるいは変数)が 0 以上であればそのままの値を返し
負であればその値の符号を反転させた値を返すものである.これを関数と称している.関数は戻り
値(出力)=関数(入力変数)という形をとる.入力変数を引数パラメータと呼んだりもする.い
くつかの基本的な関数はすでにシステムライブラリという名の下に登録済みで fabs もそのうちの
ひとつであるが自分で関数を目的に応じて定義することもできる.fabs は以下のようになる;
float fabs(float x) {
if(x>=0.0) return x ;
else return -x ;
}
出力の戻り値は return の後に指定する.関数を自分で所持しているといろいろなところで使い
回しができ新たにプログラムを起こす必要がないのでプログラミングの手間も省けまた一般化もで
きてとても便利になる.この節の最初に示した平方根を計算するプログラムも y=sqrt(x) という形
で一般かしておくと便利になることが理解できるだろう.例えば
float sqrt(float C) {
float epsilon,C,new,now ;
now=C ;
epsilon=0.001 ;
while(fabs(now*now-C)>epsilon) {
new=(now+C/now)*0.5 ;
now=new ;
}
return now ;
}
と整えておけば必要なときに
float tt,kk ;
tt=sqrt(8.0) ;
kk=sqrrt(tt) ;
計算の理論
30
などと呼び出して気軽に使えるようになる.入力変数の値を指定しなおして何回でも使い回しがき
く.関数設定はその冒頭に関数の出力(つまり関数そのものの型)を指定し関数名(変数の命名法
と同じ基準)と()をつけてその中に 0 個以上の入力パラメータを指定すればよい.
関数内で利用される変数は関数内のみ(ローカルに)で有効で呼び出す側がどのような変数が使
われているか気を使う必要もない.
関数としても戻り値を返さなくて(void)良いものもある.例えば入力で個数を与えその個数分
*をスクリーンに横一列にプリントするもの.戻り値が void 指定であれば return 指定も必要ない.
void asterisk(int n) {
int i ;
for(i=0;i<n;i++) print("*") ;
print("\n") ;
}
呼び出しは asterisk(5) ; などとする.こうすると前節で見た星の三角形描きは
int line ;
for(line=1;line<10;line++)
astersik(line) ;
とわかりやすくなる.
問題 8 整数変数(n)を外部から受け 1 から n までの和を求める関数 summ(n) を書き下せ.続い
て n から m までの和を求める summinterval(int n,int m) なる関数を書け.なおこの関数
は 2 回 summ を呼び出すことによってなるものとする.
問題 9 n!(n の階乗)を求める関数 factorial(int n) を書き下せ.
問題 10 n 項めのフィボナッチ数列項を求める関数を書き下せ.
問題 11 前節の GCD を求めるプログラムを関数として独立させよ.
問題 12 関数 sqrt の収束条件を以下のように変更してプログラムを書き改めよ:前回もとめた now
を新たな変数を導入させて覚えておき今回得られた now と絶対値比較する.その値が epsilon
より小さくなったら収束したと判断する.つまりこのニュートン法でできる数列 {xi } は i が
√
大きくなるに従って (C) に収束するのでいずれ xi と xi+1 との間隔は狭まっていくであろ
うという予測の上である.
4.5
再帰
前節問題 8 の 1 から n までの和を求めるプログラムを再考しよう.1 から n までの和は 1 から
n − 1 までの和に n を足したもの.つまり
n
∑
i=1
i=n+
n−1
∑
i
i=1
である.これは summ(n)=summ(n-1)+n ということである.また summ(1)=1 である.したがって
この関数は以下のような C プログラムで表現できる.
計算の理論
31
int summ(int n) {
if(n==1) return 1 ;
else return (n+summ(n-1)) ;
}
あるいは 1 ステップ余計ではあるが以下のようにも書き下せる.
int summtail(int n, int k) {
if(n==0) return k ;
else summtail(n-1,n+k) ;
}
int summ(int n) {
summtail(n,0) ;
}
階乗計算もこの調子で出来る.この関数の中で自分自身を呼び出すことを再帰(recursive)呼び
出しと呼んでいる.繰り返し表現はほとんど再帰を使って表現できる.例えば GCD は
int recgcd(int x,int y) {
if(y == 0) return x;
else recgcd(y,x%y) ;
}
問題 13 関数 sqrt を再帰呼び出しを用いて記述し直しなさい.
問題 14 負でない 2 つの整数 x, y に対して Ackermann 関数 Ack(x, y) は以下のように定義される.
この関数を再帰的に求めるプログラムを作りなさい.


 y+1
Ack(x, y) =


Ack(x − 1, 1)
if x = 0
if y = 0
Ack(x − 1, Ack(x, y − 1))
otherwise
ハノイの塔というゲームを考えてみよう.図 1 のようなものである.
底板に 3 本の棒が立てられている.7 枚の半径のすべて異なる中心に穴があいている円盤がある.
穴は十分広くどの棒にも差し込める.ゲーム開始時は円盤がひとつの棒に上になるにつれて小さく
なるように置かれている.図 1 のように一つの塔をなしている.この塔から 1 枚 1 枚円盤を引きは
がし反対側の棒に円盤を移し替え最終的に反対側の棒に今と同じ塔を作り上げるというがゴールあ
る.引きはがした円盤は真ん中の棒にとりあえず何枚も置いておける.しかし大きな円盤が小さな
円盤の上になるように置いておいてはいけない.もちろん最終ゴールの棒においてもその鉄則は維
持されなければならない.この単純なルールに従ってゲームを進行させるのだがちゃんとアルゴリ
ズムを立てて進めていかないとたちまちたち行かなくなってしまう.
最初の棒を source の s、行き先を destination の d、真ん中の待機棒を workspace の w とすると
アルゴリズムの基本は
1. 6 枚の円盤を(あたかもいっぺんに)s から w に移す
2. 最下の 1 枚を s から d に移す
計算の理論
32
図 1: ハノイの塔
3. 6 枚の円盤を w から d に移す
ということになる.6 枚の円盤は s → w → d と動いていく.6 枚の円盤の移動は 5 枚の円盤と最下
の 1 枚とに分離できて同じアルゴリズムが適用できる.このようにしてアルゴリズムが再帰的に表
現されていくのがわかるだろう.今までの単純な繰り返しを再帰的に表現したものと違い再帰によ
る記述が問題解法の本質になってきている例である.全体のアルゴリズムを書き出すために上の 6
枚移動のステップを n 枚と一般化して書き出しておく.
1. (n-1)枚の円盤を(あたかもいっぺんに)s から w に移す
2. 最下の 1 枚を s から d に移す
3. (n-1)枚の円盤を w から d に移す
再帰呼び出しの最終判定はただ一枚(n=1)の円盤が棒から棒へ移動していくことになる.そし
てその円盤が d に行き着いたら終了とする.そう決めておくとハノイの塔攻略アルゴリズムは再帰
的手法を使って以下のような関数に集約できる.したがってこの関数は以下のような疑似 C プロ
グラムで表現できる.
void hanoi(int n, int s, int d, int w) {
if(n==1) {
円盤を s から d へ移動 ;
return ;
} else {
hanoi(n-1,s,w,d) ;
1 枚の円盤を s から d へ移動 ;
hanoi(n-1,w,d,s) ;
}
}
計算の理論
33
この節を始めるにあたって重要なことは逐次処理、選択(分岐)、繰り返しの 3 つであると記し
た.しかしこの例を見て再帰も重要な手法のひとつであることが認識されるであろう.再帰を有効
に利用したソーティングアルゴリズムもいずれ見ていこう.
問題 15 ハノイの塔のゲームを再帰を使わず繰り返し手法で記述するアルゴリズムを考案せよ.
4.6
構造データ型入門 - 配列
今までこの節ではプログラムの計算順序の制御様式について議論してきた.今まで紹介した簡単
なアルゴリズムでは結果は単一なるデータからなる情報であった.いわばスカラー値が結果として
戻されてきた.コンピュータの基本的役割のひとつに大量のデータを加工しそれらを一貫した形で
保持する、というものがある.この大量のデータの扱いに関しては今までの一つの変数がひとつの
値を保持する(スカラー)という形では限界があり大容量のデータ貯蔵様式をプログラム言語が所
持されていることを求められるゆえんである.それがデータ構造である.プログラムで扱うデータ
にはさまざまなものがある.今まで見てきた基本データ型(int や float)以外にもさまざまな型
がある.容易にいくつか想像できるだろう.それらは構造型データとして C プログラムの中で表
現できるのだがそれは以降に考察するとしてここでは単一データ型(整数のみとか浮動小数点の
み)の数値を蓄える配列をその初歩として考えよう.例えばクラスの全員(80 人)のテストの成
績、学修番号、身長・体重でもよい.今までのスカラー変数の場合だと
int grade00,grade01,grade02,…,grade79 ;
int number00,number01,…,number79 ;
float height00,height01,height02,…,height79 ;
grade00=65 ;
grade01=78 ;
grade02=25 ;
…
grade79=55 ;
number00=11162077 ;
number01=11162088 ;
number02=11162130 ;
…
height00=149.3 ;
height01=172.0 ;
height02=165.3 ;
…
などであるが、このようにしておくとデータ処理が大変煩雑でプログラム処理も非効率となるの
は明白である.各カテゴリーの平均値の算出のプログラム構成を考えてみても力づくでなければや
れない話である.以下のようになる.
float gaverage, haverage ;
gaverage=(grade00+grade01+grade02+…+grade79)/80 ;
haverage=(height00+height01+height02+…+height79)/80 ;
計算の理論
34
そこであるかたまりデータは単一の集合に納めその名前には単一の変数名を付与し集合内の各要
素データは添字(インデックス)で区別させる配列というデータ構造が容易されている.C プログ
ラムでは以下のように配列使用をプログラム冒頭で宣言しておく.その形式は
データ型 配列名 [配列要素数] ;
である.
int grade[80],number[80] ;
float height[80] ;
…
上例では grade、number という名前の 2 つが要素数ともに 80 の int 型の配列として宣言され
ている.それに対して height は float 型である.なお型は配列の要素一つ一つがもつ値の型のこ
とである.要素番号(添字;インデックス)は 0 から始まり要素数-1 が最終要素を示す添字数とな
る.またこの場合も各データは以下のようにひとつひとつ与えなければならないが(ここでは便宜
的に以下のように記したが実際の C プログラム上ではデータ入力にいくつかの手抜き法がある)、
int grade[80],number[80] ;
float height[80] ;
grade[0]=65 ;
grade[1]=78 ;
grade[2]=25 ;
…
grade[79]=55 ;
number[0]=11162077 ;
number[1]=11162088 ;
number[2]=11162130 ;
…
height[0]=149.3 ;
height[1]=172.0 ;
height[2]=165.3 ;
…
配列を利用すると例えば平均値を求めるとか最大値、最小値を求めるといった操作が繰返し for
を使って平易に記述できる.
int grade[80],number[80] ;
float height[80] ;
float gaverage,haverage,gmax,gmin ;
int index ;
grade[0]=65 ;
grade[1]=78 ;
grade[2]=25 ;
…
…
計算の理論
35
height[0]=149.3 ;
height[1]=172.0 ;
height[2]=165.3 ;
…
gaverage=0.0 ;
for(index=0;index<80;index=index+1)
gaverage=gaverage+grade[index] ;
gaverage=gaverage/80 ;
gmax=grade[0] ;
gmin=grade[0] ;
for(index=1;index<80;index=index+1) {
if(grade[index]>gmax) gmax=grade[index] ;
if(grade[index]<gmin) gmin=grade[index] ;
}
print(gaverage,gmin,gmax) ;
データ入力は個別に扱わなければならないがそれ以降のデータ処理のプログラム構成が簡易(シ
ンプル)化され平均値の計算、最大、最小値の選出の手法がわかりやすくまとめられている.なお
配列の名前は集合の名前であり個別のデータはそれに [添字] をつけて参照され計算の対象になるの
で、a が通常の整数変数として使われているとき a=grade*number などという記述はできない.特
定の要素を指定しなければならないので a=grade[9]*5 とか a=number[i]%1000000 などとする.
この節の一番最初にあげた計算例としてフィボナッチ数列があった.最初にあげた例では変数 c
に最新項の値が 2 番目に旧い(変数 a に格納)項と 1 番目に旧い(変数 b に格納)項の和で計算さ
れて格納されるのだが項が変わっていくにつれて c,b,a はそれぞれ使い回されてきたので第 n 項目
の値はいくらか後で参照することがかなわなかった.しかし以下のようにするとフィボナッチ数列
の第 100 項目までの値を fib という配列の 1 項目から 100 項目までに保存されていることになる.
int fib[101] ;
int i ;
fib[1]=1 ;
fib[2]=1 ;
for(i=3;i<101;i++)
fib[i]=fib[i-1]+fib[i-2] ;
問題 15 以下のプログラム断片は int 型配列 velo の要素 0 から 99 までにデータを 1 から 100 ま
で格納したものである.
int velo[1000] ;
int i ;
for(i=0;i<100;i++)
velo[i]=i+1 ;
計算の理論
36
この後にさらに付け加えて velo の格納が逆順になるようにしなさい.
即ち velo[0]=100, …,velo[99]=1 となるように.
2 次元配列も利用できる.行と列の要素である.宣言の仕方は 1 次元配列と同様に
データ型 配列名 [行配列要素数][列配列要素数] ;
である.例えば float fdata[5][3] ; という配列は 5 行 3 列の行列構造をとりデータの格納は以
下のようなイメージとなる.
fdata[0][0]
fdata[0][1]
fdata[0][2]
fdata[1][0]
fdata[1][1]
fdata[1][2]
fdata[2][0]
fdata[2][1]
fdata[2][2]
fdata[3][0]
fdata[3][1]
fdata[3][2]
fdata[4][0]
fdata[4][1]
fdata[4][2]