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<k
,
52