Eerste 6 colleges118.62 KB

advertisement
Binair zoeken
Op een wiskundige manier naar programmeren kijken om je code sneller te maken.
Je probeert van te voren na te denken over wat je vaak gaat doen met je data, als je veel in je data
moet zoeken wil je dat zoeken goedkoop is. Maar mocht je vaak data in gaan voegen dan is het juist
voordelig om een data type te kiezen waar invoegen goedkoop is.
Ongesorteerde rij:
Invoegen is heel goedkoop, zet het nieuwe element gewoon op de eerstvolgende nieuwe plek. Maar
zoeken is duur want je moet elk element vergelijken omdat er verder geen structuur in zit.
Tijd voor toevoegen is constant O(1).
Tijd voor zoeken is lineair met het aantal elementen, dus O(n).
Gesorteerde rij:
Zoeken is nu een stuk goedkoper, stel i < j → A[i] ≤ A[j].
Maar het toevoegen is nu een stuk duurder, je moet van achteraf alles 1 plek opschuiven wat groter
is dan je nieuwe waarde. Dit kost weer O(n).
Binary search:
Je kan nog sneller zoeken door binary search te gebruiken, hiermee kan je steeds de helft van je
array uitsluiten:
- Houd twee variabelen bij, je boven en onder grens van je zoekgebied.
- Kijk in het midden van je zoekgebied en beslis of je in de linker of rechter helft verder gaat.
- Pas je boven- of ondergrens aan.
- Stop als je gebied klein genoeg (dus meestal 1) is.
De tijd die je nodig hebt voor binary search is O(lg(n)).
Invariant:
Een loop is ingewikkeld want het zijn vier factoren die van elkaar afhangen:
I – Initialisatie, welke variabelen gebruik je en hoe geef je ze hun eerste waarde.
C – Conditie, wanneer stop je?
B – Body, wat doe je in de lus?
T – Terminatie, hoe ga je verder als de loop klaar is.
De invariant is een Predikaat (uitspraak) over (al) je variabelen en je data, waarvoor je zorgt dat die
waar is, zowel voor als na elke slag van je loop.
Als je een invariant gekozen hebt (we noemen deze hier P) moet je op een paar dingen letten:
- Na je initialisatie moet je P waar zijn
- Nadat je een body hebt gedaan moet je P nog steeds waar zijn!
- Je moet je conditie zo kiezen dat je uit ¬c^p iets nuttigs kan concluderen.
- Bepaal aan de hand van ¬c^p wat je vervolgstappen zijn.
Variant:
Maar een programma met een lege body is dus een correct programma (na initialisatie geldt je I en
je invariant blijft dus steeds waar). Daarom moet je om te zorgen dat je programma een keer
termineert een actie in je body doen die een bepaalde “grootheid” altijd laat dezen, dit noem je de
variant.
Bewijzen en inductie
Je moet iets bewijzen, maar je beschikt ook over een bepaald gegeven, bijvoorbeeld:
1
Te bewijzen:
1 + 2 + 3 + …. + n + (n+1) = 2(n+1)(n+2)
Gegeven:
1 + 2 + 3 + …. + n + (n+1) = 2(n+1)
1
Je moet bij het bewijzen van je gegevens naar je resultaat redeneren, niet andersom (dus geen Ubewijs)!
Lineair bewijs met hint-calculus:
De linker en rechterkant noemen de
LHS en RHS.
Deze is handig om te onthouden
|
V
En verder geef ik de hoop in het bewijzen beetje op……
Quicksort
Elke methode die onderling objecten vergelijkt, valt onder camparison sort.
Je noemt een sorteer methode stabiel als records met gelijke key in dezelfde onderlinge volgorde
blijven staan.
Insertion sort:
Insertion sort zorgt er voor dat na i ronde op de eerste i plaatsen, i elementen op volgorde staan.
Kost O(n2) tijd.
Invariant: na i ronden bevalt A[0..i-1] dezelfde keys als in de input, in oplopende volgorde.
Selection sort:
Bij selection sort staan na de eerste i ronden op de eerste i plaatsen de i kleinste getallen. Je gaat
dus steeds alle getallen langs en zet dan de kleinste weer vooraan.
Kost ook O(n2) tijd, maar heeft lineair veel (O(n)) data verplaatsingen.
Invariant: Na i ronden bevat A[0..i-1] de i kleinste keys, in oplopende volgorde.
Quicksort:
Je hebt een pivot, alles wat kleiner is dan de pivot zet je links, alles wat groter is dan de pivot zet je
rechts.
Je kan dit recursief doen waardoor uiteindelijk alles op volgorde staat.
Je kiest je pivot random binnen je array (de pivot moet in je array voorkomen).
Snelheid is O(n lg (n)).
Analyse
Je kan voor elk ding kijken hoe lang het duurt (in ms) en dat bij elkaar optellen. Maar het is beter om
te abstraheren en iets als O te gebruiken.
O(n2)
Ω(n2)
ϴ(n2)
De leidende term is hoogstens n2 (uitdrukken hoe “goed” iets is).
De leidende term is minstens n2 (uitdrukken hoe “slecht” iets is).
De leidende term is precies n2.
O(n2) = O(n3) is waar! Maar O(n3) = O(n2) is niet waar.
Insertion sort heeft een lagere constante, en zal bij een kleine invoer dus sneller zijn dan quicksort.
Maar bij een grote invoer kan je zien dat quicksort altijd sneller is omdat ze leidende term kleiner is.
Je zetten binnen de O() geen constante neer!
O(n2) + O(n) = O(n2)
O(n2) + O(n2) = O(n2)
Exponentieel:
N zit in de exponent, dus 2n, 5n, 3n/2.
Binnen deze catagorie is een hoger grondtal leidend -> 2n + 3n = O(3n)
Let op:
Exponentieel telt zwaarder dan polynominaal.
Polynominaal:
Is een macht van n, zoals n, n2, n3 of √𝑛.
Hier is altijd de term met de hogere exponent de leidende term.
Polynominaal telt zwaarder dan logaritmisch.
Logaritmisch (eigenlijk: polylogaritmisch):
Een macht van lg(n), dus lg(n), lg2(n).
Het grondtal maakt niet uit -> O(lg(n)), O(ln(n)) en O(log(n)) zijn allemaal hetzelfde.
Macht binnen de lg maakt ook niet uit, O(lg(n2)) is hetzelfde als O(lg(n)).
Sommaties
Onder het sommatie teken zet de ondergrens van je domein naar, en aan de bovenkant de
bovengrens van je domein.
Rekenkundige reeks:
Een rij getallen is een rekkenkundige rij als het verschil van opvolgende teremen constant is.
Je moet het constant hier zien als -> niet afhangkelijk van de sommatie variabele.
De uitkomst van zo’n reeks -> aantal * (eerste + laatste) en dat alles delen door 2.
Meetkundige reeks:
In een meetkundige reeks is het quotient van opeenvolgende termen constant (vermenigvuldigen
met constante factor).
Als de eerste term waarde A heeft en de groeifactor r is, dan is de algemene vorm van de formule
A * ri.
𝑉𝑜𝑙𝑔𝑒𝑛𝑑𝑒−𝑒𝑒𝑟𝑠𝑡𝑒
.
𝑔𝑟𝑜𝑒𝑖𝑓𝑎𝑐𝑡𝑜𝑟 − 1
𝑥
xi” =
als |x|
(𝑥−1)2
Om de uitkomst te berekenen kan je ->
De soms van 0 naar oneindig voor “i *
< 1.
Rekenregels sommaties:
Zie pagina 6 t/m 8 van de aantekeningen van Gerard.
Harmonische reeks:
Als je iets moet sommeren met de sommatie variabele in de noemer, dan is er geen simpele
oplossing.
𝑛 1
Je noemt het harmonisch getal: ∫𝑖=1 𝑖 . Dit heeft ongeveer de waarde ln(n).
Lineair sorteren
Kan je minder dan lg(n) per key nodig hebben voor sorteren? Ja, maar alleen als je extra informatie
hebt over je keys. Maar niet als je afhankelijk bent van vergelijken.
Counting sort:
Aanname: de keys komen uit een (zeer kleine) verzameling van M elementen.
Je kijkt eerst hoe vaak elk element voorkomt door ze te tellen, daarna genereer je een rij met
opeenvolgend deze data (als er geen extra informatie aan de key verbonden is).
Je krijgt hierdoor twee loops. Meestal is M kleinere dan N dus kost het O(n) tijd.
Geheugengebruik:
Een in-situ algoritme gebruikt een constante hoeveelheid extra geheugen. Bij quicksort is dit O(lg(n))
door de recursie.
Bij counting sort heb je sowieso M tellertjes extra nodig. Maar als er ook nog extra data aan je key zit
dan heb je nog een kopie van je array nodig, dus kan je maar de helft van je geheugen gebruiken.
Wel in-situ:
Niet in-situ:
QuickSort, InsertionSort, HeapSort
CountingSort, BucketSort, MergeSort
Radix Sort:
Een soort uitbreiding van Counting sort.
Je neemt hier aan dat elke key uit een vast aantal stkujes bestaat, waarop een orderning bestaat.
Radix sort sorteert deze in fasen, waarbij je in elke fase op een van de stukjes sorteert.
Je sorteert NIET eerst op de meest significante, maar juist eerst op de minst significante. Dit zorgt
ervoor dat je stabiel sorteert.
In de praktijk gebruik je RadixSort samen met CountingSort.
De digits extracten:
Omdat delen duur is kan je soms beter schuivent met bits, dit is sneller en kost veel minder
rekenkracht.
Bucket sort:
Aanname is dat je keys uniform verdeeld zijn over een bekende interval.
Gevolg is dat je van elke key wel ongeveer kan inschatten waar hij komt.
- In een bucket moet meer dan één key kunnen.
- Als er meerdere keys in één bucket zitten dan moet je deze onderling sorteren (IS).
- genereer de output door alle buckets achter elkaar te plakken.
Extra geheugen -> ϴ(n) want je hebt precies alle waardes nog een keer nodig.
Tijd -> bucket fase ϴ(n), concatenatie fase ook ϴ(n) en de sort fase bekijkt n lijsten met samen n keys,
dus uiteindelijk (gemiddeld) ook ϴ(n).
Dus ϴ(n) extra geheugen en ϴ(n) tijd.
Download