590 likes | 675 Views
アルゴリズムとデータ構造 第 1 章 アルゴリズムと計算量 4 月 26 日. 情報知能学科 白井英俊. 前回の練習 :『 最小値 』 を求める. 問題: 「 n 個の数 x 1 , x 2 , …, x n が与えられたときに、その 『 最小値 』 を求める」アルゴリズムとプログラムを書く。 「最大値」の要素を求めるアルゴリズムがヒント. 確認 :『 最大値 』 の要素を求める. 最小値. min (n,x). def max(n,x) y = x[0] for i in 1..(n-1) if (x[i] > y)
E N D
アルゴリズムとデータ構造第1章 アルゴリズムと計算量4月26日アルゴリズムとデータ構造第1章 アルゴリズムと計算量4月26日 情報知能学科 白井英俊
前回の練習:『最小値』を求める 問題:「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 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; MIN 最小値 < <
練習問題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. 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 手続きの部分をプログラムにする。 1のステップは、二つの実数から最大値(max)と最小値(min)をそれぞれyとzに代入 よって if x[0] > x[1] y,z = x[0],x[1] else y,z = x[1],x[0] end とすれば目的は達成される なぜx[0]とx[1]を比較しているのか?
練習問題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)
ファイルの操作の問題 • 「ファイルから実数を読み込み、その中の最大値を答える」問題について • 『最大値』アルゴリズムがあるのだから、この問題は、それを使って解くのが良い。 • つまり、ファイルから「最大値を求める」対象の配列を作ればよい。そうすれば「最大値」アルゴリズムを使って答えが求められる。 • したがって、考えるべきは、ファイルに書いてある数を読み込み、それから配列を作る方法
確認:『最大値』の要素を求める 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 2のgetsの方式と組み合わせると、 2のreadlinesの場合は…(自分で考えよう) to_f は「実数」にする to_iは「整数」にする z = [ ] # 空っぽの配列を作っておく while (y = inFile.gets) z << y.to_f # 配列の後ろに入れる end y.to_f >> z は駄目!
プログラムの完成 # ファイルにある数の最大値を求める 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
(復習) アルゴリズムとは(続き) • ユークリッドの互除法 (最古のアルゴリズム) 入力:正整数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 として、試してみよう pをqで割った余り
ユークリッドの互除法を試す • プログラムを作る前に、自分でやってみることが大事 p = 72, q = 30 として ユークリッドの互除法 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 ←12 ステップ3: r != 0 なので p←30, q←12 としてステップ1へ ステップ1: p > qなので次 ステップ2: r ←6 ステップ3: r != 0 なので p←12, q←6としてステップ1へ ステップ1: p > qなので次 ステップ2: r ← 0 ステップ3: r == 0 なので qの値が最大公約数
参考:ユークリッドの互除法の仕組み なぜ、ユークリッドの互除法で最大公約数が求まるか? • 入力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になる)
アルゴリズムで大事なこと ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約数(終了) さもなくば、 p← q, q ← r として、1へ。 • 入力が何か、出力(結果)が何かが明示されていること • 計算の順番が明示されていること • 計算の停止の条件が明示されていること • 必ず有限時間内に停止し、答えを返すこと
(復習) アルゴリズムからプログラムへ • 本講義で扱う「アルゴリズム」はコンピュータの計算の手順のこと • したがって、アルゴリズムの書き方は、それを見て迷うことなくプログラムとして表現できるものが望ましい • しかし、いきなりそのように書くのは難しいかもしれないので、「人間が読んで、計算の手順が明確な物」を、ここではアルゴリズムと考える • 実際「プログラム」は、プログラム言語によって、いろいろな表現がある。教科書では、プログラミング言語CでもRubyでも表現しやすいように、アルゴリズムが書かれている
(復習)アルゴリズムを関数として表現 関数: 入力を取り、それに基づいて一連の手続き(計算)を行い、その結果を出力する(返す)、というプログラムの単位
(復習)関数について 関数定義の引数(xとy) は仮引数という。『定義』のための仮のもの • 関数名を funcとすると 関数の定義の書き方: def func(x,y) … プログラム(「コード」とも)… …どこかにreturn文が必要… end 関数の呼び出し: ans = func(3,5) 関数呼出の引数(3と5) は実引数。計算に用いられる実物。
(復習)関数の利点 • 一連の手続きに『名前』をつけ、入力と出力を明示することで、その手続きが何をしているかが明確になる • プログラムを関数に分けることで、そのプログラムの構造が明確になる • 関数ごとにデバッグをすることで、誤りが発見しやすい。また変更も容易になる • 同じような処理をする場合に、冗長性が減る • 『再帰』は関数を使わないと書けない
ユークリッドの互除法のアルゴリズムをRubyのプログラムとして表すユークリッドの互除法のアルゴリズムをRubyのプログラムとして表す ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約数(終了) さもなくば、 p← q, q ← r として、1へ。 確認: • ステップ1の「pとqの値を入れ替える」はどうやるか? • ステップ2の剰余の求め方は? • ステップ3で、どうやって1に処理をもどすか?
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
Rubyのプログラムへ(2) • 剰余の求め方 --- これ自体、関数? def modulo(p,q) r = p/q return p-r*q end 実は… p % qまたは p.modulo(q)と書ける 試してみよう: 72 % 30 95.modulo(10)
アルゴリズムからRubyプログラムへ • 関数の書き方 • 四則演算などの計算の書き方 + - * / % ** • 変数への代入の書き方 r = p % q ( 「←」は使わない) • 条件文:判定式と、その結果に基づく処理の分岐 if (判定式) 判定式が成り立つ場合の処理 else 判定式が成り立たない場合の処理 end • 判定式 :等しい、大きい、小さい、以上、以下、等しくない、等 == > < >= <= !=
Rubyのプログラムへ(3) • ステップ3で、どうやって1に処理をもどすか? Cならば goto 文を使いたいところ Rubyにはgoto文はない。だから、 繰り返しか再帰を用いる 再帰(recursion)とは、形の上では、ある関数の手続きにおいて、その関数自身の呼び出しを含む(行う)こと。再帰を使いこなすことは、アルゴリズムやプログラムの理解で重要
「階乗計算」を例に • 階乗(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
再帰? • 階乗を計算する関数を factorial(n) とすると、 factorial(n) = n! = n * (n-1) * (n-2) * ・・・ * 2 * 1 (n-1)! = factorial(n-1)
再帰! つまり factorial(n) = n * factorial(n-1) def factorial(n) if (n >= 1) return n*factorial(n-1) else return 1 end # if end # def
ユークリッドの互除法のアルゴリズムをRubyのプログラムとして表すユークリッドの互除法のアルゴリズムをRubyのプログラムとして表す ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約数(終了) さもなくば、 p← q, q ← r として、1へ。 確認: • ステップ1の「pとqの値を入れ替える」はどうやるか?解決 • ステップ2の剰余の求め方は?解決 • ステップ3で、どうやって1に処理をもどすか?
ユークリッドの互除法を試す • 繰り返しが起こっている p = 72, q = 30 として ユークリッドの互除法 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 ←12 ステップ3: r != 0 なので p←30, q←12 としてステップ1へ ステップ1: p > qなので次 ステップ2: r ←6 ステップ3: r != 0 なので p←12, q←6としてステップ1へ ステップ1: p > qなので次 ステップ2: r ← 0 ステップ3: r == 0 なので qの値が最大公約数
再帰と繰り返し つまり、繰り返し部分において q と r の最大公約数を求める、という問題を次に解こうとしているのだ(p,qの役割から)⇒再帰! return gcd(q,r) 教訓:再帰は、繰り返しを実現する方法のひとつ。再帰の方が「繰り返し」文よりも分かりやすい場合がある 注意: 本講義では「繰り返し」文としてwhile文、for文、loop文、each文を主として考える 今作ろうとしている関数gcdをgcd定義の中で使う!
ユークリッドの互除法のアルゴリズムをRubyのプログラムとして表すユークリッドの互除法のアルゴリズムをRubyのプログラムとして表す ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約数(終了) さもなくば、 p← q, q ← r として、1へ。 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
『繰り返し』文を用いたプログラム 考え方:1から3までが繰り返されて いるのだから、全体を「whileやloop」という繰り返し文で括ってしまう。 def gcd(p,q) while (true) if (q > p) p, q = q, p end # if r = p % q if (r == 0) return q end # if p, q = q, r end # while end # def ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を入れ替える 2. r ← p の q による剰余 3.r = 0 ならば、q が最大公約数(終了) さもなくば、 p← q, q ← r として、1へ。 これと再帰とでは、どちらが分かりやすいだろうか?
『繰り返し』文を用いたプログラム 別な考え方: (2)と(3)が「繰り返し」を構成していると考えれば、 (2)も含めて次のように書ける: #(2)と(3)の前半を while ((r = p % q) != 0) p = q q = r end return q ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を入れ替える 2. r ← p の q による剰余 3.r = 0 ならば、q が最大公約数(終了) さもなくば、 p← q, q ← r として、1へ。 r == 0 ならばwhile終了
ユークリッドの互除法のアルゴリズムをRubyのプログラムとして表すユークリッドの互除法のアルゴリズムをRubyのプログラムとして表す ユークリッドの互除法 pとqは正整数とする 1. q > p ならば、 p と q の値を入れ替える 2. r ← p の q による剰余 3. r = 0 ならば、q が最大公約数(終了) さもなくば、 p← q, q ← r として、1へ。 • 繰り返し文を用いると… 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が正整数なので、qによるpの剰余(r)はqよりも小さい
おまけ(基礎的な用語) • 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は素数とは言わない
ユークリッドの互除法のプログラム • ここまでで質問は? • 『再帰』を使うほうが、「繰り返し」文を使うほうよりも分かりやすかったのではないでしょうか? • だから、再帰に慣れましょう! • 分かりにくかったとしても、再帰は必要!
Rubyの「数」について • 今まで「実数」と「整数」と書きましたが、「実数」は、正式には「浮動小数点数」(Float)といいます。 • Rubyでは、かなりの大きさの整数を表現できます(これも正確に言えば、FixnumとBignumの二種類があって、Bignumのおかげで、かなり大きい整数を表現できるのです) 試してみよう: 3.class 33333333333333333333333.class 3.14.class
Rubyの「数」について(続き) • 問題: 1を3で割るといくらでしょう? 答え: 人間が答えるか、計算機が答えるかで、答えは変わります。また、計算機であっても、どのように計算させるかで、答えが変わります。
Rubyの「数」について(続き) 試してみよう: 1 / 3 1.0 / 3 1 / 3.0 1.0 / 3.0 1 / 3.to_f 1 / 3.0.to_i
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) この結果については、講義で説明します
数の表現について(補足) print 0.0000000000004 (小数点以下0が12個) をしてみると、どのような表示になるでしょう? これについても、講義で
他の問題について… (3) 最大値と最小値を答える 方針:maxにならって min という関数を用意する もしくは、max関数を作り変えて、max と min 同時に求めるようにする(どちらが良いか?) (4) 最大値と最小値を答えるだけではなく、それらが何番目の要素であるかも答える 方針:『何番目の要素だったか』を記憶する変数を別に用意し、最大値や最小値が変わるたびに、その番号を記憶する これらは宿題とします:提出は今週中!