このドキュメントは http://edu.net.c.dendai.ac.jp/ 上で公開されています。
ここでは通常の数式をどのように計算するかを考えます。 問題を簡単にするため、足し算とかけ算だけからなる式を考えます。 かけ算は足し算より優先されるので、足し算の後にかけ算が来た場合はかけ算 が優先されます。 つまり注目している演算子が足し算だった時、次がかけ算の場合があるので足 し算をすぐに実行できません。 数式を左から見ていった時、どのように計算すればいいか整理するため、次の 表を作りました。
計算式の左側 | 計算して良い部分 | |
---|---|---|
1 | 1+2+3+4... | 1+2+3 |
2 | 1+2+3*4... | 1+2 と 3*4 |
3 | 1+2*3+4... | 2*3を計算した後 1 との和を求める |
4 | 1+2*3*4... | 2*3 を計算した後 4 との積を求める |
5 | 1*2+3+4... | 1*2 を計算した後 3 との和を求める |
6 | 1*2+3*4... | 1*2 の計算と 3*4 の計算を別にしておく |
7 | 1*2*3+4... | 1*2*3 |
8 | 1*2*3*4... | 1*2*3*4 |
6 の例に注目すると、計算途中で二種類の値(足し算の左辺、かけ算の左辺)を記憶し ておく必要があることがわかります。 この観点からもう一度表を作ると次のようになります。
計算式の左側 | 足し算の左辺 | かけ算の左辺 | |
---|---|---|---|
1 | 1+2+3+4... | 1+2+3 | 4 |
2 | 1+2+3*4... | 1+2 | 3*4 |
3 | 1+2*3+4... | 1+2*3 | 4 |
4 | 1+2*3*4... | 1 | 2*3*4 |
5 | 1*2+3+4... | 1*2+3 | 4 |
6 | 1*2+3*4... | 1*2 | 3*4 |
7 | 1*2*3+4... | 1*2*3 | 4 |
8 | 1*2*3*4... | 0 | 1*2*3*4 |
前回までの計算の仕方では次の演算子を読んだ時、数字の終りを検出して、そ れまでの計算を行うというものでした。 ここで、1?2? まで読んだ状態と、1?2?3? まで読んだ状態を比較してみます。
計算式の左側 | 1?2? まで読んだ時 | 1?2?3? まで読んだ時 | |||
---|---|---|---|---|---|
足し算の左辺 | かけ算の左辺 | 足し算の左辺 | かけ算の左辺 | ||
1 | 1+2+3+... | 1+2 | なし | 1+2+3 | なし |
2 | 1+2+3*... | 1+2 | なし | 1+2 | 3 |
3 | 1+2*3+... | 1 | 2 | 1+2*3 | なし |
4 | 1+2*3*... | 1 | 2 | 1 | 2*3 |
5 | 1*2+3+... | 1*2 | なし | 1*2+3 | なし |
6 | 1*2+3*... | 1*2 | なし | 1*2 | 3 |
7 | 1*2*3+... | なし | 1*2 | 1*2*3 | なし |
8 | 1*2*3*... | なし | 1*2 | なし | 1*2*3 |
この表から、 3 とその次の演算子を読んだ時にしなければならないことを導 きます。
計算式の左側 | 1?2? まで読んだ時 | 1?2?3? まで読んだ時 | |||
---|---|---|---|---|---|
足し算の左辺 | かけ算の左辺 | 足し算の左辺への計算 | かけ算の左辺への計算 | ||
1 | 1+2+3+... | 1+2 | なし | 足し算の左辺へ 3 を足す | なし |
2 | 1+2+3*... | 1+2 | なし | そのまま | 3 を入れる |
3 | 1+2*3+... | 1 | 2 | かけ算の左辺と3の積を求め、足し算の左辺と加える | 消す |
4 | 1+2*3*... | 1 | 2 | そのまま | かけ算の左辺と3の積を求める |
5 | 1*2+3+... | 1*2 | なし | 足し算の左辺に 3 を足す | なし |
6 | 1*2+3*... | 1*2 | なし | そのまま | 3 をしまう |
7 | 1*2*3+... | なし | 1*2 | かけ算の左辺に 3 をかけ、足し算の左辺にする | 消す |
8 | 1*2*3*... | なし | 1*2 | なし | かけ算の左辺に 3 をかける |
このようにすると + と * の演算子を読み込んだ時は、それぞれしなければなら ない処理が違うことがわかります。
このようにすれば優先順位がある数式も処理可能です。
#include <stdio.h>
typedef enum {NUM, OP, END} STATE;
int result=0,mresult=0,num,sign;
char op=' ',mop=' ';
void calcmul(){
switch(mop){
case ' ':
mresult = num*sign;
break;
case '*':
mresult *= num*sign;
break;
case '/':
mresult /= num*sign;
break;
}
}
void calc(){
calcmul();
switch(op){
case ' ':
result = mresult;
break;
case '+':
result += mresult;
break;
case '-':
result -= mresult;
break;
}
mresult=0;
mop=' ';
}
main(){
char c;
STATE s=OP;
while((c=getchar())!=EOF){
switch(s){
case OP:
if((c>'0')&&(c<='9')){
num=c-'0';
sign=1;
s=NUM;
}else if(c=='-'){
num=0;
sign=-1;
s=NUM;
}
break;
case NUM:
if((c>'0')&&(c<='9')){
num*=10;
num+=c-'0';
s=NUM;
}else if((c=='*')||(c=='/')){
calcmul();
mop=c;
s=OP;
}else if((c=='+')||(c=='-')){
calc();
op=c;
s=OP;
}else if(c=='='){
calc();
printf("result = %d\n",result);
return 0;
}
break;
}
}
}
なお、この分析法は簡単ですが、C 言語のように 15 種類も演算の優先度があ るような場合は適応できません(表が膨大になり、。 そのような一般の演算子に関する解析は、構文解析の手法にある演算子 順位法を使います。 これに関してはコンパイラなどの本を参照して下さい。
上記のプログラムに対してカッコの処理を加えて、完全な数式処理を実現しな さい
ある関数から自分自身の関数を呼び出すことを再帰と言います。 これを使うと、短い記述で複雑な問題を解くことができます。
ある問題を解く際に、その問題を同じ性質を持つ小さい部分に分解できるとし ます。 すると、その問題を解く関数 solve は再帰処理を使うと次のように書くこと ができます。
int solve(解くべき問題 t){
if(t がもう分解できない){
t を処理;
return t の答え;
}else{
t1 = t を分解;
ans1 = solve(t1);
分解した残りと ans1 によりtの答を計算;
return t の答え;
}
}
階乗 n!=n*(n-1)*...*2*1 は n!=n*(n-1)! と左辺にも階乗を含む構造に分解 できます。 一方 0!=1 となります。 従って、プログラムは次のようになります。
int kaijo(int n){
if(n==0){
return 1;
}else{
return n*kaijo(n-1);
}
}
このプログラムを使って、 5! と 6! を求めなさい。
ハノイの塔とは次のようなパズルです。 大きさが一回りずつ違う円盤を大きい順に下から並べます。 そして、次のルールでその円盤の山を移動させます。
以下に 3 枚での例を示します。
a | b | c | ||
---|---|---|---|---|
1 |
= == === --a-- |
--b-- |
--c-- |
|
2 |
== === --a-- |
= --b-- |
--c-- |
move 1 from a to b |
3 |
=== --a-- |
= --b-- |
== --c-- |
move 2 from a to c |
4 |
=== --a-- |
--b-- |
= == --c-- |
move 1 from b to c |
5 |
--a-- |
=== --b-- |
= == --c-- |
move 3 from a to b |
6 |
= --a-- |
=== --b-- |
== --c-- |
move 1 from c to a |
7 |
= --a-- |
== === --b-- |
--c-- |
move 2 from c to b |
8 |
--a-- |
= == === --b-- |
--c-- |
move 1 from a to b |
この解法を再帰的に考えます。 もし、hanoi(n-1,'a','b','c') が n-1 枚の円盤を a から b へ移動する解法 を出力するとします(c はもう一つの領域)。 すると、 n 枚の円盤を a から b へ移動する解法は次のように考えられます。
このようにすると hanoi(n,'a','b','c') の解法を出力します。
#include <stdio.h>
hanoi(int n, char x, char y, char z){
if(n==1){
printf("move 1 from %c to %c\n",x,y);
return;
}else{
hanoi(n-1,x,z,y);
printf("move %d from %c to %c\n",n,x,y);
hanoi(n-1,z,y,x);
return ;
}
}
main(){
hanoi(3,'a','b','c');
}
上のプログラムを動かして hanoi(3,'a','b','c') と hanoi(4,'a','b','c') の解法を求めなさい。
組合せの数 nCm の求め方を考えます。 これは n 個の中から m 個を取り出す取り出し方は何通りあるかということで す。 ここで、n 個のものの中から一つのものに注目します。
従って、 nCm = n-1Cm + n-1Cm-1 が得られます。 なお、n 個のものから0 個を取る取り方や、 n 個全部を取り出す取り方はと もに 1 通りしかありません。つまり nC0 = nCn = 1 です。
この式を使って再帰的なプログラムを組み、 5C2、 20C2 を求めなさい。