Del tabellen på midten, sorter hver halvdel, og flett de to sorterte halvdelene sammen. Det er hele Merge-Sort, og den bruker tid i alle tilfeller, der Insertion-Sort bruker i verste tilfelle. Beskrivelsen er kort fordi den lener seg på seg selv: «sorter hver halvdel» er et rekursivt kall.
Splitt og hersk
Dekomponering reduserer en instans til mindre instanser av samme problem. Insertion-Sort kutter av ett element om gangen. Splitt og hersk deler i stedet instansen i flere uavhengige deler, gjerne to like store, løser hver del rekursivt og kombinerer delsvarene. Merge-Sort har alle fire trinnene:
-
Grunntilfelle
Er instansen liten nok, løses den direkte. En tabell med ett element er allerede sortert.
-
Del
Del instansen i to eller flere delinstanser som ikke overlapper. Merge-Sort deler tabellen på midten.
-
Løs rekursivt
Kall algoritmen på hver delinstans for seg: sorter hver halvdel.
-
Kombiner
Sett delsvarene sammen til et svar for hele instansen: flett de to sorterte halvdelene.
Delingen og kombinasjonen er kjernen i designet, og som regel ligger arbeidet i bare én av dem. I Merge-Sort er delingen triviell, og alt arbeidet ligger i flettingen. Grunntilfellet og de rekursive kallene ser like ut i hver splitt og hersk-algoritme.
Korrektheten er induksjon over problemstørrelsen, akkurat som for annen dekomponering. Grunntilfellet løses direkte. Induksjonshypotesen sier at algoritmen svarer riktig på alle mindre instanser, ikke bare på instansen som er ett element mindre. Det er sterk induksjon, og den trengs fordi en halvdel har omtrent elementer. Delinstansene er mindre enn , så hypotesen gjelder for dem. Induksjonssteget viser at delingen og kombinasjonen gir riktig svar når delsvarene er riktige: er begge halvdelene sortert, gir flettingen en sortert tabell. Argumentet er det samme for hver størrelse, så du viser det én gang for en vilkårlig instans.
Rekurrenser
Kjøretiden til en rekursiv algoritme er kjøretiden til de rekursive kallene pluss arbeidet utenfor dem. Gjør Merge-Sort operasjoner selv og kaller seg to ganger på halve instansen, er . Det er en rekurrens: en ligning der funksjonen står på begge sider, med mindre argument til høyre. Ligningen bestemmer fullstendig så snart grunntilfellet er kjent, men den sier ikke direkte hva er. Å løse rekurrensen er å bli kvitt på høyresiden.
Grunntilfellet er konstant, for under en grense , og rekursjonen stopper etter endelig mange kall. Læreboka kaller dette en algoritmisk rekurrens, og det antas når ikke annet er sagt. Avrunding sløyfes: skriv for , som om var en toerpotens. Det endrer ikke den asymptotiske løsningen.
Ta med . Sett rekurrensen inn i seg selv til et mønster viser seg, og velg så antall ekspansjoner slik at argumentet treffer grunntilfellet. Det er iterasjonsmetoden:
-
Ekspander
Rekurrensen gjelder også for , så . Sett dette inn for i : .
-
Finn mønsteret
Etter ekspansjoner er .
-
Treff grunntilfellet
Velg slik at argumentet blir grunntilfellet. gir .
-
Sett inn
.
Løs følgende rekurrens eksakt:
Oppgi svaret uten asymptotisk notasjon. Forklar kort.
Vis løsning Skjul løsning
Iterasjonsmetoden: rull ut ett steg om gangen ned til grunntilfellet.
Summen av de første oddetallene er , og bidrar ikke.
Binærsøk
Søk etter i en sortert tabell. Se på elementet i midten. Er det , er du ferdig. Er mindre, ligger den i venstre halvdel, ellers i høyre, og du søker rekursivt der. Delingen er kjernen, og kombinasjonen finnes ikke: svaret fra det rekursive kallet returneres uendret.
def bisect(A, p, r, v):
if p > r:
return None
q = (p + r) // 2
if v == A[q]:
return q
elif v < A[q]:
return bisect(A, p, q - 1, v)
else:
return bisect(A, q + 1, r, v)
def bisect_iter(A, p, r, v):
while p <= r:
q = (p + r) // 2
if v == A[q]:
return q
elif v < A[q]:
r = q - 1
else:
p = q + 1
return None Korrektheten følger ved induksjon over lengden på segmentet. Et tomt segment gir NIL, og ett element svarer direkte. Er segmentet lengre, er begge halvdelene kortere, så hypotesen gjelder for dem, og testen i midten velger den halvdelen må ligge i. Det rekursive kallet er det siste som skjer i prosedyren, så den kan skrives som en løkke som flytter eller i stedet. Bevis og kjøretid er de samme.
Ett kall på halve instansen pluss konstant arbeid gir . Iterasjon halverer argumentet for hver ekspansjon, så det trengs ekspansjoner for å nå :
Binærsøk bruker tid.
Merge-Sort
Merge-Sort deler på midten og sorterer halvdelene rekursivt. Kjernen er flettingen: det minste elementet forrest i de to sorterte halvdelene flyttes til neste ledige plass, om og om igjen. Halvdelene kopieres først til to hjelpetabeller og , hver med en vaktpost bakerst. Når den ene halvdelen er brukt opp, står forrest i den og taper hver sammenligning, så resten tas fra den andre uten en egen test for tom halvdel. Hver runde i løkka plasserer ett element, så flettingen bruker tid.
inf = float("inf")
def merge_sort(A, p, r):
if p >= r:
return
q = (p + r) // 2
merge_sort(A, p, q)
merge_sort(A, q + 1, r)
merge(A, p, q, r)
def merge(A, p, q, r):
L = A[p : q + 1] + [inf]
R = A[q + 1 : r + 1] + [inf]
i = j = 0
for k in range(p, r + 1):
if L[i] <= R[j]:
A[k] = L[i]
i = i + 1
else:
A[k] = R[j]
j = j + 1 Rekursjonstreet til Merge-Sort, med hele tabellen øverst og ett nivå per rad. Grønt er sorterte halvdeler, oransje er elementene som sammenlignes, rødt er segmentet flettingen skriver til, og er vaktposten.
Læreboka skriver flettingen uten vaktposter, med en egen test for at en halvdel er tom. Flettingen blir like riktig og bruker fortsatt tid.
To kall på halve instansen pluss arbeid til flettingen gir . Ekspansjonene i iterasjonsmetoden kan tegnes som et rekursjonstre, med ett nivå per ekspansjon og kostnaden summert nivå for nivå. Treet har på rota, to noder med under, fire med under der igjen, og hvert nivå summerer til . Treet har nivåer:
Merge-Sort bruker i alle tilfeller. Den deler på midten uansett hvordan inputen ser ut.
Substitusjonsmetoden finner ingenting selv, men verifiserer en løsning med induksjon: anta at løsningen holder for mindre argumenter, og vis at den da holder for .
Vis at løser med .
Vis løsning Skjul løsning
Grunntilfellet: . Anta at for alle . Halvdelen er mindre enn , så antagelsen gjelder for den:
Er påstanden asymptotisk, som , skriver du konstanten ut. For med antar du for alle . Da er
for , og grunntilfellet stemmer. Den samme står i hypotesen og i konklusjonen, så .
Dine venner Lurvik og Smartnes har laget hver sin sorteringsalgoritme, som rett og slett utfører to andre sorteringsalgoritmer etter hverandre:
Lurvik-Sort(A, n)
1 Insertion-Sort(A, n)
2 Merge-Sort(A, 1, n)
Smartnes-Sort(A, n)
1 Merge-Sort(A, 1, n)
2 Insertion-Sort(A, n) Hvilken av dem har best kjøretid i verste tilfelle? Forklar kort.
Vis løsning Skjul løsning
Merge-Sort bruker uansett hvordan inputen ser ut. Kjører den først, er tabellen sortert når Insertion-Sort begynner, og det er beste tilfelle for Insertion-Sort: . Smartnes-Sort bruker dermed i verste tilfelle. Lurvik-Sort kjører Insertion-Sort på usortert input, og der er verste tilfelle , som dominerer fra Merge-Sort etterpå.
Quicksort
Quicksort legger arbeidet i delingen, og kombinasjonen er ingenting. Partition velger det siste elementet som pivot og ordner resten slik at alt som er mindre enn eller lik ligger til venstre og alt større til høyre. Så settes mellom de to delene. Der står den på sin endelige plass og flyttes aldri igjen, og Quicksort sorterer de to delene rekursivt.
Løkka i Partition deler segmentet i soner. Elementene fra til og med er små, fra til og med store, og fra og utover ikke sett ennå. Et lite element bytter plass med det første store, , og grensen rykker ett hakk. Et stort element blir stående. Hver runde ser på ett nytt element, så Partition bruker tid.
def partition(A, p, r):
x = A[r]
i = p - 1
for j in range(p, r):
if A[j] <= x:
i = i + 1
A[i], A[j] = A[j], A[i]
A[i + 1], A[r] = A[r], A[i + 1]
return i + 1
def quicksort(A, p, r):
if p < r:
q = partition(A, p, r)
quicksort(A, p, q - 1)
quicksort(A, q + 1, r) Det skyggelagte området er segmentet som deles, og den stiplede linja er pivoten i det. Gult er sonen med små elementer, oransje sonen med store, og grått er ikke sett ennå. Grønne søyler står på sin endelige plass.
Deler pivoten jevnt, er rekurrensen , den samme som for Merge-Sort, og kjøretiden . Havner pivoten helt ytterst hver gang, slik den gjør når tabellen allerede er sortert, får det ene kallet elementer og det andre ingen. Da er , og iterasjon gir summen , altså .
Sortert input er vanlig input, så det verste tilfellet er ikke bare teori. Randomized-Partition bytter et tilfeldig valgt element inn på plass før delingen. Da er hvert element i segmentet like sannsynlig som pivot, uansett hvordan inputen er ordnet.
Den forventede kjøretiden finner du ved å telle per element. Partition på et segment med elementer koster , altså en konstant for hvert element i segmentet. Den totale kjøretiden er derfor summen, over alle elementene, av hvor mange delinger hvert element er med i. Følg ett element , og kall en pivot god når rangen i segmentet ligger mellom og . Da får begge delene høyst elementer, så etter en god pivot har segmentet til krympet til høyst av størrelsen. Etter gode pivoter har segmentet høyst ett element, så er med i høyst delinger med god pivot. Halvparten av elementene er gode pivoter, så en tilfeldig pivot er god med sannsynlighet minst , og i forventning går det høyst to delinger per god pivot. Element er dermed med i høyst delinger i forventning, og de elementene gir til sammen forventet .
Jevn deling er det beste som kan skje, og den koster allerede , så forventet kjøretid er for enhver input. Regningen bruker bare de tilfeldige valgene og ikke rekkefølgen i inputen. Verste tilfelle er fortsatt , men det krever uflaks med de tilfeldige valgene, og ingen input er mer utsatt enn andre.
Den samme tellingen gir gjennomsnittstilfellet for Quicksort med fast pivot. Er alle rekkefølger av inputen like sannsynlige, er det siste elementet i hvert segment like sannsynlig hvilken som helst rang, og snittet over instansene er også .
Gjennomsnittstilfelle
Snitt over instansene av størrelse , med en sannsynlighetsfordeling over input. Quicksort med fast pivot: når alle rekkefølger er like sannsynlige.
Amortisert
Snitt over operasjonene i én følge, i verste fall og uten sannsynlighet. Tabelldobling: per innsetting.
Forventet
Snitt over algoritmens egne tilfeldige valg, for én fast instans. Randomized-Quicksort: for enhver input.
Masterteoremet
Mange splitt og hersk-algoritmer gjør rekursive kall på instanser av størrelse og bruker på deling og kombinasjon: . Rekursjonstreet har nivåer under rota, nivå har noder av størrelse , og nederst står løvnoder. Antallet løvnoder skrives om med . Summert nivå for nivå:
Læreboka kaller den drivende funksjonen og vannskillefunksjonen. Ta først . Nivå koster til sammen , så summen er geometrisk med forholdet . Er , altså , vokser leddene nedover, og løvnodene dominerer: . Er , koster alle de nivåene , og . Er , krymper leddene, og rota dominerer: .
Rekursjonstreet for med , rota øverst. Hver stolpe er det samlede arbeidet på ett nivå, merket med antall kall og størrelsen på hvert. Stolpene er grønne når , røde når og oransje når .
Masterteoremet gjelder også andre , så lenge avstanden til vannskillefunksjonen er en faktor eller :
Tilfelle 1, 2 og 3 er de tre utfallene av forholdet for : løvnodene dominerer, alle nivåene koster like mye, eller rota dominerer.
I tilfelle 2 kan ha en ekstra faktor . Nivå koster da omtrent , og summen over nivåene får én faktor til.
I tilfelle 1 og 3 må avstanden til vannskillefunksjonen være polynomisk, en faktor . En logaritmisk faktor over vannskillefunksjonen havner i tilfelle 2. En logaritmisk faktor under den havner i ingen av tilfellene: i er mindre enn , men ikke polynomisk mindre, og summen nivå for nivå gir . Teoremet krever også at instansen deles på en konstant faktor , så har ikke formen.
Betingelsen i tilfelle 3 kalles regularitet. Den holder alltid for med , for da er med .
Løs og .
Vis løsning Skjul løsning
Første rekurrens: , og . Vannskillefunksjonen er , og er polynomisk mindre. Tilfelle 1 gir .
Andre rekurrens: , og . Vannskillefunksjonen er , og . Avstanden er bare en logaritmisk faktor, så dette er tilfelle 2 med , og .
| Rekurrens | Løsning | |
|---|---|---|
| tilfelle 2, : | ||
| tilfelle 2, : | ||
| tilfelle 1: | ||
| tilfelle 3: | ||
| ikke på formen | iterasjon: |
Løs følgende rekurrenser. Oppgi svaret i -notasjon.
a)
b)
Vis løsning Skjul løsning
a) Leddet gjør at utrullingen blir en teleskopsum der alt unntatt og grunntilfellet faller bort:
b) Her er og , så vannskillefunksjonen er . Da er , og tilfelle 2 med gir at får én logaritmefaktor til.