Slides

アルゴリズムとデータ構造入門
第7回 教科書1.3節後半&レポート解説
担当: 馬谷 誠二([email protected])
2
本日の内容
§1.3.3 汎用メソッドとしての手続き
§1.3.4 返り値としての手続き
レポート解説
不動点の計算
関数 f の不動点
f(x) = x を満たすような x
不動点を求める方法
f(x), f(f(x), f(f(f(x))), … をclose-enough?になるまで
繰り返す
Schemeコード
(define tolerance 0.00001)
(define (fixed-point f first-guess)
(define (close-enough? v1 v2)
(< (abs (- v1 v2)) tolerance))
(define (try guess)
(let ((next (f guess)))
(if (close-enough? guess next)
next
(try next))))
(try first-guess))
3
4
不動点としての平方根
•  x の平方根を y とする
y2 = x → y = x/y
•  じゃあ,平方根を
の不動点として求めよう
(define (sqrt x)
(fixed-point (lambda (y) (/ x y))
1.0))
...上手くいかない.たとえば√2だと,1.0, 2.0, 1.0, 2.0, 1.0, …
→ 振幅を狭くしてみよう(average damping,平均緩和法)
(define (sqrt x)
(fixed-point (lambda (y) (average y (/ x y)))
1.0))
5
§1.1.7の平方根プログラムとの比較
今回のコード
(define (sqrt x)
(fixed-point (lambda (y) (average y (/ x y)))
1.0))
§1.1.7のコード
まったく同じことをしているが,
(define (sqrt-iter guess x)
明らかに上の方が解りやすい
(if (good-enough? guess x)
guess
(sqrt-iter (improve guess x)
x)))
(define (improve guess x)
(average guess (/ x guess)))
(define (sqrt x) (sqrt-iter 1.0 x))
6
§1.3.4 返り値としての手続き
(あらたに生成した)手続きを返す手続き
例: 平均緩和法
(define (average-damp f)
(lambda (x) (average x (f x))))
((average-damp square) 10) ⇒ 55
•  sqrtの改訂
(define (sqrt x)
(fixed-point (average-damp (lambda (y) (/ x y)))
1.0))
•  手法をより明確に表現
•  再利用性: cube-root
(lambda (y)
(/ x (square y)))
7
ニュートン法(1)
定義
g(x) を微分可能な関数とする.方程式 g(x) = 0 の解は:
の不動点である(Dg は
g の導関数).
微分演算子D (高階手続き)
(define dx 0.00001)
(define (deriv g)
(lambda (x)
(/ (- (g (+ x dx)) (g x)) dx)))
((deriv (lambda (x) (* x x x)) 5) ⇒ 75.00014999664018
8
ニュートン法(2)
Schemeコード
(define (newton-transform g)
(lambda (x)
(- x (/ (g x) ((deriv g) x)))))
(define (newtons-method g guess)
(fixed-point (newton-transform g) guess))
例: sqrt
(define (sqrt x)
(newtons-method (lambda (y) (- (square y) x))
1.0))
数値微分を使っている点に注意.平均緩和法は記号微分に相当
さらなる抽象化(一般化)
9
平方根を求める2つの方法
1.  平均緩和法 + 不動点
(define (sqrt x)
(fixed-point (average-damp (lambda (y) (/ x y))) 1.0))
2.  ニュートン法(これも不動点)
(define (newtons-method g guess)
(fixed-point (newton-transform g) guess))
(define (sqrt x)
(newtons-method (lambda (y) (- (square y) x)) 1.0))
あきらかに共通点 (define (fixed-point-of-transform g transform guess)
(fixed-point (transform g) guess))
(define (sqrt x) ;; 1の方法
(fixed-point-of-transform (lambda (y) (/ x y))
average-damp
1.0))
10
第一級手続き
第一級(first class): プログラム中でできることのランク
1.  変数定義により名前をつけられる
(define cube (lambda (x) (* x x x)))
2.  手続きに引数として渡せる
(sum cube 1 inc 100)
3.  手続きの評価結果として返せる
(define (average-damp f)
(lambda (x) (average x (f x))))
4.  データ構造の中に含められる
(define (foo f)
(cons (deriv f)
(deriv (deriv f))))
11
ここからレポート解説
•  webページでの公開はしません
•  欲しい人には印刷したものを配布しますので,教員 or TA に
リクエストしてください
•  他の問題についても模範回答を知りたければ,遠慮なくリクエ
ストしてください
正解率の低かった問題
•  (10/7課題 Ex1.3)
•  10/14課題 (Ex1.7), 問題4
•  10/28課題 問題1, 問題2
と,昨日締切の中から
•  11/4課題 問題3
12
10/14課題 Ex1.7
good-enough?を改良せよ.
(和文) good-enough?を実装するもう一つの方法は,ある繰返
しから次へのguessの変化に注目し,変化が予測値に比べ非
常に小さくなった時に止めるのである.
13
10/14課題 Ex1.7
good-enough?を改良せよ.
(和文) good-enough?を実装するもう一つの方法は,ある繰返
しから次へのguessの変化に注目し,変化が予測値に比べ非
常に小さくなった時に止めるのである.
(原文) … watch how guess changes from one iteration to
the next and to stop when the change is a very small
fraction of the guess …
14
10/14課題 問題4
if が cond の特殊ケースであるのと同様に,and ならびに
or が cond (あるいは if)の特殊ケースであることを表す等式
を示せ.
(and <e1> … <en>): 特殊形式
• 
• 
• 
式⟨ei⟩を左から右へ一つずつ評価
ある⟨ei⟩の評価の結果が#fなら, and式の値は#f.
残りの⟨ei+1⟩ … は評価しない
すべての⟨ei⟩が真に評価されれば, and式の値は最後の式の値
(if <e1>
(if <e2>
... (if <en> <en> #f) ...
#f)
#f))
;; (if en-1 en #f)も可
15
10/28課題 問題1
1.  教科書の脚注37に書いてあること(fast-exptの乗算
回数は
以下である)をきちんと証明せよ.
(define (square x) (* x x))
(define (fast-expt b n)
(cond ((= n 0) 1)
((= n 1) b)
((even? n)
(square (fast-expt b (/ n 2))))
; (1)
(else (* b (fast-expt b (- n 1)))))) ; (2)
乗算回数:
(1) が実行される回数 + (2) が実行される回数
16
より高速な方法(2)
計算量
•  簡単な場合:
•  一般の場合:
・ の個数 = log2 n
Θ(log n) → 脚注37(レポート課題)
n を2進数で表したときの1の個数を x とする.fast-exptにより
実行される乗算の回数は:
例: n = 13 (1101) の場合,x = 3 で上式の値は 3 + 3 – 1 = 5
ヒント: 2で割る = 1ビット右へシフト
奇数から1を引く = 最下位ビットをクリア
17
(fast-expt b 13)
(2)
⇒ (* b
(fast-expt b 12))
(1)
⇒ (* b (square (fast-expt b
(1)
⇒
...
(fast-expt b
3) ...
(2)
⇒
...
(fast-expt b
2) ...
(2)
⇒
...
(fast-expt b
1) ...
⇒
...
b
6)))
...
18
(fast-expt b 1101)
(2)
⇒ (* b
(fast-expt b 1100))
クリア
(1)
⇒ (* b (square (fast-expt b 110)))
シフト
(1)
⇒
...
(fast-expt b 11) ...
シフト
(2)
⇒
...
(fast-expt b 10) ...
クリア
(2)
⇒
...
(fast-expt b 1)
...
シフト
⇒
...
b
...
1101
2i
i ビット
(1)の回数 = i
(2)の回数 = i ビット中の1の個数
したがって,(1) + (2) 19
10/28課題 問題2
,
,
( もパラメータ) を計算する
手続きをそれぞれ書け.また,それぞれの計算量も求めよ.
1. 
• 
• 
は正整数, は2以上の整数とする
もちろん,JAKLD組込みの手続きlogを使用してはいけない
方針
すなおに解釈すると:
という条件を満たすような整数
x を見つければ良い.
x を 0 から順に試してみよう(線形探索).
20
(define (linear-floor-log0 b n)
(define (iter x)
(if (> (fast-expt b x) n)
(- x 1)
(iter (+ x 1))))
(iter 1))
•  一応正解だが,実行効率があまり良くない(O((log n)2))
•  (fast-expt b x) を毎回呼び出さなくても,蓄積引数を
一つ追加するだけで bx を求めることができる(Θ(log n))
(define (linear-floor-log b n)
(define (iter x prod)
;; prod = b^x
(if (> prod n)
(- x 1)
(iter (+ x 1) (* prod b))))
(iter 1 b))
21
(別解) 脚注37がヒント
= 入力 n を b 進数表現したときの桁数 - 1
例:
10010011 (= 147)
7 (27 = 128)
(define (linear-floor-log-down b n)
(define (iter cnt quot)
(if (< quot b)
cnt
(iter (+ cnt 1) (quotient quot b))))
(iter 0 n))
•  上からの線形探索
とも思える
•  Θ(log n)
(define (linear-floor-log b n)
(define (iter x prod)
;; prod = b^x
(if (> prod n)
(- x 1)
(iter (+ x 1) (* prod b))))
(iter 1 b))
22
11/4課題 問題3
さらに,同様の方法によって
を求める手続きを書け.
また,前回レポートの必須問題2において自分の書いた
を求める手続きと計算時間を比較せよ.
(define (floor-log b n)
(define (search low high)
(let* ((mid (quotient (+ low high) 2))
(mid-expt (fast-expt b mid)))
(cond ((< n mid-expt)
; 左不成立
(search low mid))
((<= (* b mid-expt) n) ; 右不成立
(search (+ mid 1) high))
(else mid))))
(search 0 n))
23
二分探索(binary search)
•  ソートされたデータ列 A[i]
1
A 2
2
3
4
5
6
7
8
8
13 15 30 37 42 50
O(log n)
•  一方,前から順に探していくのを線形探索(linear search)
•  計算オーダは O(n)
•  より広い意味で用いることもある
•  計算オーダは
•  ちょうど良いあたりの半分で領域を分ける
•  二つのどちらに含まれているか判定して再帰
を見つかるまで繰り返す探索方法
24
計算量
少しごまかしてる
とすると
なので
とすると
すなわち
従って
.同様にして
25
補足
(define (floor-log b n)
(define (search low high)
(let* ((mid (quotient (+ low high) 2))
(mid-expt (fast-expt b mid)))
(cond ((< n mid-expt)
(search low mid))
((<= (* b mid-expt) n)
(search (+ mid 1) high))
(else mid))))
(search 0 n))
•  線形探索の
Θ(log n) より遅い(!)
•  この二分探索が遅い理由
1.  searchの初期値が悪い
2.  反復毎のfast-expt呼出し
26
フェーズ1:
0
を
になるまで繰り返す
n/4
n/2
n
O(log n)回
0
h
フェーズ2: 本当の(?)二分探索
2h
O(log log n)回
27
補足
•  現実的には,入力
n の上限は決まっている
たとえば,32ビット符号なし整数なら,232 –
1
→ (search 0 32) で十分
(define (floor-log b n)
(define (search low high)
(let* ((mid (quotient (+ low high) 2))
(mid-expt (fast-expt b mid)))
(cond ((< n mid-expt)
(search low mid))
((<= (* b mid-expt) n)
(search (+ mid 1) high))
(else mid))))
(search 0 32))
さらに補足
b を固定すれば(あまりスマートなコードに見えないが)
fast-expt 呼出しをなくすことも可能
(define (floor-log-base2 n)
(if (>= n 16)
; 2^4
(if (>= n 64)
; 2^6
(if (>= n 128) ; 2^7
7
6)
(if (>= n 32)
; 2^5
5
4))
(if (>= n 4)
; 2^2
(if (>= n 8)
; 2^3
3
2)
(if (>= n 2)
; 2^1
1
0))))
→ 計算量:
O(log log n)
28
29
おわりに
•  授業への感想,意見,コメント等あれば,課題提出システムに
コメントをお願いします.
•  11/18課題として,空のレポート課題をつくっておきます.
•  それでは,2章以降を楽しんで学んでください.