情報処理Ⅱ 第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 2024 ExpyDoc