Algoritmiek 2016 / 2017 Algoritmiek
Waarom dit vak? Omdat Mensen ongeduldig zijn … Het belangrijk is dat antwoorden (van berekeningen door computers) snel / op tijd komen (en juist zijn) Dus leren we Algoritmische technieken kennen, toepassen, analyseren Belangrijke algoritmen voor centrale problemen kennen en begrijpen Algoritmiek
Docenten Hans Bodlaender Buys Ballotgebouw 503 030-2534409 h.l.bodlaender@uu.nl Maandags afwezig Erik Jan van Leeuwen Werkt bij UU vanaf midden maart Algoritmiek
Team werkcollege en practicum Werkcollegeleider: Tom van der Zanden Studentassistenten (werkcollege, clarifications domjudge): Gerben Aalvanger Casper Hagenaars Max Hessey Jan Posthoorn Algoritmiek
Onderdelen College Werkcollege Practicum Tentamens Algoritmiek
Onderdelen College Twee keer per week, belangrijke aanvulling op stof uit boek Powerpoints op website Werkcollege Practicum Tentamens Algoritmiek
Onderdelen College Werkcollege Twee keer per week: let op de tijden Belangrijk: oefenen van stof is belangrijker dan er naar luisteren Twee keer een “autodiagnostische toets” (proeftentamen Vragen over: opgaven, practicum (maar zonder naar code te kijken) Practicum Tentamens Algoritmiek
Onderdelen College Werkcollege Practicum 6 opgaven met Domjudge (straks meer) Tentamens Algoritmiek
Onderdelen College Werkcollege Practicum Tentamens Twee deeltentamens, elk over ongeveer helft van de stof Je mag meenemen: 4 kantjes A4 (2 vel A4 aan beide zijden beschreven, 4 vel A4 aan 1 kant beschreven, normaal lettertype, zelfgemaakt) Algoritmiek
Cijfer Beide deeltentamens tellen even zwaar Eindcijfer: Hooguit 3 practicumopgaven goed: onvoldoende 4 practicumopgaven goed: gemiddeld tentamencijfer afgerond 5 practicumopgaven goed: gemiddeld tentamencijfer afgerond + 0.5 6 practicumopgaven goed: gemiddeld tentamencijfer afgerond + 1 Afronding: 5.5 gaat naar 6, 5.49 naar 5. Boven de 6 op halve punten afgerond. Algoritmiek
Herkansingsregeling Je kan of: 1 practicumopgave inhalen 2 practicumopgaven inhalen Hertentamen doen 1 practicumopgave inhalen en hertentamen doen Hertentamen vervangt laagste deeltentamencijfer en gaat over de hele stof Algoritmiek
Practicum 6 opgaven, inleveren via Domjudge 1e opgave niet triviaal, maar veel makkelijker dan de rest Latere opgaven veel moeilijker. Opgave 6 is het moeilijkst Je moet er minstens 4 halen. Let op de deadlines! Te laat is te laat… Dus: begin op tijd! Algoritmiek
Alleen werken Je moet de opgaven alleen maken Er wordt gecontroleerd op fraude, en bij een vermoeden van fraude wordt de regeling van de Universiteit Utrecht gevolgd en is het eindcijfer van dit vak een 1 Je mag wel kort (1.5 minuut) met elkaar praten over het practicum, maar niet: Code of pseudocode van elkaar bekijken (of inkijken, uitwisselen, geven, in ontvangst nemen, debuggen, inleveren, etc.) Algoritmiek
Domjudge Je kan een opgave tot de deadline zo vaak inleveren als je wilt Tot in het redelijke… Je ingeleverde code wordt: Gecompileerd en gedraaid met een aantal geheime testinvoeren Je krijgt te horen: Code compileert niet goed Code geeft runerror Code geeft fout antwoord op testinvoer Code gebruikt te veel tijd Correct programma In het algemeen geldt: bij correct programma heb je de opgave gehaald; anders kan je je programma verbeteren en opnieuw inleveren Algoritmiek
Tips en hulp Test je programma ook met eigen testinvoeren en die van de website Clarifications: Vragen over je programma kan je stellen via clarificationssysteem in domjudge aan studentassistenten Verwacht niet altijd en niet direct antwoord Zorg voor leesbare code Hulp door clarifications heeft grenzen… Geen clarifications dag voor de deadline Programma te langzaam: Gebruik je het goede algoritme? Gebruik je de goede constructies uit C#? (Bijvoorbeeld: ingebouwde functies kunnen grote complexiteit hebben) Let op: Geheime inputs Tijd Algoritmiek
Allerlei vragen Over organisatie van vak, cijfers van tentamens, etc: docent Hans Bodlaender Over practicum (cijfers, inloggen, …): Tom van der Zanden Over inhoud: werkcollege, of docent in de pauze Algoritmiek
Inhoud Algoritmiek Algoritmische technieken: Dynamisch programmeren Divide and Conquer Greedy Algorithms Randomized algorithms Analyse van algoritmen Geamortiseerde analyse (o.a. Union-Find datastructuur) NP-volledigheid Grafen en netwerken Kortste paden Opspannende bomen Stroming en snedes Algoritmiek
Dynamisch Programmeren I Algoritmiek
Dynamisch programmeren Een algoritmische techniek Werkt voor veel verschillende problemen Soms snel, soms langzaam Soms makkelijk, soms moeilijk Algoritme wordt gemaakt door volgen van een `stappenplan’ Het maken van een DP algoritme vereist: methodisch werken: volg de ontwerpmethode; inzicht in de structuur van het op te lossen probleem, en wat geduld en creativiteit. Algoritmiek
Inhoud Voorbeelden De techniek Het stappenplan Nog meer voorbeelden, en ingewikkelder toepassingen van Dynamisch Programmeren Implementatie details Algoritmiek
Eerste voorbeeld Fibonaccigetallen F(0) = 0; F(1) = 1 Als i > 1, dan is F(i) = F(i – 1) + F(i – 2) Hoe reken je F(6) uit? Algoritmiek
Recursief Method Fibonacci (integer i) if i == 0 then return 0; Return Fibonacci(i – 1) + Fibonacci(i – 2) Algoritmiek
Beter: een ‘dynamisch programma’ Method FibonacciDP (integer i) if i == 0 then return 0 if i == 1 then return 1 Maak een integer array F[0 … i] aan F[0]=0; F[1]=1; for j == 2 to i do F[j]=F[j – 1]+F[j – 2] enddo. Return F[j] Algoritmiek
De driehoek van Pascal 1 2 3 4 6 Function C(n,k) if (k == 0) or (k == n) then return 1 else return C(n – 1, k – 1) + C(n – 1, k) Algoritmiek
Recursief of met Memorisatie? In functionele taal als bijv. Haskell (hangt af van compiler): Iedere term C(i,j) wordt maar 1 keer uitgerekend. Kost O((n+1)(k+1)) tijd, zeg O(nk). In imperatieve taal: Verschillende termen worden vaker dan 1 keer uitgerekend Kost veel tijd: berekenen van C(n,k) kost Q(C(n,k)) tijd!! Function C(n,k) if (k == 0) or (k == n) then return 1 else return C(n – 1, k – 1) + C(n – 1, k) Even voorbeeld uitwerken op het bord. Voor het Haskell programma: iedere aanroep kost O(1) tijd als we de tijd in de recursieve aanroepen niet bij die aanroep tellen. C(i,j) wordt aangeroepen Voor alle waarden i tussen 0 en n en alle waarden j tussen 0 en k; dat zijn er dus (n+1)(k+1). Algoritmiek
Driehoek van Pascal Imperatief Function C(n,k) Neem 2-dimensionaal array A(0…n, 0…k) for i=0 to n do for j=0 to min(i,k) do if (j == 0) or (j == i) then A(i,j) = 1 else A(i,j) = A(i – 1, j – 1) + A(i – 1, j) Return A(n,k) Zelfde stappen als in recursief algoritme, maar … Algoritmiek
Equivalent Function C(n,k) Neem 2-dimensionaal array A(0…n, 0…k) for i=0 to n do A(i,0) = 1; for i=0 to k do A(i,i) = 1; for i=0 to n do for j=0 to min(i,k) do A(i,j) = A(i – 1, j – 1) + A(i – 1, j) Return A(n,k) Initialisatie buiten de hoofdloop gehaald: dit is net iets efficienter Algoritmiek
Analyse dynamisch programmeer algoritme O(nk) tijd Kan in O(k) geheugenruimte door maar twee regels van array te gebruiken Function C1(n,k) Neem arrays A(0…k) en B(0 … k) for i=0 to n do for j=0 to min(i,k) do if (j == 0) or (j == i) then A(j) = 1 else A(j) = B( j – 1) + B(j) for j=0 to k do B(j) = A(j) Return A(k) Algoritmiek
1 2 3 4 6 5 10 15 20 7 21 35 Overigens: de waarde n over k kan ook in O(n) tijd worden uitgerekend door gewoon de basisformule te volgen. Dit kan wel lastiger worden als n en k relatief groot zijn, en we zonder afrondingen willen werken. Algoritmiek
Voorbeeld 2: Simplified Risk Deel van spel: Twee `legers’: i rode steentjes en j blauwe steentjes. Beide spelers gooien een dobbelsteen. Als de rode speler meer dan de blauwe speler gooit, dan gaat er 1 blauw steentje weg, anders 1 rood steentje. Een speler wint als de tegenstander geen steentjes meer heeft P(i,j) is kans dat rode speler wint. P(i,j) = 15/36 * P(i,j – 1) + 21/36 * P(i – 1, j) Function P(i,j) if (i == 0) then return 0 else if (j == 0) then return 1 else return 15/36 * P(i,j – 1) + 21/36 * P(i – 1, j) Risk is a trademark of Parker Brother / Hasbro. Algoritmiek
Simplified Risk (2) Met Memorisatie (functioneel): snel Recursief imperatief: Te veel tijd Function P(i,j) if (i == 0) then return 0 else if (j == 0) then return 1 else return 15/36 * P(i,j – 1) + 21/36 * P(i – 1, j) Algoritmiek
j: aantal steentjes blauwe speler 1 2 4 3 * 1 1 15/36 i: aantal van rode speler 85/144 2 3 4 Algoritmiek
Berekeningsvolgorde Function SRP(i,j) Maak 2 dim array P(0 … i,0 … j) for r = 0 to i do for b = 0 to j do Bereken P(r,b) Return P(i,j) Algoritmiek
DP algoritme voor Simplified Risk Function SRP(i,j) Maak 2 dim array P(0 … i,0 … j) for r = 0 to i do for b = 0 to j do if (r == 0) then P(r,b) = 0 else if (b == 0) then P(r,b) = 1 else P(r,b) = 15/36 * P(r,b – 1) + 21/36 * P(r – 1, b) Return P(i,j) Algoritmiek
Basisidee Dynamisch Programmeren Reken iets niet voor de tweede keer uit ‘iets’: deelprobleem Twee mogelijkheden: `Klassiek’ DP: vul datastructuur met antwoorden voor deelproblemen, zodat nodige gegevens al eerder zijn berekend Memorisatie: kijk of we ‘t al eerder hebben uitgerekend – zo ja, geef dat antwoord; zo nee: reken uit en sla antwoord op in datastructuur (bijvoorbeeld array of hashtabel) Algoritmiek
De ontwerpmethode (1) Identificeer `deelproblemen’, waarbij helpt Wat is de `rij van keuzes die leidt tot een oplossing’? Wat is de `top choice’ (laatste keuze)? Ontwerp een recurrente betrekking voor het probleem: druk het probleem uit in termen van (andere) deelproblemen. Geef ook basisgevallen Top choice helpt (vaak: gevalsonderscheid) Wat is de berekeningsvolgorde? (Mag geen cycle hebben…) Bereken in volgorde de waardes voor de deelproblemen. Eventueel: geheugenbesparing of constructieve versie. Algoritmiek
Voorbeeld 3 Gepast betalen Stel, we hebben munten van waardes a1, … ar en we willen b betalen. Hoe kunnen we dit doen met zo min mogelijk munten? Euro’s kunnen greedy (komt later deze collegeserie), maar andere sets munten niet altijd. Greedy: geef steeds grootst mogelijke munt terug, bijv: 84: 50 (34), 20 (14), 10 (4), 2 (2), 2. Met munten van 1, 10, 25 geeft greedy niet altijd goede antwoord: Greedy geeft voor 30 cent: 25, 1, 1, 1, 1, 1 (6 munten), maar optimaal is: 10, 10, 10 (3 munten) Hier: DP algoritme voor `wisselgeld probleem’. Algoritmiek
Gepast betalen: probleemstelling Gegeven: positieve gehele getallen a1, …, ar, niet negatief geheel getal b Gevraagd: op welke manier kan b verkregen worden door zo min mogelijk munten van waardes a1, …, ar ? Munten mogen ieder niet-negatief aantal keren gebruikt worden. Totale som moet b zijn. We zoeken dus x1 , … , xr in N= {0,1,…} met en zo klein mogelijk Algoritmiek
Rij van keuzes Hoeveel nemen we van de eerste munt? Hoeveel nemen we van de tweede munt? … Hoeveel nemen we van de laatste munt? Oftwel: x1 , … , xr Top choice: xr Algoritmiek
Hoe ziet de situatie eruit als we al een aantal keuzes gemaakt hebben? Stel we hebben al van de eerste q munten besloten hoeveel te nemen. Situatie kan je karakteriseren met: q Aantal munten tot nu toe gebruikt Totaalbedrag van de tot nu toe gebruikte munten Dus: (q, , ) Algoritmiek
Deelproblemen Gegeven a1, …, ar, b, schijf W(i,c) als het minimum aantal munten Nodig om bedrag c te krijgen Als we alleen munten a1, …, ai mogen gebruiken Vb: Munten van waarden 1, 5, 8, 20 W(1,7) = 7 (we mogen alleen munt 1 gebruiken) W(2,7) = 3 (beste oplossing is 5, 1, 1) W(3,7) = W(4,7) = 3 (nog steeds) W(1,23) = 23; W(2,23) = 7; W(3,24) = 5 (met: 8,8,5,1,1); W(4,23) = 4 (met: 20,1,1,1) Algoritmiek
Speciale gevallen Munten met waardes 3, 5, 10, 30 W(*,0) = 0: geen munten nodig om 0 te betalen. W(4,7) = +¥ (kan niet gemaakt worden) W(0,5) = +¥ (geen munten dus kan niet gemaakt worden) Algoritmiek
Recurrente betrekkingen W(i, 0) = 0 voor alle i ³ 0. W(0, y) = +¥ voor alle y > 0. Als i > 0 en y > 0 en ai > y, dan W(i, y) = W( i – 1, y) Munt ai kan niet gebruikt worden om y te betalen. Als i > 0 en y > 0 en ai £ y, dan W(i, y) = min ( W( i – 1, y) , W(i, y – ai) + 1) Munt ai kan wel gebruikt worden. Als je de munt niet gebruikt moet je y maken met de eerste i – 1 munten; anders moet je y – ai maken en nog een munt ai erbij. Bekijk beide en neem de beste (minimum). Basisgevallen Gevalsonderscheid met de top-choice Algoritmiek
Recursief programma Wissel( Array A, integers i, y) If (y == 0) then return 0; If (i == 0) then return maxint; If (ai > y) then return Wissel(A, i – 1, y); Return min (Wissel(A, i – 1, y), Wissel(A,i, y – ai ) + 1) Algoritmiek
Voor berekeningen nodig: Element met i-1 en element met y-ai i: munten Voor berekeningen nodig: Element met i-1 en element met y-ai y: bedrag Algoritmiek
Uitrekenen in volgorde Function Wissel(A[1…r], b) Maak 2-dim array W[0…r, 0…b]. W[0,0]=0. for j=1 to b do W[0,j] = +¥. for i=1 to r do for j=0 to b do Bereken W[i,j]. Return W[r,b] Straks preciezer Algoritmiek
Algoritme Function WisselDP(A[1…r], b) Maak 2-dim array W[0…r, 0…b]. for j=1 to b do W[0,j] = +¥. for i=1 to r do for j=0 to b do if (A[i] > j) then W[i,j] = W[i-1,j] else W[i,j] = min (W[i-1,j],W[i,j-A[i]]+1) Return W[r,b] Algoritmiek
Analyse WisselDP gebruikt O(rb) tijd. Als b niet te groot is, is dit snel. Algoritmiek
Niet altijd integer functies Wisselbaar Gegeven munten met waardes a1, …, ar, kan hiermee een bedrag b betaald worden? Wisselbaar({3,5,11},6) = true Wisselbaar({3,5,11},16) = true Wisselbaar({3,5,11},7) = false Zelfde soort algoritme als voor Wissel: nu met Booleans. Algoritmiek
DP algoritme voor Wisselbaar probleem Function Wisselbaar(A[1…r], b) Maak 2-dim array van Booleans W[0…r, 0…b]. W[0,0]=true. for j=1 to b do W[0,j] = false. for i=1 to r do for j=0 to b do if (A[i] > j) then W[i,j] = W[i-1,j] else W[i,j] = W[i-1,j] or W[i,j-A[i]] Return W[x,y] Algoritmiek
Het optimaliteitsprincipe Dynamisch programmeren lukt vaak als: Het niet uitmaakt hoe je er gekomen bent, maar alleen waar je bent Preciezer: Als een oplossing optimaal is, dan zijn diens deeloplossingen ook optimaal. Algoritmiek
Uitwisselargumenten Algoritmiek
Wat is een goede keuze van deelproblemen Vaak werkt dit: Hoe ziet een oplossing voor de hele input eruit? Als we naar een deel (bijv. beginstuk) van de input kijken, wat zien we dan van zo’n oplossing (definieer een notie deeloplossing) Wat is essentieel om te weten van een deeloplossing voor mogelijke uitbreiding tot een ‘hele’ oplossing? Algoritmiek
Eerlijke Boedelverdeling Twee broers moeten een stel voorwerpen verdelen van waarde a1, a2, …, an. Hoe doen ze dat zo eerlijk mogelijk? (D.w.z., verdeel a1, a2, …, an in twee verzamelingen waarvan de som zo weinig mogelijk verschilt.) 1, 5, 8, 10, 11 {8, 10} en {1, 5, 11} is eerlijkste verdeling (verschil 1.) Ook wel eens: twee rovers willen een buit verdelen. Algoritmiek
Stappenplan voor DP Stap 1: wat is de rij van keuzes? Voor de hand liggend: Welke broer krijgt voorwerp 1? Welke broer krijgt voorwerp 2? … Welke broer krijgt voorwerp n? Top choice: welke broer krijgt voorwerp n? Algoritmiek
Stappenplan voor DP Stap 3: Identificeer deelproblemen Vaak werkt volgende strategie: Deelprobleem is: Beginstuk van gebeurtenissen of beslissingen + Situatie na een stel gebeurtenissen en beslissingen Algoritmiek
Boedelprobleem Stap 1 1e poging Beslissingen: welke broer krijgt voorwerp 1; welke broer krijgt voorwerp 2; etc. Idee voor deelproblemen: Wat is de eerlijkste verdeling van de eerste i voorwerpen? Hiermee is geen DP algoritme te maken: optimaliteitsprincipe geldt niet. Voorwerpen 1, 5, 6: eerlijkste verdeling 1e twee voorwerpen zegt niets over eerlijkste verdeling alle drie voorwerpen mislukt Algoritmiek
Waarom werkt dit niet? Er zijn mogelijke begin-keuzes die tot een optimale oplossing leiden die niet in een deelprobleem `gevangen worden’ Voorwerpen 1, 5, 6, 10 Begin: (1 en 5, 6) is geen deel van de optimale oplossing (1 en 10, 5 en 6) Algoritmiek
Boedelprobleem Stap 1 Verdeel(i, c): is er een verdeling van de eerste i voorwerpen met verschil in waarde precies c? 1,5,8,10,11: Verdeel(2,4) = true Verdeel(2,5) = false Verdeel(2,6) = true (1,5 vs niets) Verdeel(3,2) = true (1,5 vs 8) Algoritmiek
Welke deelproblemen zijn relevant? Algoritmiek
Stap 3 Ontwerp recurrente betrekking die oplossing van deelprobleem uitdrukt in oplossingen van andere deelproblemen. Verdeel(0,0) = true Voor c ¹ 0: Verdeel(0,c) = false Voor i>0: Verdeel(i,c) = Verdeel(i-1,c-ai) or Verdeel(i-1,c+ai) ide voorwerp naar 1e of 2e broer: gevalsonderscheid op de top-choice Algoritmiek
Stap 4 i: voorwerpen Wat is de berekeningsvolgorde? Hier: kolomsgewijs, d.w.z, stijgend aantal voorwerpen c: verschil Algoritmiek
Stap 5 Bereken de deelproblemen met behulp van de recurrente betrekking in de gevonden volgorde. Bereken T = Maak array V[0…n, –T…T] for i = 0 to n do for c = -T to T do if i=0 and c=0, then V[i,c]= true else if i=0 and c ¹ 0 then V[i,c]=false else V[i,c] = V[i-1,c-ai] or V[i-1,c+ai] Nu nog het antwoord vinden… Eigenlijk fout: neem een false voor elt die niet in de array zitten Algoritmiek
Vervolg stap 5: Antwoord vinden: poging 1 for c = 0 to T do if V[n,c] == true then return c Dit vindt het kleinst mogelijke verschil in de waarde van de verdeling tussen de broers. Maar… nog niet de eerlijkste verdeling zelf? Hoe dat te doen is Stap 6. To be continued… Algoritmiek
Practicumsom 1: een variant Aantal rijtjes van A naar B A en B getallen, en een rijtje is een rij met D’s en I’s: D: twee keer ‘t vorige getal; I: plus 1 Bijvoorbeeld: 5 met IIDI geeft 15 (6, 7, 14, 15) Practicumsom: gegeven A en B, wat is het aantal (A,B)-rijtjes. Hier: wat is het kortste (A,B)-rijtje? Algoritmiek
Het kortste (A,B)-rijtje Lengte van kortste rijtje … Kortste rijtje Het kortste (A,B)-rijtje Wat is de rij keuzes? Wat is de laatste keuze? Wat zijn de deelproblemen? Recurrente betrekking Uitdrukking in kleinere deelproblemen Basisgeval(len) Berekeningsvolgorde Programma voor lengte van kortste rijtje Versie voor constructie van het kortste rijtje Algoritmiek
Wordt vervolgd Vinden van oplossingen in plaats van waardes van oplossingen Memorisatie Besparen van geheugenruimte Andere toepassingen van dynamisch programmeren Ingewikkelder structuren Algoritmiek