アルゴリズムと データ構造 第9回 再帰的アルゴリズム.

Slides:



Advertisements
Similar presentations
ハノイの塔 1年9組 馬部 由美絵.
Advertisements

基本情報技術概論 I 演習(第5回) 埼玉大学 理工学研究科 堀山 貴史
4.ソート 4-1.ソート問題について 4-2.簡単なソートアルゴリズム 4-3.高度なソートアルゴリズム
第12回 ソート(3): シェルソート、クイックソート
第11回 整列 ~ シェルソート,クイックソート ~
アルゴリズムとデータ構造1 2005年7月8日
独自ルールを用いた “ハノイの塔”の拡張 立命館高校 3年9組 馬部 由美絵.
データ構造とアルゴリズム論 第6章 探索のアルゴリズム
データ構造と アルゴリズム 理工学部 情報システム工学科 新田直也.
2009/10/9 良いアルゴリズムとは 第2講: 平成21年10月9日 (金) 4限 E252教室 コンピュータアルゴリズム.
プログラミング論 I 関数の再帰呼び出し
第10回 ソート(1):単純なソートアルゴリズム
第2回 内容 ハノイの塔―無向グラフと有向グラフ 教科書の 1.3 節 pp.7-12 参照
情報工学概論 (アルゴリズムとデータ構造)
アルゴリズムとデータ構造 第3回基本的なデータ構造(リスト) 2015/10/28 アルゴリズムとデータ構造 2015.
データ構造と アルゴリズム 第二回 知能情報学部 新田直也.
データ構造と アルゴリズム 知能情報学部 新田直也.
データ構造とアルゴリズム論 第6章 探索のアルゴリズム
アルゴリズムとデータ構造 第2回 線形リスト(復習その2).
①データ構造 ②アルゴリズム ③プログラム言語 ④マークアップ言語
データ構造と アルゴリズム 第八回 知能情報学部 新田直也.
アルゴリズムとデータ構造 補足資料10-2 「nクイーン」
データ構造と アルゴリズム 第十一回 理工学部 情報システム工学科 新田直也.
第11回 整列 ~ シェルソート,クイックソート ~
プログラミング基礎a 第8回 プログラムの設計 アルゴリズムとデータ構造
二分探索木によるサーチ.
データ構造とアルゴリズム論 第8章 再帰処理 平成15年12月2日 森田 彦.
アルゴリズムとデータ構造 2012年6月28日
アルゴリズムとデータ構造 2011年6月27日
関数の定義.
アルゴリズムとデータ構造1 2006年6月16日
データ構造と アルゴリズム第4回 知能情報学メジャー 和田俊和.
プログラミング2 関数の再帰呼び出し
2011/06/21-24 情報知能学科「アルゴリズムとデータ構造」 担当:白井英俊
プログラミング言語論 第五回 理工学部 情報システム工学科 新田直也.
第9回 優先度つき待ち行列,ヒープ,二分探索木
第5回放送授業.
データ構造と アルゴリズム 第五回 知能情報学部 新田直也.
Cプログラミング演習 第10回 二分探索木.
アルゴリズムとデータ構造1 2005年7月5日
関数の再帰呼び出しとは ハノイの塔 リダイレクト レポート課題
プログラミング基礎a 第8回 プログラムの設計 アルゴリズムとデータ構造
プログラミング 4 整列アルゴリズム.
アルゴリズムとデータ構造 2010年7月26日
プログラミング 4 探索と計算量.
情報とコンピュータ 静岡大学工学部 安藤和敏
再帰的手続き.
11.再帰と繰り返しの回数.
2011年度 情報科学&情報科学演習 ~ 定番プログラム(2) ~.
補講:アルゴリズムと漸近的評価.
データ構造とアルゴリズム (第5回) 静岡大学工学部 安藤和敏
第9回 優先度つき待ち行列,ヒープ,二分探索木
アルゴリズムとデータ構造1 2008年7月24日
アルゴリズムとデータ構造1 2006年6月23日
アルゴリズムとデータ構造 2013年7月1日
PROGRAMMING IN HASKELL
計算機プログラミングI 第4回 2002年10月31日(木) 問題解決とアルゴリズム クラスメソッドと手続きの抽象化 最大公約数
第11回放送授業.
アルゴリズムとデータ構造 補足資料9-1 「ハノイの塔」
オブジェクト指向言語論 第五回 知能情報学部 新田直也.
PROGRAMMING IN HASKELL
プログラミング2 関数の再帰呼び出し
参考:大きい要素の処理.
高度プログラミング演習 (07).
プログラミング演習I 2003年6月11日(第9回) 木村巌.
第10回 関数と再帰.
オブジェクト指向言語論 第三回 知能情報学部 新田直也.
計算技術研究会 第5回 C言語勉強会 関数(function)を使う
オブジェクト指向言語論 第六回 知能情報学部 新田直也.
プログラミング論 バイナリーサーチ 1.
Presentation transcript:

アルゴリズムと データ構造 第9回 再帰的アルゴリズム

先週の復習 探索 線形探索 2分探索 ハッシュ法 番兵による線形探索 ハッシュ法によるデータの格納・探索 衝突を処理する方法 チェイン法 オープンアドレス法 再ハッシュ(rehashing) ランダムに処理する方法

先週の演習問題 バケット数5のハッシュ表を考える.ハッシュ関数h(x)= x mod 5 を用いて,空のハッシュ表に数値データ x ={23, 48, 35, 4, 10}を格納する場合について以下の問に答えよ. (問1) チェイン法を用いてデータを格納した場合,ハッシュ表はどうなるか?図を描け. (問2) オープンアドレス法(再ハッシュ)を用いてデータを格納した場合,ハッシュ表はどうなるか?図を描け.

答え 23 mod 5 = 3 48 mod 5 = 3 35 mod 5 = 0 4 mod 5 = 4 10 mod 5 = 0 10 (問1)解答 (チェイン法) 23 mod 5 = 3 48 mod 5 = 3 35 mod 5 = 0 4 mod 5 = 4 10 mod 5 = 0 ポインタの配列 連結リスト 10 35 table[0] table[1] NULLの ところは斜線 バケット数 5の ハッシュ表 table[2] 48 23 table[3] table[4] 4

(問1)なぜ先頭に挿入するの? f c a g b c a f g b 先頭への挿入なら一定時間でできる 挿入位置の計算O(1) table [0] c a [1] 先頭への挿入なら一定時間でできる g b [2] 挿入位置の計算O(n) 末尾はどこ? table [0] c a f [1] g b [2]

再ハッシュ hi (x)= (h(x)+i) mod 5 (問2)解答オープンアドレス法 ハッシュ関数 h(x)= x mod 5 再ハッシュ hi (x)= (h(x)+i) mod 5 h(23) = 23 mod 5 = 3 h(48) = 48 mod 5 = 3 再 h1(48)=(h(48)+1) mod 5 = 4 mod 5 = 4 h(35) = 35 mod 5 = 0 h( 4) = 4 mod 5 = 4 再 h1(4)= (h(4)+1) mod 5 = 5 mod 5 = 0 再 h2(4)= (h(4)+2) mod 5 = 6 mod 5 = 1 h(10) = 10 mod 5 = 0 再 h1(10)= (h(10)+1) mod 5 = 1 mod 5 = 1 再 h2(10)= (h(10)+2) mod 5 = 2 mod 5 = 2 バケット数 5の ハッシュ表 配列 35 table[0] 4 table[1] 10 table[2] 23 table[3] 48 table[4]

            第9回      再帰的アルゴリズム

再帰の基本 再帰(recursive)とは 自然数の定義 再帰関数呼び出し 関数が自分自身をさらに呼び出す  ある事象が,自分自身を含んでいたり,それを用いて定義されていること。 自然数の定義 (a)1は自然数である。 (b) ある自然数の直後の整数は自然数である。 このような定義を再帰的定義(recursive definition)といいます。 再帰関数呼び出し 関数が自分自身をさらに呼び出す 実際は自分自身と同じ関数を「別途」呼び出す

再帰の例:階乗値 階乗n!の定義 10! = 10* 9! 9! = 9 * 8! (a) 0! = 1 数学的帰納法で定義されるような問題は,再帰的となります。 階乗n!の定義 (a) 0! = 1 (b) n! = n × (n – 1)! (n > 0) 10! = 10* 9! 9! = 9 * 8! int factorial(int n) { if (n > 0) return (n * factorial(n – 1)); /* 再帰的関数呼び出し */ else return (1); }

再帰の例:階乗値 再帰関数呼び出しの流れ int factorial(int n ) { if (n > 0) return ( n * factorial(n – 1)); else return (1); } factorial(3) 6 3 2 1 3 * factorial(2) 2 2 * factorial(1) 1 2 3 1 2 1 1 * factorial(0) 1

直接的な再帰と間接的な再帰 直接的な再帰: 自分と同じ関数を呼び出す。 間接的な再帰: 別の関数を呼出し, その関数が自分と同じ関数を呼び出す。 関数A   A(・・) 関数A   B(・・) 関数B   A(・・) 関数A   A(・・) 関数A   A(・・)

2数の最大公約数(greatest common divisor) 再帰の例:ユークリッドの互除法 2数の最大公約数(greatest common divisor) 2数を辺とする長方形を、あまりが出ないよう正方形で埋め尽くす問題 例:12と33 33 12 12 9 9 12 3 3 3 3

再帰の例:ユークリッドの互除法 最大公約数を求める手順 数学的手順 (a) 大きい方の数を小さい方の数で割ったあまりを求める (b) あまりが0になるまで、小さい方の数を大きい方の数、あまりを小さい方の数として繰り返す 数学的手順 最大公約数gcd(x, y)は、yが0ならx、そうでなければgcd(y, x % y) Euclid(Eukleides) ユークリッド(エウクレィデス) (紀元前300年ごろ) 33 12 12 9 9 12 3 3 3 3

再帰の例:ユークリッドの互除法 関数実装例 最大公約数gcd(x, y)は、yが0ならx、そうでなければgcd(y, x % y) int gcd(int x, int y) { if (y == 0) return (x); else return (gcd(y, x % y)); /* 再帰的関数呼び出し */ }

再帰呼出しを使うことのメリット 記述が簡潔になる 理解しやすくなる アルゴリズムの正しさの証明がしやすくなる 計算量の解析が容易になる

再帰的アルゴリズムの作り方 数学的帰納法で証明を書くつもりで! 解くべき問題を、同じ問題でよりサイズの小さな問題を解くことに帰着させる。   (漸化式を立てる)   例) mとn(m≧n)の最大公約数は、nとm%nの最大公約数と同じ。       gcd(m,n)=gcd(n,m%n) for n>0 最小サイズの問題の解を示す。   例) mとn(m≧n)の最大公約数は、m%n=0のときn int gcd(int x, int y) { if (y == 0) return (x); else return (gcd(y, x % y)); } 最小サイズの問題 より小さな問題

再帰アルゴリズムの解析 解析用関数 関数内で2度再帰呼び出し:真に(genuinely)再帰的な関数 int recur(int n) { if (n > 0) { recur(n – 1); printf(“%d\n”, n); recur(n – 2); } 関数内で2度再帰呼び出し:真に(genuinely)再帰的な関数

再帰アルゴリズムの解析 トップダウン法(top-down method) 呼び出し側から始めて段階的に詳細へと調べていく解析手法 recur(3) recur(3) 4 4 recur(2) recur(2) recur(2) recur(2) 3 3 recur(1) recur(1) recur(1) recur(1) 2 2 recur(0) recur(0) recur(1) recur(1) 2 2 recur(0) recur(0) recur(0) recur(0) 1 1 recur(-1) recur(-1) recur(0) recur(0) 1 1 recur(-1) recur(-1) recur(0) recur(0) 1 1 recur(-1) recur(-1)

再帰アルゴリズムの解析 ボトムアップ(bottom-up method)解析(n=4の場合) recur(-1) : (何もしない) → 1 recur(2) : recur(1) 2 recur(0) → 1 2 recur(3) : recur(2) 3 recur(1) → 1 2 3 1 recur(4) : recur(3) 4 recur(2) → 1 2 3 1 4 1 2

再帰を使うべきか 再帰の問題点 再帰にすべきかどうか 関数呼び出しの状態を保存しておくため、膨大なメモリが必要 関数呼び出しのオーバーヘッドなどで、計算時間が増大する可能性大 再帰にすべきかどうか 非再帰で実現できるなら非再帰を用いるべき 非再帰では複雑になりすぎるなら再帰を用いる

何でも再帰呼出しにすれば良いという訳では・・・ gcd(int m,int n) { int a[2]={m,n}, i=1,j; while(a[i]!=0) { j=(i+1)%2; a[j]=a[j]%a[i]; i=j; } return a[(i+1)%2]; gcd(int m,int n) { if( n=0 ) return m; else return gcd(n,m%n); } 再帰呼出しを 使わないと 関数の最初または最後に 1回だけ再帰呼出しされる場合 ×記述は少し複雑       ○メモリ使用量は少ない    (スタック領域の使用量が少ない) ○計算時間も短い  (関数呼出し、復帰処理がない) ループを用いて比較的容易に かける場合が多い

再帰呼出しの時間計算量 再帰的アルゴリズムの時間計算量= 再帰呼出しの時間計算量 + 再帰呼出し以外の時間計算量 (例)    再帰呼出しの時間計算量 + 再帰呼出し以外の時間計算量 (例) プログラム int factorial(int n) { if(n==1) return 1; else return n*factorial(n-1); } factorial(n)の実行時間をT(n)とおくと 再帰呼出しfactorial(n-1)の時間計算量=T(n-1) 再帰呼出し以外の時間計算量≦C (定数) よって   T(n)≦T(n-1) + C, T(1)≦C         T(n-1)≦C(n-1) したがって T(n)≦Cn=O(n)

再帰関数の応用例 ハノイの塔 8王妃問題 

ハノイの塔 (towers of Hanoi) ハノイの塔は、フランスの数学者E・リュカ(Edouard Lucas)が 1883年に考えたものである。リュカは、インドに次のような伝 説があると説明している。 ブラフマーの塔  インドのガンジス河の畔のベナレス(ヴァラナシ)に世界の中心を表すという聖堂がある。そこには3本の大理石の柱(ダイヤモンドの針との説もあり)が立てられており、そのうちの1本には、当初64枚の黄金の円盤が大きい円盤から順に重ねられていたという。バラモン僧たちはそこで、一日中円盤を別の柱に移し替える作業を行っている。そして、全ての円盤の移し替えが終わったときに、この世は崩壊し終焉を迎えると言われている。 もちろんこれはリュカの作り話であるが、64枚の円盤を移動させるには、最低でも18,446,744,073,709,551,615回かかり、1枚移動させるのに1秒かかったとして、約5,845億年かかる(なお、ビッグバンは今から約137億年前の発生とされている)。

ハノイの塔とは 固定された3本の柱のうちの1本に、下へ行くほど半径の大きい円盤 以下のルールで別の1本に円盤をすべて移動 重なった円盤を3本柱の間で移動する問題(3枚の場合) (より小さい円盤が必ず上にくるように重ねる) 1 2 3 固定された3本の柱のうちの1本に、下へ行くほど半径の大きい円盤 以下のルールで別の1本に円盤をすべて移動 1回に動かせる円盤は1枚 半径の小さい円盤の上に半径の大きい円盤は重ねられない 棒以外の部分には円盤は置けない

ハノイの塔 移動例(3枚の場合) 3 2 1 5 4 6 7 円盤の移動回数: 1 1 2 1 2 3 1 2 3 1 開始軸 中間軸 円盤の移動回数:  3 2 1 5 4 6 7 1 1 2 1 2 3 1 2 3 1 開始軸 中間軸 目的軸

ハノイの塔 ハノイの塔の解法 底の円盤を除いた円盤グループを開始軸から中間軸に移動 底の円盤を開始軸から目的軸に移動 円盤グループを中間軸から目的軸に移動 1.と3.で再帰呼び出し

ハノイの塔 関数実装例 void hanoi(int no, int x, int y) { if (no > 1) hanoi (no - 1, x, 6 - x - y); printf(“[%d]を軸%dから軸%dへ移動\n”, no, x, y); hanoi (no - 1, 6 - x - y, y); } ・1 : 開始軸、2 : 中間軸、3 : 目的軸

8王妃問題とは 8x8のチェス盤に、8個の王妃を互いに取られないように配置 王妃は縦横斜めいずれのライン上のコマも取ることができる ラインにも別の王妃がないように 配置しなければならない 例

8王妃問題 王妃の配置 何も考えないと、64x63x62x61x60x59x58x57 = 178,462,987,637,760通り   = 178,462,987,637,760通り 各列に王妃を1個のみ配置と限定すると、   8x8x8x8x8x8x8x8 = 16,777,216通り 各行にも王妃を1個のみ配置と限定すると・・・簡単には計算できない

王妃の配置 flagを導入し、すでに配置されている行のflagを立てておく flagの立っている行には配置作業を行わない 必要のない枝分かれを抑制して、不要な組み合わせの列挙を省略:分枝限定法 注意点: 1. 南西斜め筋はcol+rowが一定! N-王妃問題を解くためのデータ構造 2. 南東斜め筋はcol-rowが一定! 次の四つの配列で表現: 配列1: 各行に王妃が配置済みか flag_a[row] その行に王妃がいるときにFALSE、いないときにTRUE 配列2: 右上がり(/)方向に王妃が配置済みか flag_b[col+row] 配列3: 右下がり(   )方向に王妃が配置済みか        flag_c[col-row+N-1] 配列4: 各列の王妃の位置        pos[col]

分枝限定法よる解法 Step 1. 配列: pos[col]=row に、とりあえず王妃を置いてみる。 pos[0]=0 効き筋を埋める 考え方: とりあえず、置いてみる。 行き詰ったら、前に戻って、 別の選択肢でやってみる。 Step 1. 配列: pos[col]=row に、とりあえず王妃を置いてみる。 pos[0]=0 効き筋を埋める 次の候補は A, B, C Step 2: とりあえずA pos[1]=2 に置いてみる 効き筋チェック 次の候補はD Step 3:pos[2]=4 に置いてみる この場合にはうまくいっていますが、次の候補がなければキャンセル して前に戻る(バックトラック) 効き筋チェック

8王妃問題を解くプログラム バックトラック

まとめ 再帰の基本 再帰関数呼び出し 再帰呼出しを使うことのメリット 再帰的アルゴリズムの作り方 再帰呼出しの時間計算量 再帰関数の応用例 ハノイの塔 8王妃問題 

演習問題