cp-9. 再帰関数
(C プログラミング入門)
URL: https://www.kkaneko.jp/pro/adp/index.html
1
金子邦彦
局所変数を使った関数の理解、再帰呼び出しによる
漸化式の計算
【学習内容の構成】
1. スタック:配列とスタックポインタによる
push/pop操作の実現
2. 局所変数と大域変数:関数内部でのみ有効な変
数と、複数関数で共有される変数の区別
3. 再帰関数:自分自身を呼び出す関数による総和
計算
4. 関数呼び出しの流れ:引数の受け渡しと戻り値
の仕組み
• 前提:C言語の関数、配列の基本
• 意義:データ構造の実装手法と再帰的思考の習得
2
• スタックのプッシュ(push), ポップ(pop) を行う
関数を作る
• 次の2つの大域変数を使うこと
1. 配列stack
2. スタックポインタtop
3
例題1.スタック
スタックのプッシュとポップ
• プッシュ
(入れる操作)
• ポップ
(出す操作)
すでに
入っていた
データ
積み重なる
ように入る
すでに
入っていた
データ
一番最後に入った
データが先に出る
4
スタックとは
• 入れた順に積みあがっていく
1 1
2
3
2
1
2
1
2
1
4
push 1
push 2 push 3 pop push 4
5
#include <stdio.h>
int stack[100];
int top=0;
void push(int n)
{
stack[top]=n;
top++;
return;
}
int pop()
{
int n;
top--;
n = stack[top];
return n;
}
push関数
pop関数
6
スタック
main 関数の例
int main()
{
push(1);
push(2);
push(3);
printf( "%d¥n", pop() );
push(4);
printf( "%d¥n", pop() );
printf( "%d¥n", pop() );
printf( "%d¥n", pop() );
return 0;
}
7
スタック
実行結果の例
3
4
2
1
8
関数呼び出しの流れ
main 関数
int main()
push(1);
push(2);
push(3);
printf( "%d¥n", pop() );
push(4);
printf( "%d¥n", pop() );
printf( "%d¥n", pop() );
printf( "%d¥n", pop() );
関数呼び出し
戻り
push 関数
void push( int n )
return;
戻り
pop 関数
int pop()
return n;
9
配列によるスタックの実現
• スタック
• 十分に多くのデータを格納できる配列stackを用意.
• スタックポインタ
• 現在の「空き部分の底」を示す値(スタックポイ
ンタ)を格納させる変数topを用意.
• スタックの底は stack[0],スタックの頂上は
stack[top-1]
配列
スタックの底 stack[0]
スタックの頂上 stack[top-1]
空
空き部分の底 stack[top]
10
配列によるスタックの実現
• push 時: pushの後に,topの値を1足す
• pop 時: popの前に,topの値を1引く
1 1
2
3
2
1
2
1
2
1
4
push 1
push 2 push 3 pop push 4
1
top
2 3 4 3 4
11
局所変数
• 関数の仮引数と,関数本体内で宣言された変
数のこと
• 関数の内部でだけ有効
• ほかの関数(main関数など)からは直接アクセス
できない
• ある関数の局所変数と,呼び出した関数中の
局所変数と,例え同じ名前であっても,別の
実体である
12
大域変数
• 関数の外側で宣言された変数のこと
例) 配列stackとスタックポインタtopは,push
関数とpop関数のいずれからも参照できる.
#include <stdio.h>
int stack[100];
int top=0;
int push(int n)
{
stack[top]=n;
top++;
return n;
}
int pop()
{
int n;
top--;
n = stack[top];
return n;
}
これが大域変数
push関数と
pop関数で
共有される
13
局所変数と大域変数
局所変数 大域変数
宣言の場所
関数の仮引数
または関数内
関数の外側
有効範囲
関数の内部だ
けで有効
複数の関数で
共有される
14
スタックの使用例
• 正整数を読み込んだら,pushする.
• 負整数を読み込んだら,popして,取り出された値
を表示する.
• 0が読み込まれるまで上の操作を繰り返す.
15
#include <stdio.h>
#pragma warning(disable:4996)
int main()
{
int item;
top= -1;
do {
printf("Enter a number ");
scanf("%d",&item);
if (item>0) {
printf("pushed: %d¥n",item);
push(item);
} else if (item<0) {
printf("popped: %d¥n",pop());
}
} while (item!=0);
return 0;
}
16
課題1.スタック操作の例外処理
スタック操作のプログラムにおいて,
push 関数と pop 関数を書き換えて,
次に挙げた,2つの例外事象の対処を考慮しなさい.
1.スタックが空のときにポップ(pop)しない.
スタックが空のときに pop 関数が呼び出されると,「Stack
empty」のエラーメッセージを表示すること
2.スタックが満杯のときにプッシュ(push)しない.
スタックが満杯のときに push 関数が呼びだされると,
「Stack full」のエラーメッセージを表示すること
17
課題1のヒント
配列
スタックポインタの
値は 「0」
空のとき 満杯のとき
スタックポインタの
値は,「配列のサイズ」
•スタックポインタは,現在の「空き部分の底」の
位置を示す値 → 「スタック内のデータ数」に等しい
18
例題2.再帰関数による総和
• 整数Nから,1からNまで総和を求める再帰関数
を作る
例) 5 → 15
• 再帰関数とは,関数 f の本体にf の呼出しを含むよ
うな関数のこと
+
=
=
−
=
=
otherwise
1 if1
1
1
1
n
i
n
i
in
n
i
19
再帰関数とは
• 自分自身を呼び出すような関数のこと
• 必ず終了条件が成立するように気をつけること
20
#include <stdio.h>
#pragma warning(disable:4996)
int sum(int n)
{
if (n < 1) {
return 0;
}
if (n == 1) {
return 1;
}
else {
return n+sum(n-1);
}
}
int main()
{
int n;
int s;
printf("Enter a number: ");
scanf("%d",&n);
s = sum(n);
printf("sum(%d)=%d¥n",n ,s);
return 0;
}
sum関数
main関数
21
再帰関数による総和
実行結果の例
Enter a number: 2
sum(2)=3
22
関数呼び出しの流れ
(main 関数で n = 2 のとき)
main 関数
int main()
sum(n);
関数呼び出し
sum 関数
int sum( int n )
return n + sum(n-1);
関数呼び出しと戻
り
sum 関数
int sum( int n )
return 1;
戻り
23
n の値の変化
(main 関数で n = 2 のとき)
main 関数
int main()
sum(n);
関数呼び出し
sum 関数
int sum( int n )
return n + sum(n-1);
関数呼び出しと戻
り
return
1;
戻り
n = 2
sum 関数
int sum( int n )
n = 1
1を返す
3を返す
24
再帰関数による総和
n == 1
No
Yes
return 1;
return n + sum(n-1);
25
プログラム実行順
(main関数で n=2 のとき)
s = sum( n );
main 関数
int main()
n ==1
return n + sum(n-
1);
No
Yes
return 0;
sum 関数
int sum( int n )
関数
呼び出し
戻り
printf( "Enter a number:" );
scanf( "%d", &n );
return 0;
printf("sum(%d)=%d¥n",n ,s);
n ==1
return n + sum(n-
1);
No
Yes
return 1;
sum 関数
int sum( int n )
関数
呼び出し
戻り
26
データの流れ
s = sum( n );
return n + sum(n-1);
関数
呼び出し
戻り
① n の値を,
sum 関数に渡す
④返された値を
s に格納する
②渡された値は,
「n」という名前で使う
③ n-1 の値を,
sum 関数に渡す
⑤値「3」を返す
n
2
プログラム
データ
プログラム
main 関数
int main()
bar関数
int sum( int n )
n
2
データ
1
return1;
プログラム
bar関数
int sum( int n )
n
データ
1
④渡された値は,
「n」という名前で使う
関数
呼び出し
⑤値「1」を返す
戻り
27
return n+sum(n-1);
int sum(int n)
{
if (n < 1) {
return 0;
}
if (n==1) {
return 1;
}
else {
return n+sum(n-1);
}
}
呼び手
関
数
本
体
戻り値の受け取り
引数を仮引数にセットする
28