Transcript
Kapitel 5: Effizienz und Komplexität
Programmieren in Haskell
1
Analyse von insertionSort
insertionSort :: (Ord a) => [a] -> OrdList a insertionSort [] = [] insertionSort (a:as) = insert a (insertionSort as) insert :: (Ord a) => a -> [a] -> [a] insert a [] = [a] insert a (a’:as) | a <= a’ = a:a’:as | otherwise = a’:insert a as
Programmieren in Haskell
(insert.1) (insert.2) (insert.2.a) (insert.2.b)
2
(insert c) (d:z) ⇒ | c <= d | otherwise ⇒ | True | otherwise
= c:d:z
(insert.2)
= d:insert c z = c:d:z
(<=)
= d:insert c z
⇒ c : d : z
(insert.2.a)
beziehungsweise ⇒ | False | otherwise ⇒ d:insert c z
Programmieren in Haskell
= c:d:z
(<=)
= d:insert c z (insert.2.b)
3
T ime(insert c [])
=
1
T ime(insert c (d:z))
=
3,
T ime(insert c (d:z))
=
3 + T ime(insert c z),
Programmieren in Haskell
falls c 6 d falls c > d
4
T ime(insert c [])
=
1
T ime(insert c (d:z))
=
3,
T ime(insert c (d:z))
=
3 + T ime(insert c z),
falls c 6 d falls c > d
T ime(isort [])
=
1
T ime(isort (a:x))
=
1 + T ime(isort x ⇒ v) + T ime(insert a v)
Programmieren in Haskell
4
t_insertionSort :: (Ord a) => [a] -> (Int,OrdList a) t_insertionSort [] = (1,[]) t_insertionSort (a:as) = (t+u+1,ys) where (t,xs) = t_insertionSort as (u,ys) = t_insert a xs t_insert :: (Ord a) => a -> [a] -> (Int,[a]) t_insert a [] = (1,[a]) t_insert a (a’:as) | a <= a’ = (3,a:a’:as) | otherwise = (t+3,a’:xs) where (t,xs) = t_insert a as
Programmieren in Haskell
5
Untere und obere Schranken
T isort (n) 6 T ime(isort [a1 ,...,an ]) 6 T isort (n)
Programmieren in Haskell
6
Untere und obere Schranken
T isort (n) 6 T ime(isort [a1 ,...,an ]) 6 T isort (n)
Programmieren in Haskell
T isort (n)
=
min{ T ime(isort x) | length x = n }
T isort (n)
=
max{ T ime(isort x) | length x = n }
6
Programmieren in Haskell
T insert (0)
=
1
T insert (n + 1)
=
3
T insert (0)
=
1
T insert (n + 1)
=
3 + T insert (n)
7
Programmieren in Haskell
T insert (0)
=
1
T insert (n + 1)
=
3
T insert (0)
=
1
T insert (n + 1)
=
3 + T insert (n)
T isort (0)
=
1
T isort (n + 1)
=
1 + T insert (n) + T isort (n)
T isort (0)
=
1
T isort (n + 1)
=
1 + T insert (n) + T isort (n)
7
> rsolve({insert(n) = 3 + insert(n-1), insert(0)=1, isort(n) = 1 + insert(n-1) + isort(n-1), isort(0)=1},{insert,isort}); 3 2 1 {insert(n ) = 1 + 3 n, isort(n ) = 1 + n + n } 2 2
Programmieren in Haskell
8
> rsolve({insert(n) = 3 + insert(n-1), insert(0)=1, isort(n) = 1 + insert(n-1) + isort(n-1), isort(0)=1},{insert,isort}); 3 2 1 {insert(n ) = 1 + 3 n, isort(n ) = 1 + n + n } 2 2
Programmieren in Haskell
T insert (0)
=
1
T insert (n + 1)
=
3
T insert (n)
=
3n + 1
T isort (0)
=
1
T isort (n + 1)
=
4n + 1
T isort (n)
=
(3/2)n2 + (1/2)n + 1
8
Asymptotische Zeit- und Platzeffizienz Θ(g)
=
{ f | (∃n0 , c1 , c2 )(∀n > n0 ) c1 g(n) 6 f (n) 6 c2 g(n) }
(1)
Ist f ∈ Θ(g), so heißt g asymptotische Schranke von f .
Programmieren in Haskell
9
Asymptotische Zeit- und Platzeffizienz Θ(g)
=
{ f | (∃n0 , c1 , c2 )(∀n > n0 ) c1 g(n) 6 f (n) 6 c2 g(n) }
(1)
Ist f ∈ Θ(g), so heißt g asymptotische Schranke von f .
Programmieren in Haskell
T insert (n)
∈
Θ(1)
T insert (n)
∈
Θ(n)
T isort (n)
∈
Θ(n)
T isort (n)
∈
Θ(n2 )
9
Programmieren in Haskell
f ∈ Θ(f )
(Reflexivität)
(2)
f ∈ Θ(g) ∧ g ∈ Θ(h) ⇒ f ∈ Θ(h)
(Transitivität)
(3)
f ∈ Θ(g) ⇒ g ∈ Θ(f )
(Symmetrie)
(4)
cf ∈ Θ(f )
(5)
na + nb ∈ Θ(na ) für a > b
(6)
loga n ∈ Θ(logb n)
(7)
10
Untere und obere asymptotische Schranken
Ω(g)
=
{ f | (∃n0 , c)(∀n > n0 ) cg(n) 6 f (n) }
(8)
O(g)
=
{ f | (∃n0 , c)(∀n > n0 ) f (n) 6 cg(n) }
(9)
Ist f ∈ Ω(g), so heißt g untere asymptotische Schranke von f . Für f ∈ O(g) heißt g entsprechend obere asymptotische Schranke von f .
Programmieren in Haskell
11
Effizienz strukturell rekursiver Funktionen
Programmieren in Haskell
T insert (0)
=
0
T insert (n + 1)
=
1 + T insert (n)
T isort (0)
=
0
T isort (n + 1)
=
T insert (n) + T isort (n)
12
Effizienz strukturell rekursiver Funktionen T insert (0)
=
0
T insert (n + 1)
=
1 + T insert (n)
T isort (0)
=
0
T isort (n + 1)
=
T insert (n) + T isort (n)
C(0)
=
c
C(n + 1)
=
f (n + 1) + kC(n)
C(n)
=
n
k c+
n X
kn−i f (i)
(10)
i=1
Programmieren in Haskell
12
T insert (n)
=
n X
1 = n ∈ Θ(n)
i=1
T isort (n)
=
n X i=1
Programmieren in Haskell
i−1=
1 n(n − 1) ∈ Θ(n2 ) 2
13
Platzbedarf von isort
isort [a1 , ..., an−1 , an ]
Programmieren in Haskell
⇒
insert a1 (· · · (insert an−1 (insert an [])) · · ·)
⇒
[aπ(1) , ..., aπ(n−1) , aπ(n) ]
14
Platzbedarf von isort
isort [a1 , ..., an−1 , an ]
Programmieren in Haskell
⇒
insert a1 (· · · (insert an−1 (insert an [])) · · ·)
⇒
[aπ(1) , ..., aπ(n−1) , aπ(n) ]
Space(insert a x)
∈
Θ(length x)
Space(isort x)
∈
Θ(length x)
14
Worst-case Laufzeit von sortTree sortTree :: Tree Integer -> [Integer] sortTree (Leaf a) = [a] sortTree (Br l r) = merge (sortTree l) (sortTree r) merge merge merge merge | |
:: (Ord a) => [] bs (a:as) [] (a:as) (b:bs) a <= b otherwise
Programmieren in Haskell
OrdList a -> OrdList a -> OrdList a = bs = a:as = a:merge as (b:bs) = b:merge (a:as) bs
15
T merge (m, n)
Programmieren in Haskell
=
m+n−1
für m, n > 1
16
T merge (m, n)
für m, n > 1
m+n−1
=
n = Zahl der Blätter (size): T sT (1)
=
0
T sT (n)
=
n − 1 + max{ T sT (i) + T sT (n − i) | 0 < i < n }
n
1
2
3
4
5
6
7
8
9
T sT (n) 0
1
3
6
10
15
21
28
36
T sT (n)
=
n X i=1
i−1=
1 n(n − 1) ∈ Θ(n2 ) 2
Schlechtester Fall: entarteter Baum Programmieren in Haskell
16
n = Tiefe (depth): 0
T sT (0) 0
T sT (n + 1)
0 T sT (n)
=
n X i=1
n−i
2
i
=
0
=
2n+1 − 1 + 2T sT (n)
(2 − 1) =
n X
0
2n − 2n−i = n2n − 2n + 1 ∈ Θ(n2n )
i=1
Schlechtester Fall: ausgeglichener Baum
Programmieren in Haskell
17
T ime(sortTree t)
00
T sT (s, d)
Programmieren in Haskell
6
=
depth t*size t
sd
18
Ende 5.2 und 5.3 fehlen.
Programmieren in Haskell
19
Problemkomplexität Bisher: isort: „worst case“-Laufzeit von Θ(n2 ) mergeSort: „worst case“-Laufzeit von Θ(n log n) Effizienz eines Sortierverfahrens
Programmieren in Haskell
20
Problemkomplexität Bisher: isort: „worst case“-Laufzeit von Θ(n2 ) mergeSort: „worst case“-Laufzeit von Θ(n log n) Effizienz eines Sortierverfahrens Jetzt: Komplexität des Sortierproblems
Programmieren in Haskell
20
Entscheidungsbäume
a1<=a2 / \ [a1,a2] [a2,a1]
a1<=a2
/ \ a1<=a3 a2<=a3 / \ / \ a2<=a3 [a3,a1,a2] a1<=a3 [a3,a2,a1] / \ / \ [a1,a2,a3] [a1,a3,a2] [a2,a1,a3] [a2,a3,a1]
Programmieren in Haskell
21
T imesort (n) > log2 (n!)
Programmieren in Haskell
22
T imesort (n) > log2 (n!) Die Fakultätsfunktion läßt sich mit der Stirlingschen Formel abschätzen: n n √ n! > 2πn . e
Programmieren in Haskell
22
Insgesamt erhalten wir T imesort (n) > log2 = log2
√ √
2πn
n n e
2πn + log2
n n e n n
= log2 (2πn)1/2 + log2
e 1 n = log2 (2πn) + n log2 2 e 1 1 = log2 (2π) + log2 n + n log2 n − n log2 e 2 2 ∈ Θ(n log n)
Programmieren in Haskell
23
Der Weg zu einer guten Problemlösung
1. Man verschafft sich Klarheit über die Komplexität des zu lösenden Problems. 2. Man entwickelt einen Algorithmus, dessen Effizienz in der Klasse der Problemkomplexität liegt. Asymptotisch gesehen, ist dieser bereits „optimal“. 3. Man analysiert die konstanten Faktoren des Algorithmus und sucht diese zu verbessern.
Programmieren in Haskell
24
Optimierung von mergeSort build :: [a] -> Tree a build [] = Nil build [a] = Leaf a build (a:as) = Br (build (take k as))(build (drop k as)) where k = length as ‘div‘ 2
Programmieren in Haskell
25
Optimierung von mergeSort build :: [a] -> Tree a build [] = Nil build [a] = Leaf a build (a:as) = Br (build (take k as))(build (drop k as)) where k = length as ‘div‘ 2
build’’ :: [a] -> Tree a build’’ as = buildn (length as) as where buildn :: Int -> [a] -> Tree a buildn 1 (a:as) = Leaf a buildn n as = Br (buildn k (take k as)) (buildn (n-k) (drop k as)) where k = n ‘div‘ 2
Programmieren in Haskell
25
T build (n) = 5n + 2n log2 n − 4 T build00 (n) = 5n + n log2 n − 4
Programmieren in Haskell
26
build’ :: [a] -> Tree a build’ as = fst (buildSplit (length as) as) buildSplit n as = (Br l r, as’’) where k = n ‘div‘ 2 (l,as’) = buildSplit k as (r,as’’) = buildSplit (n-k) as’
Programmieren in Haskell
27
build’ :: [a] -> Tree a build’ as = fst (buildSplit (length as) as) buildSplit n as = (Br l r, as’’) where k = n ‘div‘ 2 (l,as’) = buildSplit k as (r,as’’) = buildSplit (n-k) as’
Programmieren in Haskell
T buildSplit (n)
=
6 + T buildSplit (bn/2c) + T buildSplit (dn/2e)
T buildSplit (1)
=
1
27
build’ :: [a] -> Tree a build’ as = fst (buildSplit (length as) as) buildSplit n as = (Br l r, as’’) where k = n ‘div‘ 2 (l,as’) = buildSplit k as (r,as’’) = buildSplit (n-k) as’
T buildSplit (n)
=
6 + T buildSplit (bn/2c) + T buildSplit (dn/2e)
T buildSplit (1)
=
1
Für n = 2k : T buildSplit (2k ) = 6(2k+1 − 1) = 12n − 6 ∈ Θ(n) Programmieren in Haskell
27
mergeSort as = sortTree (build as) build [] = Nil build [a] = Leaf a build (a:as) = Br (build (take k as))(build (drop k as)) where k = length as ‘div‘ 2
Programmieren in Haskell
28
mergeSort [] = sortTree(build []) = sortTree Nil mergeSort [a] = sortTree(build [a]) = sortTree (Leaf mergeSort (a:as) = sortTree(build (a:as)) = sortTree(Br (build (take k as)) (build where k = length = merge (sortTree (build (take k as)) sortTree (build (drop k as))) where k = length = merge (mergeSort (take k as) mergeSort (drop k as))) where k = length
Programmieren in Haskell
a)
= [] = [a]
(drop k as))) as ‘div‘ 2
as ‘div‘ 2
as ‘div‘ 2
29
mergeSort’ [] = [] mergeSort’ [a] = [a] mergeSort’ (a:as) = merge (mergeSort (take k as)) (mergeSort (drop k as)) where k = length as ‘div‘ 2
Programmieren in Haskell
30
mtest = mergeSort [1..10000] mtest’ = mergeSort’ [1..10000]
Programmieren in Haskell
31
Top down Baumkonstruktion [8, 3, 5, 3, 6, 1] [8, 3, 5]
[3, 6, 1] [3, 5]
8
3
5
[6, 1] 3
[3, 5] [3, 5, 8]
6
1 [1, 6]
[1, 3, 6] [1, 3, 3, 5, 6, 8]
Programmieren in Haskell
32
Bottom up Baumkonstruktion /\ /t1\ ----
Programmieren in Haskell
/\ /t2\ ----
/\ /t3\ ----
/\ /t4\ ----
/\ /t5\ ----
/\ /t6\ ----
/\ /t7\ ----
33
Bottom up Baumkonstruktion /\ /t1\ ----
/\ /t2\ ----
/\ /t3\ ----
o / /\ /t1\ ----
Programmieren in Haskell
/\ /t4\ ----
/\ /t5\ ----
o \ /\ /t2\ ----
/ /\ /t3\ ----
/\ /t6\ ----
/\ /t7\ ----
o \ /\ /t4\ ----
/ /\ /t5\ ----
\ /\ /t6\ ----
/\ /t7\ ----
33
bubuild :: [a] -> Tree a bubuild = buildTree . map Leaf buildTree buildTree buildTree buildTree
:: [Tree a] -> Tree a [] = Nil [t] = t ts = buildTree (buildLayer ts)
buildLayer buildLayer buildLayer buildLayer
Programmieren in Haskell
:: [Tree a] -> [Tree a] [] = [] [t] = [t] (t1:t2:ts) = Br t1 t2:buildLayer ts
34
Berücksichtigung von Läufen
16 14 13 4 9 10 11 5 1 15 6 2 3 7 8 12 16 14 13 4 | 9 10 11 | 5 1 | 15 6 2 | 3 7 8 12.
Programmieren in Haskell
35
runs runs runs runs
:: [a] -> [] = [a] = (a:b:x) =
[[a]] [[]] [[a]] if a<=b then ascRun b [a] x else descRun b [a] x
ascRun, descRun :: a -> [a] -> [a] -> [[a]] ascRun a as [] = [reverse (a:as)] ascRun a as (b:y) = if a<=b then ascRun b (a:as) y else reverse (a:as):runs (b:y) descRun a as [] = [a:as] descRun a as (b:y) = if a<=b then (a:as):runs (b:y) else descRun b (a:as) y
Programmieren in Haskell
36
Geschmeidiges Merge-Sort
smsort :: Ord a => [a] -> [a] smsort = mergeRuns . build’ . runs mergeRuns :: Ord a => Tree [a] -> [a] mergeRuns (Leaf x) = x mergeRuns (Br l r) = merge (mergeRuns l) (mergeRuns r)
Programmieren in Haskell
37
Nachtrag zu Listen
[1 ..] Liste der positiven Zahlen, [1 .. 99] Liste der positiven Zahlen bis einschließlich 99, [1, 3 ..] Liste der ungeraden, positiven Zahlen, [1, 3 .. 99] Liste der ungeraden, positiven Zahlen bis einschließlich 99.
Programmieren in Haskell
38
Listenbeschreibungen (list comprehensions)
squares :: [Integer] squares = [n*n | n <- [0..99]]
Programmieren in Haskell
39
Listenbeschreibungen (list comprehensions)
squares :: [Integer] squares = [n*n | n <- [0..99]]
map’ f x = [f a | a <- x] squares = map (\n -> n * n) [0..99] a ‘elem‘ x = or [a==b | b <- x]
Programmieren in Haskell
39
divisors :: (Integral a) => a -> [a] divisors n = [d | d <- [1..n], n ‘mod‘ d == 0] primes :: (Integral a) => [a] primes = [n | n <- [2..], divisors n == [1,n]]
Programmieren in Haskell
40
divisors :: (Integral a) => a -> [a] divisors n = [d | d <- [1..n], n ‘mod‘ d == 0] primes :: (Integral a) => [a] primes = [n | n <- [2..], divisors n == [1,n]]
qsort’’ :: (Ord a) => [a] -> [a] qsort’’ [] = [] qsort’’ (a:x) = qsort’’ [b | b <- x, b < a] ++ [a] ++ qsort’’ [ b | b <- x, b >= a]
Programmieren in Haskell
40
[(a,b) | a <- [0,1], b <- [1..3]] ⇒
Programmieren in Haskell
[(0,1),(0,2),(0,3),(1,1),(1,2),(1,3)]
41
Felder (Arrays) squares’ :: Array Int Int squares’ = array (0,99) [(i,i*i) | i <- [0..99]]
squares’!7 ⇒ 7*7 ⇒ 49
Programmieren in Haskell
42
Felder (Arrays) squares’ :: Array Int Int squares’ = array (0,99) [(i,i*i) | i <- [0..99]]
squares’!7 ⇒ 7*7 ⇒ 49
multTable :: Array (Int, Int) Int multTable = array ((0,0),(9,9)) [((i,j),i*j) | i <- [0..9], j <- [0..9]]
Programmieren in Haskell
42
Funktionen auf Indextypen
range inRange array bounds assocs (!)
:: :: :: :: :: ::
Programmieren in Haskell
(Ix (Ix (Ix (Ix (Ix (Ix
a) a) a) a) a) a)
=> => => => => =>
(a,a) (a,a) (a,a) Array Array Array
-> [a] -> a -> Bool -> [(a,b)] -> Array a b a b -> (a,a) a b -> [(a,b)] a b -> a -> b
43
Funktionstabellierung
tabulate :: (Ix a) => (a -> b) -> (a,a) -> Array a b tabulate f bs = array bs [(i, f i) | i <- range bs]
Programmieren in Haskell
44
Funktionstabellierung
tabulate :: (Ix a) => (a -> b) -> (a,a) -> Array a b tabulate f bs = array bs [(i, f i) | i <- range bs]
∀i <- range bs : (tabulate f bs)!i == f i
Programmieren in Haskell
44
Anwendung Tabellierung
badfib 0 = 1 badfib 1 = 1 badfib n = badfib (n-2) + badfib (n-1) fib n = t!n where t = tabulate f (0,n) f 0 = 1 f 1 = 1 f n = t!(n-2) + t!(n-1)
Programmieren in Haskell
45
listArray :: (Ix a) => (a,a) -> [b] -> Array a b listArray bs vs = array bs (zip (range bs) vs)
zip [a1 ,a2 ,...] [b1 ,b2 ,...] = [(a1 ,b1 ),(a2 ,b2 ),...]
Programmieren in Haskell
46
Binäre Suche
binarySearch :: (Ord b, Integral a, Ix a) => Array a b -> b -> Bool binarySearch a e = within (bounds a) where within (l,r) = l <= r && let m = (l + r) ‘div‘ 2 in case compare e (a!m) of LT -> within (l, m-1) EQ -> True GT -> within (m+1, r)
Programmieren in Haskell
47
Anwendung: Ein lineares Sortierverfahren countingSort :: (Ix a) => (a, a) -> [a] -> [a] countingSort bs x = [ a | (a,n) <- assocs t, i <- [1..n]] where t = accumArray (+) 0 bs [(a,1) | a <- x, inRange bs a]
Programmieren in Haskell
48
Anwendung: Ein lineares Sortierverfahren countingSort :: (Ix a) => (a, a) -> [a] -> [a] countingSort bs x = [ a | (a,n) <- assocs t, i <- [1..n]] where t = accumArray (+) 0 bs [(a,1) | a <- x, inRange bs a]
listSort :: (Ix a) => (a, a) -> [[a]] -> [[a]] listSort bs xs | drop 8 xs == [] = insertionSort xs | otherwise = [[] | [] <- xs] ++ [a:x | (a, ys) <- assocs t, x <- listSort bs ys] where t = accumArray (\y b -> b:y) [] bs [(a,x) | (a:x) <- xs]
Programmieren in Haskell
48
Array-Update
(//) :: (Ix a) => Array a b -> [(a, b)] -> Array a b unitMatrix :: (Ix a, Num b) => (a,a) -> Array (a,a) b unitMatrix bs@(l,r) = array bs’ [(ij,0) | ij <- range bs’] // [((i,i),1) | i <- range bs] where bs’ = ((l,l),(r,r))
Programmieren in Haskell
49
Pascalsches Dreieck 0
1
2
3
4
5
6 7
8
0 1
Programmieren in Haskell
1 1
1
2 1
2
1
3 1
3
3
1
4 1
4
6
4
1
5 1
5
10
10
5
1
6 1
6
15
20
15
6
7 1
7
21
35
35
21
7 1
8 1
8
28
56
70
56
28 8
1
1
50
pascalsTriangle :: Int -> Array (Int,Int) Int pascalsTriangle n = a where a = array ((0,0),(n,n)) ( [((i,j),0) | i <- [0..n], j <- [i+1..n]] ++ [((i,0),1) | i <- [0..n]] ++ [((i,i),1) | i <- [1..n]] ++ [((i,j),a!(i-1,j) + a!(i-1,j-1)) | i <- [2..n], j <- [1..i-1]])
Programmieren in Haskell
51
Binomialkoeffizienten
(x + y)n
=
! n X n k n−k x y k
k=0
n k
Programmieren in Haskell
! =
8 <
n! (n−k)!k!
06k6n
:
0
06n