情報処理Ⅱ
第8回:2003年12月9日(火)
前回の講義の見直し
識別子の宣言(関数・変数の定義)に関する補足
constを含む変数と代入
識別子の宣言に関する補足
グローバル区間に同一の識別子を複数宣言できない.
ブロック内(ローカル)に変数を定義するときは,それ
より外の識別子と重複してもよい.
ブロック内では,重複する外の識別子は使用できない.
モジュール化に関して有用なルール.
関数はブロック内で定義できない.
関数と同じ名前の変数もNG.
必ずグローバル区間での定義となる.
グローバル - global - 大域的 - あらゆるブロックの外
ローカル - local - 局所的 - あるブロックの中
constを含む変数と代入
constを含む型について,オブジェクトの参照はOK.
代入など,左辺値としての使用はよくない(警告).
ポインタの代入について
const char *p; char *q;としたとき,
constなしからconstあり(p = q;)はOK.
constありからconstなし(q = p;)はよくない(警告).
キャスト演算子による変換(q = (char *)p;)はOK.
記憶域クラスには適用できない(型の一部ではない).
x = (static int)y; はNG(コンパイルエラー).
本日のテーマ:再帰(recursion)
Cでは,関数の内部で自分自身を呼び出す(再帰呼び出
し,recursive call)ような記述ができる.
再帰的(帰納的,循環的)な定義の実装や,バックト
ラック(backtrack),分割統治法(divide-and-conquer)と
いった手法に対してよく用いられる.
注意点
無限ループにならないようにする.
再帰を使わないほうがよいこともある.
例題1
カウントダウンするプログラム
自作関数countdownの定義の中で,countdownを呼び出
している.
例題1のプロセス状態は?
main
num = 3
countdown
x=3
countdown
x=2
countdown
x=1
同一の変数名(x)に対して
複数のオブジェクトが生
成される点に注意.
countdown
x=0
例題1のバリエーション
出力の位置を変えると,countupになる.
再帰なしのプログラムのほうが,効率はよい.
なぜ再帰なしのほうが効率がよいか? …関数呼び出しのコ
ストが大きいから.
「オーバーヘッド」という
再帰的な定義の例(1)
階乗
(n≧2)
フィボナッチ数列(例題2)
0! = 1, 1! = 1
n! = n×(n-1)!
a1 = 1, a2 = 1
an= an-1 + an-2
(n≧3)
再帰を用いれば,シンプルに書ける.
しかしこれらも,whileループのほうが効率がよい.
再帰的な定義の例(2)
UNIXのファイル構造
「ディレクトリ」には「ファイル」を配置できる.
ファイルには,「通常ファイル」と「ディレクトリ」があ
る(実際にはもっとさまざまな種類のファイルがある).
ディレクトリは「親ディレクトリ」をちょうど1つ持つ.
etc
/
home
usr
X11
Sessions
hosts
XF86Config
resolv.conf
XF86Config-4
そこで問題
あるディレクトリから下のすべてのファイル名を出力す
るには?
ディレクトリが持つファイルを(1個1個,順に)獲得す
し,そのファイル名を出力する.
それがディレクトリであれば,(再帰的に)そのディレク
トリから下のすべてのファイル名を出力する.
本質的にこれは「木(tree)の探索問題」であり,さまざ
まなアルゴリズムが知られている.
例題3:ぷよ連結問題
右図のような(2次元)
フィールドと,その中の
座標を入力に取り,そこ
と連結する「ぷよ」の個
数を求めよ.
フィールドの表現
フィールドは2次元 ⇒2次元配列を使用
色は赤・青・黄と空白 ⇒int型で表現
int field[高さ][幅] = {
0, 0, 3, 0, 0, 0,
1, 3, 2, 0, 0, 1,
3, 2, 3, 2, 2, 2,
3, 2, 2, 3, 3, 3,
1, 1, 2, 3, 1, 3,
1, 1, 2, 2, 3, 3,
};
空白…0
…1
…2
…3
再帰の使い方
関数プロトタイプは int consize(int x, int y, int color);
(x, y)と連結していて色がcolorである「ぷよ」の数を返す.
フィールド情報は,関数の「外」から与える.
戻り値は,
(探索打ち切りの)条件を満たすときは,0
そうでなければ,
1
+ consize(x + 1, y, color)
+ consize(x - 1, y, color)
+ consize(x, y + 1, color)
+ consize(x, y - 1, color)
: 探索の方向
探索打ち切りの条件
以下のいずれか
(x, y)が範囲外
color == 0
field[y][x] != color
(x, y)の地点は探索済み
探索済みかどうかを記憶するための領域も必要.
int field_checked[高さ][幅]; で確保する.
分割統治法
対象領域を細分化して求め(分割),全体として正しい
解になる(統治)ようにする.
例: クイックソート
「4より小」,
「4と等しい」,
「4より大」に
分ける.
4 7 2 6 1 8 3 5
2 1 3 4 7 6 8 5
1 2 3 4 6 5 7 8
1 2 3 4 5 6 7 8
まとめ
再帰的に定義された問題は,再帰呼び出しを用いてプロ
グラムを記述するとうまくいきやすい.
一般に,再帰を用いないほうが効率的である.
関数内のauto変数は,関数呼び出しごとに生成される点に
注意.
レポートの補足
「レポートの書き方」を作成したので参考にしてほしい.
http://www.wakayama-u.ac.jp/~takehiko/ipii2003/report.pdf
講義のページからリンクしている.
発展的な話題:
再帰は常に解消できるか?
結論から言うと,可能.
方法は…
反復に置き換える.
「スタック」や「リスト」と呼ばれるデータ構造を用いる.
簡単な数列の問題は,これで可能.
木の探索,ぷよ連結問題,クイックソートともこれで可能.
注意点
自然でない記述になるため,バグが入りやすい.
かける手間に対して効率がよくなるかの検討が必要.
発展的な話題:
自分自身を呼び出さない再帰
関数 x が関数 y を呼び出し,関数 y が関数 x を呼び出す場合も再帰
という.
例: 再帰下降構文解析
「式」は,「項」,「項+項」,「項+項+項」… のいずれかにより
構成される.
「項」は,「因数」,「因数*因数」,「因数*因数」,「因数*因数*
因数」…のいずれかにより構成される.
「因数」は,「(式)」もしくは「定数」により構成される.
5 や 1+2*3 はこの意味で式であるが,2++ や (4)) は式ではない.
どうすれば判定(解析)できるか?
⇒ 文字列(の先頭位置)を入力に取って,式,項,因数,定数
をそれぞれ解析する関数を定義し,必要に応じて互いを呼び出
し合えばよい.
© Copyright 2026 ExpyDoc