To metoder kan løse samme problem og likevel ikke være i samme klasse. Skal vi pare givere og mottakere i et donasjonsregister, kan vi prøve alle koblinger etter tur, eller vi kan kjøre Ford-Fulkerson. Med fire personer på hver side har opptellingen 24 permutasjoner å gå gjennom, mens Ford-Fulkerson bruker rundt 36 operasjoner. Opptellingen ser billigst ut. Dobler vi til åtte, står det 40 320 mot 70.
Problem, instans og maskin
Et problem er en relasjon mellom input og output. Sortering krever at outputen er en stigende omordning av inputen, og sier ingenting om hvilke tall som kommer inn. En instans er én bestemt input, for eksempel . Problemstørrelsen er plassen en instans trenger, som regel antall elementer i den.
Kjøretiden er en funksjon av . For at den funksjonen skal si noe som gjelder på andre maskiner enn din egen, regner vi i RAM-modellen: aritmetikk, sammenligning, hopp og oppslag i én celle koster én tidsenhet hver, og instruksjonene utføres etter hverandre. Heltallene har begrenset bredde, om lag bits, akkurat nok til å indeksere hele inputen. Vi kan regne med større tall enn det, slik kryptografi gjør, men da holder ikke antagelsen om konstant tid per operasjon lenger.
Vekstrater
Anta ett mikrosekund per operasjon og hundre år til rådighet. Da rekker vi disse problemstørrelsene:
| Kjøretid | Navn | Største på hundre år |
|---|---|---|
| logaritmisk | ||
| lineær | ||
| linearitmisk | ||
| kvadratisk | ||
| kubisk | ||
| eksponentiell | ||
| faktoriell |
En raskere maskin hjelper mindre enn en bedre algoritme. Kjøper du en maskin som er tusen ganger raskere, flytter du de 17 faktorielle elementene til 20. Bytter du til en kvadratisk algoritme, flytter du dem til titalls millioner.
Asymptotisk notasjon
Vi vil sammenligne vekst uten å måtte telle enkeltoperasjoner. Asymptotisk notasjon gjør det ved å se bort fra konstantfaktorer og lavere ordens ledd.
Grensen trenger bare å holde fra en eller annen og oppover, og kan skaleres med en konstant først. Derfor er . For stor nok er lite mot det kvadratiske leddet, og faktoren går inn i .
De to strenge operatorene
sier at vokser høyst så raskt som , og tillater at de to vokser like raskt. og er begge sanne, men bare den første er stram. For en øvre grense som ikke er stram bruker vi , og der må holde for enhver , ikke bare for én eneste. Det er en langt sterkere påstand, og den er ekvivalent med at går mot null. Så , mens .
er det samme speilvendt: en nedre grense som ikke er stram, med for enhver . Da vokser over alle grenser, og er nøyaktig det samme som .
Alle fem oppfører seg som sammenligning av to tall, og det er den raskeste veien til å huske dem:
| Skriver vi | Leses som | Grensen | Som tall |
|---|---|---|---|
| vokser høyst så raskt som | endelig | ||
| vokser minst så raskt som | større enn null | ||
| vokser like raskt som | en konstant, verken eller | ||
| vokser strengt saktere enn | |||
| vokser strengt raskere enn |
Notasjonen dukker også opp midt inne i et uttrykk. Da står den for en ukjent funksjon av den oppgitte ordenen, slik at betyr « pluss en eller annen funksjon som er ». Slike ligninger leses fra venstre mot høyre, og høyresiden må være minst like generell som venstresiden. Derfor stemmer , mens er galt.
Finn det enkleste ekvivalente uttrykket, uten å tape presisjon.
Vis løsning Skjul løsning
Ta de øvre og de nedre grensene hver for seg. gir en øvre grense og ingen nedre, så den nedre er . gir en nedre grense og ingen øvre. gir begge, .
Summen av de nedre grensene er . Summen av de øvre er ubegrenset, fordi ikke setter noen tak. Vi står da igjen med en nedre grense alene, og en nedre grense alene skrives med . Regelen står som én rad i Formelsamlingen.
Grenser og tilfeller
For hver finnes det mange instanser, og de koster ulikt, så størrelsen alene bestemmer ikke kjøretiden. Beste tilfelle er den billigste instansen av størrelse , verste tilfelle den dyreste, og gjennomsnittstilfellet er middelet over alle instansene av den størrelsen. Hvert av de tre er en egen funksjon av .
Hvert punkt er én instans, kjørt gjennom Insertion-Sort og talt opp. De mørke punktene er den billigste og den dyreste. Velg tilfelle og grense, og se at alle ni kombinasjonene sier noe sant.
Hvilket tilfelle vi snakker om, og hvor stramt vi begrenser det, er to uavhengige valg. Alle fem operatorene kan brukes på alle tre funksjonene:
| Tilfelle | |||
|---|---|---|---|
| Beste | |||
| Gjennomsnittlige | |||
| Verste | |||
| Ukjent input | ingen |
Utelater du tilfellet, gjelder påstanden alle instanser. «Insertion-Sort bruker » er da riktig, og «» også, mens «» er galt: på en sortert tabell bruker den .
Løkkeinvarianter
En løkke gjør det samme trinnet om og om igjen, og korrekthetsbeviset for den er et induksjonsbevis. Vi trenger en påstand om tilstanden som holder ved starten av hver iterasjon.
Initialisering er grunntilfellet og vedlikehold er induksjonssteget. Terminering er det løkker har i tillegg: beviset er verdiløst om løkka aldri stopper.
Svak induksjon antar bare forrige trinn og passer til løkker. Sterk induksjon antar alle mindre tilfeller, og passer når en algoritme kaller seg selv på flere mindre instanser.
Insertion-Sort
Læreboka skriver algoritmen i pseudokode med indekser fra 1. Under står den i Python, med indekser fra 0, slik øvingen krever.
Invarianten er at A[0 : i] er sortert ved starten av hver iterasjon av
for-løkka, og består av de elementene som lå der fra begynnelsen. Før første
iterasjon er , og utsnittet har ett element. Én iterasjon løfter ut
A[i], skyver hvert større element i utsnittet ett hakk mot høyre og setter
nøkkelen inn i den ledige plassen, slik at A[0 : i+1] er sortert. Når løkka
stopper er utsnittet hele tabellen.
def insertion_sort(A, n):
for i in range(1, n):
key = A[i]
# Sett key inn i den sorterte delen A[0 : i]
j = i - 1
while j >= 0 and A[j] > key:
A[j + 1] = A[j]
j = j - 1
A[j + 1] = key
return A Grønt er den sorterte delen. Nøkkelen løftes ut i boksen over tabellen, den ledige plassen flytter seg mot venstre så lenge naboen er større, og nøkkelen settes inn når naboen ikke er det.
Den ytre løkka går ganger uansett input. Den indre gjør et sted mellom 0
og forskyvninger, og det er der forskjellen mellom tilfellene ligger. Er
tabellen sortert fra før, er A[j] aldri større enn nøkkelen, while-testen
slår feil med én gang, og vi gjør arbeid totalt.
Tabellen er sortert synkende, . Hvor mange forskyvninger gjør algoritmen?
Vis løsning Skjul løsning
I iterasjon er A[i] mindre enn hvert av de elementene foran seg, så
nøkkelen må forbi dem alle. Det er forskyvninger. Lagt sammen over
iterasjonene :
Summen er trekanttallet, et halvt rektangel med sider og . Uten konstantfaktoren og det lineære leddet står vi igjen med .
| Tilfelle | Kjøretid | Instans |
|---|---|---|
| Beste | sortert fra før, ingen forskyvninger | |
| Gjennomsnittlige | tilfeldig rekkefølge, halve utsnittet i snitt | |
| Verste | sortert synkende |