アルゴリズムとデータ構造 第1章 アルゴリズムと計算量 4月22日 情報知能学科 白井英俊 4月15日の課題 4. 三つの要素(いずれも数)をもつ配列aの中身 を、小さいものから大 きいものになるよう並び かえた配列を返す関数seiret3の定義 例: a = [1,2,3] とすると seiret3(a)は [1,2,3] を返し、 a = [20,10,30] とすると seiret3(a)は [10,20,30] を返 し、 a = [33,22,11] とすると seiret3(a)は [11,22,33] を返す 4月15日の課題(4)続 4. 三つの要素(いずれも数)をもつ配列aの中身 を、小さいものから大 きいものになるよう並び かえた配列を返す関数seiret3の定義 考え方: 3つの要素(仮にx,y,zとする)を小さい ものから大きいものに並び替えるとすれば6通 りの可能性がある( 3! = 6) (1) x ≦ y ≦ z (2) x ≦ z ≦ y (3) y ≦ x ≦ z (4) y ≦ z ≦ x (5) z ≦ x ≦ y (6) z ≦ y ≦ x 4月15日の課題(4)続 4. 三つの要素(いずれも数)をもつ配列aの中身 を、小さいものから大 きいものになるよう並び かえた配列を返す関数seiret3の定義 考え方: まずxとyの大きさを比べる… は x ≦ y の場合の可能性 (1) x ≦ y ≦ z (2) x ≦ z ≦ y (3) y ≦ x ≦ z (4) y ≦ z ≦ x (5) z ≦ x ≦ y (6) z ≦ y ≦ x 4月15日の課題(4)続 4. 三つの要素(いずれも数)をもつ配列aの中身 を、小さいものから大 きいものになるよう並び かえた配列を返す関数seiret3の定義 考え方: まずxとyの大きさを比べる… 次にx と zの大きさを比べる(x ≦ z の場合) (1) x ≦ y ≦ z (2) x ≦ z ≦ y 最後にy と zの大きさを比べて(1)か(2)が決まる (5) z ≦ x ≦ y 4月15日の課題(4)続 4. 三つの要素(いずれも数)をもつ配列aの中身 を、小さいものから大 きいものになるよう並び かえた配列を返す関数seiret3の定義 if (x <= y) # xとyの大きさを比べる… if ( x <= z) # 次にx と zの大きさを比べる if (y <= z) # 最後にy と zの大きさを比べる return [x,y,z] # x<= y<= z else return [x,z,y] # x<= z <= y end #if (y<=z) これにならって他の場合を書きこむ! end # if (x<=z) end # if (x<=y) 4月15日の課題(4)続 4.三つの要素(いずれも数)をもつ配列aの中身を、小さいもの から大 きいものになるよう並びかえた配列を返す関数seiret3 if (x <= y) #x≦y if ( x <= z) # x ≦ y , x ≦ z if (y <= z) # x ≦ y , x ≦ z, y≦z return [x,y,z] else # x ≦ y , x ≦ z, y > z return [x,z,y] end #if (y<=z) else # x ≦ y かつ x > z return [z,x,y] end # if (x<=z) end # if (x<=y) 4月15日の課題(4)続 4.三つの要素(いずれも数)をもつ配列aの中身を、小さいもの から大 きいものになるよう並びかえた配列を返す関数seiret3 if (x <= y) #x≦y if ( x <= z) # x ≦ y , x ≦ z if (y <= z) # x ≦ y , x ≦ z, y ≦ z return [x,y,z] # x<= y<= z else return [x,z,y] # x<= z <= y end #if (y<=z) else # x ≦ y , x > z return [z,x,y] end # if (x<=z) else #x>y この場合について考えみよう end # if (x<=y) 4月15日の課題(4)続 「else」のところに処理が来るのは、 (1) x ≦ y ≦ z (2) x ≦ z ≦ y (3) y ≦ x ≦ z (4) y ≦ z ≦ x (5) z ≦ x ≦ y (6) z ≦ y ≦ x (なぜなら… (1),(2),(5)は処理済み (return)) これらのうちどの場合かを判別する コードをかけばよい! (いろいろな方法があるけれど…) 9 4月15日の課題(4)続 (3) y ≦ x ≦ z (4) y ≦ z ≦ x (6) z ≦ y ≦ x このように2つに分けるとすれば、 そのどちらであるかを見分ける条件文は? 答: if (y <= z) #y≦z # (3)か(4)の場合 注意: (4)+(6)と(3) という分け方も可能 else そのための条件式 # (6)の場合 はどうなるか? 10 end 4月15日の課題(4)続 (3) y ≦ x ≦ z (4) y ≦ z ≦ x (6) z ≦ y ≦ x さらに(3)と(4)を分ける: if (y <= z) # y≦z if (x <= z) # y≦z, x≦z return [y,x,z] else # y≦z, x > z return [y,z,x] end # if (x<=z) else #y>z return [z,y,x] end # if (y<=z) 11 4月15日の課題(4)続 4. 三つの要素(いずれも数)をもつ配列aの中身を、小さいものから大 きい ものになるよう並びかえた配列を返す関数seiret3の定義 if (x <= y) #x≦y if ( x <= z) # x ≦ y, x ≦ z if (y <= z) # x ≦ y, x ≦ z, y≦z return [x,y,z] elsif (y <= z) # y<x, y≦z else # x ≦ y, x ≦ z, y>z if ( x <= z) # y<x, y≦z,x≦z return [x,z,y] return [y,x,z] end #if (y<=z) else # y<x, y≦z, x > z else # x ≦ y, x > z return [y,z,x] return [x,z,y] end #if (x<=z) end # if (x<=z) else # y < x , z < y return [z,y,x] end ちょっと条件分岐について一言 これらは同じ処理 if (x <= y) 処理 else if (x <= z) return [y,x,z] else return [y,z,x] end # if (x<=z) else return [z,y,x] end # if (y<=z) end if (x <= y) 処理 elsif (x <= z) return [y,x,z] else return [y,z,x] end # if (x<=z) else return [z,y,x] end 13 練習問題(1) • n個の実数x1, x2, …, xnが与えられて、その中 の最小値を求める (1)アルゴリズムを書く (2)プログラムを書く (3)適当なデータを与えて、プログラムを実行して みる。そして最小値を返すことを確認する。 14 練習問題(2) • n個の実数x1, x2, …, xnが与えられて、その中 の2番目に大きな数を求める (1)アルゴリズムを書く (2)プログラムを書く (3)適当なデータを与えて、プログラムを実行して みる。そして2番目に大きな数を返すことを確 認する。 15 課題(3と4は提出を求める宿題) • プログラムを書くために、Rubyを復習しよう • 次の問題にチャレンジしてみよう (1)最古のアルゴリズム(ユークリッドの互除法)をプ ログラム化する (2) ファイルから実数を読み込み、その中の最大値 を答える(つまり、数がファイルに書いてある) (3) 最大値と最小値からなる配列を返す (4) 最大値と最小値からなる配列を答えるだけでは なく、それらが何番目の要素であるかも答える 宿題の提出期限は4月28日(木曜日)まで。あて先はウェブページ 16 参照 プログラム課題提出の注意 • (これこれの問題を解く)「プログラムを書け」、もしく は「アルゴリズムを書け」という課題が多く出される • この時に求められているのは「プログラム(やアルゴ リズム)」だけではないことに注意 • 要求事項がちゃんと満たされていることも示す(検 証する)必要がある • そのために、プログラムの中身、その実行例(複数 個の例題)、実行結果の吟味(ちゃんと目的を果たし ているかどうか)を示す 17 ユークリッドの互除法 入力:正整数p, q 出力:pと q の最大公約数 手続き: 1. q > p ならば、 p と q の値を入れ替える 2. r ← (p の q による剰余) 3. r = 0 ならば、q が最大公約数(終了) さもなくば、 p ← q, q ← r として、1へ。 p = 72, q = 30 として、試してみよう 18 基本的な用語の確認 整数xが整数aの約数 ⇔ aがxで割り切れる • aとbの公約数:aもbも正整数であることが前提 aの約数とbの約数で共通する数のこと 例: 30 の約数は 1, 2, 3, 5, 6, 10, 15, 30 72の約数は 1, 2, 3, 4, 6, 8, 9, 12, …, 36, 72 • aとbの最大公約数(GCD):aとbに共通する公 約数のうち最大のもの 30と72の最大公約数(これをGCD(30,72)と書く) は… 19 ちょっと休憩 20 前回の練習:『最小値』を求める 問題:「n個の数x1, x2, …, xnが与えられたときに、その 『最小値』を求める」アルゴリズムとプログラムを書く。 「最大値」の要素を求めるアルゴリズムがヒント 最小値 確認:『最大値』の要素を求める min(n,x) def max(n,x) y = x[0] for i in 1..(n-1) if (x[i] >< y) y = x[i] end # if end # for return y end # def Algorithm MIN MAX(n, x1, x2, …, xn): 入力(Input): 正整数 n と n 個の実 数x1, x2, …, xn 出力(Output): x1, x2, …, xnの中 の最大値 最小値 手続き(Procedure): 1. y ← x1 ; 2. for i ← 2 until n do if xi >< y then y ← xi ; 3. return y; 練習問題2の解説 • 問題:実数x1, x2, …, xnが与えられて、これら を大きい順に並べた時に2番目に来る値を求 めるアルゴリズムを作れ。 • 注意:「実数x1, x2, …, xnを大きい順に並べ ろ」とは書いていない。出力すべき値の記述 でしかないことに注意。 • 教科書の解答例1(p.183)によれば1行で書け る…しかし、これはこの課題の狙いではない • 教科書の解答例2を見てみよう: 練習問題2の解説(続) • 問題:実数x1, x2, …, xnが与えられて、これらを大きい順に並 べた時に2番目に来る値を求める • 教科書による解答:「最大値を求めるアルゴリズムの拡張」 入力: 自然数nと、n個の実数x1, x2, …, xn 出力: x1, x2, …, xnのうち2番目に大きな値 手続き: 1. y ← Max(x1, x2) ; z ← Min(x1, x2) # 先頭2要素の大と小 2. for i ← 3 until n do # 三番目の要素から調べる if (xi > z) then # 2番目の要素よりもxiが大きければ if (xi > y) then z ← y and y ← xi else z ← xi # 更新 3. return z 練習問題2の解説:プログラムへ このアルゴリズムも「関数」 として記述する。 関数の名前をsecondとする。 したがって、概略は: def second(n,x) … end というようになる。ここでnは 自然数、xはn個の実数を要素 とする配列 入力: 自然数nと、n個の実数 x1, x2, …, xn 出力: x1, x2, …, xnのうち2番 目に大きな値 手続き: 1. y ← Max(x1, x2) ; z ← Min(x1, x2) 2. for i ← 3 until n do if (xi > z) then if (xi > y) then z ← y and y ← xi else z ← xi 3. return z 練習問題2解説:プログラム(続) 入力: 自然数nと、n個の実数x1, 手続きの部分をプログラムに x2, …, xn する。 出力: x1, x2, …, xnのうち2番目に 1のステップは、二つの実数 大きな値 から最大値(max)と最小値 手続き: (min)をそれぞれyとzに代入 1. y ← Max(x1, x2) ; なぜx[0]とx[1]を比 よって z ← Min(x1, x2) 較しているのか? if x[0] > x[1] 2. for i ← 3 until n do y,z = x[0],x[1] if (xi > z) then else if (xi > y) then z ← y and y,z = x[1],x[0] y ← xi else z ← xi end 3. return z とすれば目的は達成される 練習問題2解説:プログラム(続) 2のステップは、素直にかける: for i in 2..(n-1) if (x[i] > z) if (x[i] > y) z, y = y, x[i] else z = x[i] end # if end # if end #for 3は簡単。 入力: 自然数nと、n個の実数x1, x2, …, xn 出力: x1, x2, …, xnのうち2番目に 大きな値 手続き: 1. y ← Max(x1, x2) ; z ← Min(x1, x2) 2. for i ← 3 until n do if (xi > z) then if (xi > y) then z ← y and y ← xi else z ← xi 3. return z 練習問題2の解説:プログラム 以上、まとめて: def second(n,x) if x[0] > x[1] y,z = x[0],x[1] else y,z = x[1],x[0] end # if for i in 2..(n-1) if (x[i] > z) if (x[i] > y) z, y = y, x[i] else z = x[i] end # if end # if end #for return z end #def 入力: 自然数nと、n個の実数x1, x2, …, xn 出力: x1, x2, …, xnのうち2番目に 大きな値 手続き: 1. y ← Max(x1, x2) ; z ← Min(x1, x2) 2. for i ← 3 until n do if (xi > z) then if (xi > y) then z ← y and y ← xi else z ← xi 3. return z 練習問題2のプログラムの検証 • プログラムを書いただけで安心してはいけな い。具体的なデータを与えて、検証してみよう。 その全体は以下のようになるだろう: def second(n,x) # 先ほどのsecond関数の定義 end # 検証用のデータ x = [0.3, 0.5, 1.1, 0.8, 1.3, 0.4] print second(x.size,x) 練習問題3 問題:実数x1, x2, …, xnが与えられて、これらを 小さい順に並べた時に2番目に来る値を求め るプログラムを作れ。 30 ファイルからの読み込み • 「ファイルから実数を読み込み、その中の最大値を 答える」問題について • 『最大値』アルゴリズムがあるのだから、この問題は、 それを使って解くのが良い。 • つまり、ファイルから「最大値を求める」対象の配列 を作ればよい。そうすれば「最大値」アルゴリズムを 使って答えが求められる。 • したがって、考えるべきは、ファイルに書いてある数 を読み込み、それから配列を作る方法 確認:『最大値』の要素を求める def max(n,x) y = x[0] for i in 1..(n-1) if (x[i] > y) y = x[i] end # if end # for return y end # def Algorithm MAX(n, x1, x2, …, xn): 入力(Input): 正整数 n と n 個の実 数x1, x2, …, xn 出力(Output): x1, x2, …, xnの中 の最大値 手続き(Procedure): 1. y ← x1 ; 2. for i ← 2 until n do if xi > y then y ← xi ; 3. return y; 問題:ファイルから数を読み込み… Ruby (に限らず多くのプログラミング言語)では、 ファイルから数を読み込むには 書き込むために『開く」 のもある 1. ファイルを読み込み用に「開く」(openする、 実は、一字ずつ取り出すなど、い という) ろいろできる 2. ファイルから一行ずつ「文字列」を取りだす 3. 取り出した文字列を数に変換する 4. 変換した数を集めて配列にする とすればよい 覚えていますか?…Rubyの操作(1) 1.ファイルを読み込み用に「開く」 例: inFile = open(“realNumebrs.txt”,”r”) 開いた結果を変数(ここでは inFile)に記憶するのを 忘れないこと 覚えていますか?…Rubyの操作(2) 2. ファイルから一行ずつ「文字列」を取りだす 例1: 一回限りの取り出しなら y = inFile.gets これを使ってまとめて取り出す方法: z = [ ] # 空っぽの配列を作っておく while (y = inFile.gets) z << y # 配列の後に付け足す z.push(y) end 例2: 別なやりかた: z = inFile.readlines 覚えていますか?…Rubyの操作(3) 3&4. 取り出した文字列を数に変換し、配列に yが文字列とすると、 y.to_f to_f は「実数」にする 2のgetsの方式と組み合わせると、to_i は「整数」にする z = [ ] # 空っぽの配列を作っておく while (y = inFile.gets) z << y.to_f # 配列の後ろに 入れる end y.to_f >> z は駄目! 2のreadlinesの場合は…(自分で考えよう) プログラムの完成 # ファイルにある数の最大値を求める def max(n,x) # 最大値を求めるプログラムをここに挿入 end # ファイルを開いて数の配列を作る inFile = open(“realNumbers.txt”,”r”) x = [ ] # 空っぽの配列を用意 while (y = inFile.gets) # ファイルから一行ずつ読み込む x << y.to_f # 読み込んだ文字列を実数にしてxに追加 end # while inFile.close # ファイルを『閉じる』 print max(x.size,x) 参考:ファイル名を引数とする関数に def max(n,x) # 最大値を求めるプログラムをここに挿入 end # ファイルを引数とする関数にする def findMaxInFile(f) inFile = open(f, “r”) x = [ ] # 空っぽの配列を用意 while (y = inFile.gets) # ファイルから一行ずつ読み込む x << y.to_f # 読み込んだ文字列を実数にしてxに追加 end # while inFile.close # ファイルを『閉じる』 print max(x.size,x) end # def # findMaxInFile(“realNumbers.txt”) 再度:アルゴリズムとは • コンピュータ(や人間)が計算をするための手順 • ユークリッドの互除法:最大公約数を求めるアルゴリ ズム • aとbの公約数:aもbも整数であることが前提 aの約数とbの約数で共通する数のこと 例: 30 の約数は 1, 2, 3, 5, 6, 10, 15, 30 72の約数は 1, 2, 3, 4, 6, 8, 9, 12, …, 36, 72 • aとbの最大公約数(GCD):aとbに共通する公約数 のうち最大のもの 30と72の最大公約数(これをGCD(30,72)と書く)は 6 39 再度:アルゴリズムとは(続き) • ユークリッドの互除法 (最古のアルゴリズム) 入力:正整数p, q 出力:pと q の最大公約数 pをqで割った余り 手続き: 1. q > p ならば、 p と q の値を入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約数(終了) さもなくば、 p ← q, q ← r として、1へ。 p = 72, q = 30 として、試してみよう 40 ユークリッドの互除法を試す • プログラムを作る前に、自分でやってみることが大事 p = 72, q = 30 として ステップ1: p > qなので次 ステップ2: r ←12 ステップ3: r != 0 なので p←30, q←12 としてステップ1へ ステップ1: p > qなので次 ステップ2: r ←6 ステップ3: r != 0 なので p←12, q←6としてステップ1へ ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を 入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約 数(終了) さもなくば、 p← q, q ← r とし て、1へ。 ステップ1: p > qなので次 ステップ2: r ← 0 ステップ3: r == 0 なので qの値が最大公約数 41 参考:ユークリッドの互除法の仕組み なぜ、ユークリッドの互除法で最大公約数が求まるか? • 入力p,qは「正整数」なので必ず最大公約数がある(たとえ それが1であっても!) • それを m で表すと、p = a*m, q = b*m と表される(しかも、a とbは互いに素—最大公約数は1) • p≧q (つまりa ≧ b)とすれば、pとqの差 r = p – q = (a-b)*m であり、rとqの最大公約数もm。しかも p > rのはず • qとrの大きい方をp, 小さい方をqとして、上の方法を繰り返し ていけば、いつか必ずmが求まる(r=0になる) 42 アルゴリズムで大事なこと • 入力が何か、出力(結果 )が何かが明示されてい ること • 計算の順番が明示され ていること • 計算の停止の条件が明 示されていること • 必ず有限時間内に停止 し、答えを返すこと ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を 入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約 数(終了) さもなくば、 p← q, q ← r とし て、1へ。 43 (復習) アルゴリズムからプログラムへ • 本講義で扱う「アルゴリズム」はコンピュータの計算 の手順のこと • したがって、アルゴリズムの書き方は、それを見て 迷うことなくプログラムとして表現できるものが望ま しい • しかし、いきなりそのように書くのは難しいかもしれ ないので、「人間が読んで、計算の手順が明確な物 」を、ここではアルゴリズムと考える • 実際「プログラム」は、プログラム言語によって、いろ いろな表現がある。教科書では、プログラミング言 語CでもRubyでも表現しやすいように、アルゴリズ ムが書かれている 44 (復習)アルゴリズムを関数として表現 関数: 入力を取り、それに基づいて一連の手続き (計算)を行い、その結果を出力する(返す)、という プログラムの単位 45 (復習)関数について 関数定義の引数(xとy) は仮引数という。『定義』 のための仮のもの • 関数名を func とすると 関数の定義の書き方: def func(x,y) … プログラム(「コード」とも)… …どこかにreturn文が必要… end 関数呼出の引数(3と5) は実引数。計算に用い 関数の呼び出し: られる実物。 ans = func(3,5) 46 (復習)関数の利点 • 一連の手続きに『名前』をつけ、入力と出力を 明示することで、その手続きが何をしているか が明確になる • プログラムを関数に分けることで、そのプログ ラムの構造が明確になる • 関数ごとにデバッグをすることで、誤りが発見 しやすい。また変更も容易になる • 同じような処理をする場合に、冗長性が減る • 『再帰』は関数を使わないと書けない 47 ユークリッドの互除法のアルゴリズム をRubyのプログラムとして表す 確認: 1. ステップ1の「pとqの値 を入れ替える」はどうや るか? 2. ステップ2の剰余の求 め方は? 3. ステップ3で、どうやっ て1に処理をもどす か? ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を 入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約 数(終了) さもなくば、 p← q, q ← r とし て、1へ。 48 Rubyのプログラムへ(1) • ステップ1の「pとqの値を入れ替える」方法 普通の方法:第3の変数を持ち込む(例えばzとする) z = p; p = q; q = z Rubyならではの方法:多重代入(並行的な評価) p, q = q, p 試してみよう: p, q = 3, 5 p, q = q, p 49 Rubyのプログラムへ(2) • 剰余の求め方 --- これ自体、関数? def modulo(p,q) r = p/q return p-r*q end 実は… p % q または p.modulo(q) と書ける 試してみよう: 72 % 30 95.modulo(10) 50 アルゴリズムからRubyプログラムへ • 関数の書き方 • 四則演算などの計算の書き方 + - * / % ** • 変数への代入の書き方 r = p % q ( 「←」は使わない) • 条件文:判定式と、その結果に基づく処理の分岐 if (判定式) 判定式が成り立つ場合の処理 else 判定式が成り立たない場合の処理 end • 判定式 :等しい、大きい、小さい、以上、以下、等しくない、等 == > < >= <= != 51 Rubyのプログラムへ(3) • ステップ3で、どうやって1に処理をもどす か? Cならば goto 文を使いたいところ Rubyにはgoto文はない。だから、 繰り返しか再帰を用いる 再帰(recursion)とは、形の上では、ある関数の手続きにおいて、 その関数自身の呼び出しを含む(行う)こと。再帰を使いこなす ことは、アルゴリズムやプログラムの理解で重要 52 「階乗計算」を例に • 階乗(factorial)計算: 正整数nの階乗を n! と書く。 定義: n! = n * (n-1) * (n-2) * ・・・ * 2 * 1 特別に 0! = 1 。 • プログラムによる実現法: 繰り返しを使う def factorial(n) x=1 for m in 1..n x=x*m end # for return x end # def 53 再帰? • 階乗を計算する関数を factorial(n) とすると、 factorial(n) = n! = n * (n-1) * (n-2) * ・・・ * 2 * 1 (n-1)! = factorial(n-1) 54 再帰! つまり factorial(n) = n * factorial(n-1) def factorial(n) if (n >= 1) return n*factorial(n-1) else return 1 end # if end # def 55 ユークリッドの互除法のアルゴリズム をRubyのプログラムとして表す 確認: 1. ステップ1の「pとqの値 を入れ替える」はどうや るか?解決 2. ステップ2の剰余の求 め方は?解決 3. ステップ3で、どうやっ て1に処理をもどす か? ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を 入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約 数(終了) さもなくば、 p← q, q ← r とし て、1へ。 56 ユークリッドの互除法を試す • 繰り返しが起こっている p = 72, q = 30 として ステップ1: p > qなので次 ステップ2: r ←12 ステップ3: r != 0 なので p←30, q←12 としてステップ1へ ステップ1: p > qなので次 ステップ2: r ←6 ステップ3: r != 0 なので p←12, q←6としてステップ1へ ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を 入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約 数(終了) さもなくば、 p← q, q ← r とし て、1へ。 ステップ1: p > qなので次 ステップ2: r ← 0 ステップ3: r == 0 なので qの値が最大公約数 57 再帰と繰り返し つまり、繰り返し部分において q と r の最大公 約数を求める、という問題を次に解こうとして いるのだ(p,qの役割から)⇒再帰! return gcd(q,r) 今作ろうとしている関数gcd をgcd定義の中で使う! 教訓:再帰は、繰り返しを実現する方法のひと つ。再帰の方が「繰り返し」文よりも分かりや すい場合がある 注意: 本講義では「繰り返し」文としてwhile文、for文、 loop文、each文を主として考える 58 ユークリッドの互除法のアルゴリズム をRubyのプログラムとして表す def gcd(p,q) if (q > p) p, q = q, p end # if r=p%q if (r == 0) return q else return gcd(q,r) end # if end # def ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を 入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約 数(終了) さもなくば、 p← q, q ← r とし て、1へ。 59 『繰り返し』文を用いたプログラム 考え方:1から3までが繰り返されて いるのだから、全体を「whileやloop」という 繰り返し文で括ってしまう。 ユークリッドの互除法 def gcd(p,q) while (true) pとqは正整数とする if (q > p) 1. q > p ならば、 p と q の値を 入れ替える p, q = q, p 2. r ← p の q による剰余 end # if 3. r = 0 ならば、q が最大公約 r=p%q 数(終了) if (r == 0) さもなくば、 p← q, q ← r とし return q て、1へ。 end # if p, q = q, r end # while end # def これと再帰とでは、どちらが分かりやすいだろうか? 60 『繰り返し』文を用いたプログラム 別な考え方: (2)と(3)が「繰り返し」を構成して いると考えれば、 (2)も含めて次のように書ける: ユークリッドの互除法 #(2)と(3)の前半を while ((r = p % q) != 0) pとqは正整数とする 1. q > p ならば、 p と q の値を p=q 入れ替える 2. r ← p の q による剰余 q=r 3. r = 0 ならば、q が最大公約 r == 0 ならばwhile終了 数(終了) end さもなくば、 p← q, q ← r とし て、1へ。 return q 61 ユークリッドの互除法のアルゴリズム をRubyのプログラムとして表す • 繰り返し文を用いると… def gcd(p,q) if (q > p) p, q = q, p end # if while ((r = p % q) != 0) p=q q=r end # while return q end # def ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を 入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約 数(終了) さもなくば、 p← q, q ← r とし て、1へ。 注意:p、qが正整数なので、qによるpの剰余(r)はqよりも小さい 62 おまけ(基礎的な用語) • aとbの公倍数:aとbを約数に持つ数 注意:aとbは正の整数であることが前提 例: 8と12の公倍数は、24, 48, 72, … • aとbの最小公倍数(LCM) aとbの公倍数のうちで最小の正数 aとbのLCM = a * b / GCD(a, b) • aとbが互いに素:整数aとbの最大公約数が1 • 素数:約数が1とそれ自身だけの1より大きな正整数 注意:1は素数とは言わない 63 ユークリッドの互除法のプログラム • ここまでで質問は? • 『再帰』を使うほうが、「繰り返し」文を使うほう よりも分かりやすかったのではないでしょう か? • だから、再帰に慣れましょう! 分かりにくかったとしても、再帰は必要! 64 Rubyの「数」について • 今まで「実数」と「整数」と書きましたが、「実数」は、 正式には「浮動小数点数」(Float)といいます。 • Rubyでは、かなりの大きさの整数を表現できます (これも正確に言えば、FixnumとBignumの二種類 があって、Bignumのおかげで、かなり大きい整数を 表現できるのです) 試してみよう: 3.class 33333333333333333333333.class 3.14.class 65 Rubyの「数」について(続き) • 問題: 1を3で割るといくらでしょう? 答え: 人間が答えるか、計算機が答えるかで、 答えは変わります。また、計算機であっても、ど のように計算させるかで、答えが変わります。 66 Rubyの「数」について(続き) 試してみよう: 1/3 1.0 / 3 1 / 3.0 1.0 / 3.0 1 / 3.to_f 1 / 3.0.to_i 67 Rubyの「数」について(続き) • 浮動小数点数では、表現できる数の「精度」に限界 があります(これはRubyに限ったことではありませ ん) • 試しに次をやってみてください: a = 1.0/3.0 print a,”\n” printf(“%30.28f\n”,a) b = a – 0.333333333333333 # 3が18個 printf(“%30.28f\n”,b) この結果については、講義で説明します 68 数の表現について(補足) print 0.0000000000004 (小数点以下0が12個) をしてみると、どのような表示になるでしょう? これについても、講義で 69 他の問題について… (3) 最大値と最小値を答える 方針:maxにならって min という関数を用意する もしくは、max関数を作り変えて、max と min 同時 に求めるようにする(どちらが良いか?) (4) 最大値と最小値を答えるだけではなく、それ らが何番目の要素であるかも答える 方針:『何番目の要素だったか』を記憶する変数を 別に用意し、最大値や最小値が変わるたびに、その 番号を記憶する 70 再帰の問題(1):チャレンジ 『再帰』の仕組みを理解し、「再帰関数」を作れるようにしておこ う。(以下は『配列』の扱いが難しいので、次回以降の宿題…) 1.配列の深さを求めるアルゴリズムとプログラム 配列の深さ(depth)の定義: 要素に配列がなければ、1 そうでなければ、要素の「配列の深さ」の最大値+1 (「配列」でない要素の『深さ』を0とすると考えやすい) 注意:このように定義自体が「再帰的」になっている場合、 「再帰関数」を考えるのはとっても自然でやりやすい 例: [ 3, 5, 1] の深さは 1 [ 2, [3,5,1], 4]の深さは 2 [5, [ 2, [3,5,1], 4], [1,2,3] , [7] ]の深さは 2 71 再帰の問題(2):チャレンジ 2.配列を入力とし、配列に埋め込まれた要素すべてからなる 配列を返すアルゴリズムとプログラム(関数名を flat とする) 注意:関数が帰す結果において、要素の順番も、要素の重複 も問わないことにする 例: flat([ 3, 5, 1] ) ⇒ [3,5,1] ([1,3,5]など他の並びも可) flat([ 2, [3,5,1], 4]) ⇒ [2, 3,5,1, 4] (他の並びも可) flat([5, [ 2, [3,5,1], 4], [1,2,3] , [7] ]) ⇒[5,2,3,5,1,4,1,2,3,7] (注意:要素の重複も可) 1か2については考えてみてください。 72 計算量とオーダ(予習) • アルゴリズムの「理論的な」計算量は、処理する計算 機の性能と、アルゴリズムの手続きを解析することによ り、入力の大きさをnとすると、例えば、3n**2+8*n+6 のように求めることができる。これを、できるだけ簡単 なnの関数で表したものがオーダ。 • 元の式からオーダを求めるには、 1. 定数は無視する(元の式がnを含まない場合は1とす る)、 2. nが無限大に近付くときに最も大きくなるnの項を取り 出す 。 例えば、3n**2+8*n+6のオーダはn**2。これを O(n**2) と表す 73 計算量とオーダ(続) • オーダを求めるには、 log(n), n, n*log(n), n**2, n**2*log(n), n**3, 2**n, n!, n**n のような関数が、nの値の変化に対して(特にnが大き な値のときに)どのように大きくなるかを認識する必 要がある。 • それを図示するソフトウェア の一つが GnuPlot。 • 対数 の知識も不可欠。 74 Gnuplotの使用例 log(x), (log(x))**2, x, x log(x), x**2の比較 1e+008 x log(x) x*log(x) x**2 (log(x))**2 1e+007 1e+006 100000 10000 1000 100 10 1 0 1000 2000 3000 4000 5000 6000 7000 8000 9000 1000075 Gnuplotの使用例 log(x), x**0.5, x, x**2, x**3の比較 1e+009 x**0.5 x 1e+008 log(x) x**2 x**3 1e+007 1e+006 100000 10000 1000 100 10 1 100 200 300 400 500 600 700 800 900 1000 76 Gnuplotの使用例 x, x**2, x**3, 2**x, x!, x**xの比較 1e+090 x x**x 1e+080 fact(x) 2**x x**3 1e+070 1e+060 1e+050 1e+040 1e+030 1e+020 1e+010 1 5 10 15 20 25 30 35 40 45 50 77 プログラムの計算量 以下のような繰り返しでは、計算量は繰り返しの回数に比例 例: for i in 1..n # n回繰り返す⇒ O(n) print i, i**2, i**3,”\n” end #for 以下のような二重の繰り返しは、繰返し回数の積⇒ O(n*m) 例: for i in 1..n # n回繰り返す for j in 1..m # m回繰り返す print a[i,j]*a[j,i],”\n” end #for j end #for i 78 プログラムの計算量(続) • 関数を用いている場合 関数の性質によって計算量は異なる 入力の大きさを n とすると一般に: ソート(並び替え) ⇒ O(n*log(n)) 配列への操作(代入、アクセス) ⇒ O(1) 配列への要素の追加 ⇒ O(n) • 再帰関数の場合はいろいろ...第5章参照 79
© Copyright 2024 ExpyDoc