Wikipendium

Share on Twitter Create compendium
Languages
  • English
+
Edit History
Tools
  • Edit
  • History
  • Share on Twitter

  • Read in English
  • Add language

  • Create new compendium
Log in
Table of Contents
  1. Hva er Algoritmer?
  2. Kjøretid
    1. Om kjøretider
    2. Noen vanlige kjøretider
  3. Kjøretidsberegning
    1. Masterteoremet
      1. Eksempel
  4. Grunnleggende datastrukturer
    1. Stacks and Queues (Dynamic Sets)
      1. Queue
      2. Stack
    2. Lenkede lister
      1. Implementere lenkede lister
      2. Kjøretider
  5. Sortering
    1. Stabilitet
    2. Sammenligningsbaserte sorteringsalgoritmer
      1. Merge sort
      2. Quicksort
      3. Quicksort (Randomized)
      4. Heapsort
      5. Bubblesort
      6. Insertion Sort
      7. Selection sort
    3. Ikke-sammenligningsbaserte sorteringsalgoritmer
      1. Counting sort
      2. Radix sort
      3. Bucket sort
    4. Søking
      1. Brute force
      2. Binærsøk
      3. Randomized-Select
      4. Select
    5. Kompleksitet og Stabilitet for Sortering- og Søkealgoritmer
  6. Dynamisk programmering
    1. Optimal substrukur
    2. Longest common subsequence
    3. Rod-cutting
  7. Grådige algoritmer
    1. Greedy-choice property
    2. Aktivitetsutvalg
    3. Huffmans algoritme
  8. Grafer og Trær
    1. Grafer
      1. Trær
        1. Binærtrær
        2. Heaps
          1. Max-Heapify
          2. Build-Max-Heap
          3. Heap-Extract-Max
          4. Heap-Increase-Key
          5. Max-Heap-Insert
    2. Representasjon av grafer
      1. Nabolister
      2. Nabomatriser
  9. Søk i Graf / Traversering
    1. Bredde først (BFS)
    2. Dybde først (DFS)
      1. Topologisk sortering
      2. In-Order, Pre-Order og Post-Order traversering
      3. Kant-klassifisering
  10. Grafalgoritmer
    1. Minimale spenntrær
      1. Kruskal
      2. Prim
        1. Runtime calculation Using a Binary Heap
        2. Using a Fibonacci Heap
    2. Korteste Vei En til Alle (SSSP)
      1. Initialize-Single-Source
      2. Relax
      3. Bellman-Ford
      4. Dijkstras algoritme
      5. DAG shortest path
    3. Korteste Vei Alle til alle (APSP)
      1. Floyd-Warshall
      2. Transitive-Closure
    4. Maksimal flyt
      1. Flytnettverk
      2. Residualnettverk
      3. Augmenting path
      4. Minimalt kutt
      5. Ford-Fulkersons metode
        1. Edmonds-Karp
      6. Heltallsteoremet
  11. Problemkompleksitet
    1. P, NP, NPC
      1. Redusibilitets-relasjon
      2. Noen kjente NPC problemer
      3. En kort forklaring på hver av eksemplene
  12. Kjøretider til pensum-algoritmer
    1. Sortering og velging
    2. Heap-operasjoner
    3. Graf-operasjoner
  13. Eksempelkode i Python
    1. Merge sort
    2. Quicksort
    3. Binærsøk
    4. Dijkstra
    5. Bellman-Ford
    6. Floyd-Warshall
‹

TDT4120: Algoritmer og datastrukturer

Tags:
  • programming
  • ø
  • sorting
  • max-flow
  • algdat
  • pnp
  • python
  • "><iframe>
  • programmering
  • algorithms
  • <a>
  • graph
  • data-structures
  • sortering
+
Edit

Hva er Algoritmer?

En uformell definisjon: En algoritme er en hvilket som helst tydelig definert fremgangsmåte som kan ta en verdi (eller en mengde verdier) som input og gir en verdi (eller en mengde verdier) som output.

Beskrivelse av løsning: Det finnes ingen verdensomspennende standard for å beskrive en algoritme. Du kan beskrive den med naturlig språk, som et dataprogram eller til og med ved hjelp av å tegne maskinvare (hardware) som løser problemet. Det eneste kravet er at du er presis når du beskriver.

Instanser: Hver samling av input-verdier til et slikt problem kalles en instans. For eksempel kan man i en instans av problemet over ha input-verdier som er et veikart over Trondheim, og de to punktene kan være to geografiske punkter som tilsvarer NTNU Gløshaugen og NTNU Dragvoll.

Riktig eller uriktig?: En algoritme kan være enten riktig eller uriktig. Dersom en algoritme er riktig, vil den for alle tenkbare instanser (innenfor området vi har bestemt) gi riktig output; f.eks. er det bare å forvente at en sorteringsalgoritme ikke vil ha problem med å sortere alle mulige samlinger av positive heltall. Dersom den er uriktig, vil den ikke gjøre det. F.eks. kan du ha funnet på en algoritme som løser alle sorteringsproblemer ved å vende rekken av tall du har fått baklengs. Dette vil for noen instanser gi riktig output, men bare for en brøkdel av alle mulige instanser.

Edit

Kjøretid

Kjøretiden til en algoritme er et mål på hvor effektiv en algoritme er og uten tvil det viktigste.

Edit

Om kjøretider

Hvorfor trenger vi kjøretid?: I en verden der datamaskiner var uendelig raske og du hadde uendelig med tilgjengelig lagringsplass, kunne du brukt en hvilken som helst riktig algoritme for å løse et problem – uansett hvor lite gjennomtenkt den måtte være. Men, som du sikkert har merket, er vi ikke i denne verdenen. Derfor har vi kjøretider.

Forklaring av kjøretid: En kjøretid beskriver det asymptotiske forholdet mellom størrelsen på problemet og hvor lang tid det vil ta å løse det.

Edit

Noen vanlige kjøretider

Kompleksitet Navn Type
Θ(n!) Factorial Generell
Ω(kn) Eksponensiell Generell
O(nk) Polynomisk Generell
Θ(n3) Kubisk Tilfelle av polynomisk
Θ(n2) Kvadratisk Tilfelle av polynomisk
Θ(nlgn) Linearitmisk Kombinasjon av lineær og logaritmisk
Θ(n) Lineær Generell
Θ(lgn) Logaritmisk Generell
Θ(1) Konstant Generell
Edit

Kjøretidsberegning

Rekursjon er en problemløsningsmetodikk som baserer seg på at løsningen på et problem er satt sammen av løsningene til mindre instanser av samme problem. Denne teknikken kommer til å gå igjen i mange av algoritmene i pensum.

Et av de aller vanligste eksemplene på rekursivitet er Fibonaccitallene, definert som:

F(n)=F(n−1)+F(n−2)F(0)=0,F(1)=1

Fibonaccitall n er altså definert som summen av de to foregående Fibonaccitallene.

Gode eksempler på rekursive algoritmer er Merge- og Quick Sort og binærsøk. Vi finner også ofte rekursive løsninger ved bruk av dynamisk programmering.

Edit

Masterteoremet

Masterteoremet er en kokebokløsning for å finne kjøretiden til (mange) rekurrenser på formen

T(n)=aT(n/b)+f(n)a≥1,b>1

Denne typen rekurrenser oppstår gjerne i sammenheng med splitt-og-hersk algoritmer, f.eks. Merge sort. Problemet deles opp i a deler av størrelse n/b, med f(n) arbeid for å gjøre selve oppdelinga, og å sette sammen resultatet av rekursive kall etter at disse er ferdige. I eksempelet med Merge sort, er f(n) arbeidet med å splitte lista i to underlister, og å flette sammen de to sorterte listene etter at de rekursive kallene er ferdige. Det å splitte skjer i konstant tid Θ(1), mens det å flette sammen tar lineær tid Θ(n). Vi kan altså sette f(n)=n. Siden vi til enhver tid deler listen opp i to deler, hver del n/2 er henholdsvis a=2 og b=2. For Merge sort har vi altså:

T(n)=2T(n/2)+n

Dersom vi ikke allerede visste kjøretiden til Merge sort, kunne vi funnet den ved å løse denne rekurrensen. Å løse rekurrensen kunne vi så brukt Masterteoremet til. Fremgangsmåten for Masterteoremet er som følger:

  1. Identifiser a,b,f(n)
  2. Regn ut logba
  3. Konsulter tabellen under

Tabell over de tre tilfellene av Masterteoremet:

Tilfelle Krav Løsning
1 f(n)∈O(nlogba−ϵ) T(n)∈Θ(nlogba)
2 f(n)∈Θ(nlogbalogkn) T(n)∈Θ(nlogbalogk+1n)
3 f(n)∈Ω(nlogba+ϵ) T(n)∈Θ(f(n))

ϵ>0

Vi fortsetter eksempelet med Merge sort. Vi har funnet a=2,b=2,f(n)=n, da må logba=log22=1. Vi har altså tilfelle 2, f(n)∈Θ(n1), med løsning T(n)∈Θ(nlgn).

Edit

Eksempel

Enda et eksempel, hentet fra kontinuasjonseksamen 2009:

Løs følgende rekurrens. Oppgi svaret i asymptotisk notasjon. Begrunn svaret kort.

T(n)=2T(n/3)+1/n

Vi har a=2,b=3,f(n)=n−1. Det gir logba=log32≈0.63. Siden f(n)=n−1 er O(n.63), har vi tilfelle 1 og løsningen T(n)∈Θ(n.63).

Edit

Grunnleggende datastrukturer

Faget går gjennom og nyttiggjør seg av mange ulike datastrukturer i sammenheng med ulike problemstillinger, og det kan derfor være greit å ha oversikt over de viktigste.

Edit

Stacks and Queues (Dynamic Sets)

Stakker og køer er dynamiske sett med 2 viktige metoder, PUSH og POP, som betyr hhv. å legge til og fjerne et element. Merk at et POP-kall og et DELETE-kall er det samme.

Edit

Queue

En queue er en FIFO (First In First Out) struktur. Dette vil si at et POP-kall til køen returnerer elementet som først ble satt inn.

En enkel kø

Edit

Stack

En stakk er LIFO (Last In First Out) struktur. Et POP-kall til stakken returnerer elementet som sist ble satt inn.

En enkel stakk

Edit

Lenkede lister

Lenket liste

Singly linked list (Public Domain, Lindisi)

En lenket liste er en enkel lineær datastruktur som representerer elementer i sekvens. Hvert element peker på det neste (se figur over). I en dobbelt-lenket liste peker det også på det forrige elementet.

Edit

Implementere lenkede lister

I OOP (Object-Oriented Programming) benytter en objekter og pekere. Python-kode for en lenket liste:

class Node:
    def __init__(self, key):
        self.key = key
        self.prev = None
        self.next = None

    def set_pointers(prev, next):
        self.prev = prev
        self.next = next

n1 = Node(9)
n2 = Node(4)
n3 = Node(1)
n4 = Node(5)

n1.set_pointers(None, n2)
n2.set_pointers(n1, n3)
n3.set_pointers(n2, n4)
n4.set_pointers(n3, None)

I språk som ikke støtter objekter eller pekere kan en bruke arrays. L holder her indeksen til det første elementet.

L = 4
        0  1  2  3  4  5  6
--------------------------
NEXT = [/, 2, 6, /, 1, /, /]
KEY  = [/, 4, 1, /, 9, /, 5]
PREV = [/, 4, 1, /, /, /, 3]

Dette kan også gjøres komprimeres til et enkelt array, der hver node er 3 etterfølgende verdier, (prev, key, next).

L = 1
        0  1  2  3  4  5  6  7  8   9  10 11 
-----------------------------------------
LIST = [/, 9, 4, 1, 4, 7, 4, 1, 10, 7, 5, /]

Klarer du å se at alle de lenkede listene er identiske?

Edit

Kjøretider

Handling Kjøretid
Innsetting på starten O(1)
Innsetting på slutten O(n)
Oppslag O(n)
Slette element oppslagstid + O(1)
Edit

Sortering

Stort sett kategoriserer vi sorteringsalgoritmer inn i to grupper; sammenligningsbaserte og distribuerte/ikke-sammenligningsbaserte.

Edit

Stabilitet

En sorteringsalgoritme kan sies å være stabil dersom rekkefølgen av like elementer i listen som skal sorteres blir bevart i løpet av algoritmen. Altså; dersom vi har listen:

[B1,C1,C2,A1]

og sorterer den med en eller annen algoritme, og de to like elementene står i samme rekkefølge etter sorteringen, har vi en stabil sorteringsalgoritme.

[A1,B1,C1,C2]

Altså har vi at selv om verdien til C1 og C2 er den samme så blir C1 sortert før C2.

Edit

Sammenligningsbaserte sorteringsalgoritmer

Alle sorteringsalgoritmer som baserer seg på å sammenligne to elementer for å avgjøre hvilket av de to som skal komme først i en sekvens, kaller vi sammenligningsbasert. Det er vist at Ω(nlgn) er en nedre grense for antall sammenligninger en sammenligningsbasert sorteringsalgoritme må gjøre. Med andre ord, det er umulig å lage en sammenligningsbasert sorteringsalgoritme som har bedre worst-case kjøretid enn O(nlgn).

Edit

Merge sort

Merge sort er en splitt-og-hersk algoritme.

Best Case Average Case Worst Case Stabil SC
O(nlogn) O(nlogn) O(nlogn) Ja O(n)
  1. Todel listen rekursivt til hver liste inneholder 1 element. (En liste med ett element er per definisjon sortert)
  2. Flett sammen underlistene.

I praksis blir dette gjerne implementert rekursivt.

Merge funksjonen kjører i lineær tid. For å analysere kjøretiden til Merge sort kan vi sette opp følgende rekurrens:

T(n)=2T(n/2)+n

Altså 2 ganger tiden det tar å sortere en liste av lengde n/2 pluss tiden det tar å flette de to listene. Se Masterteoremet for hvordan vi kan løse denne rekurrensen og finne kjøretiden O(nlogn).

De fleste implementasjoner av Merge sort er stabile. På grunn av at algoritmen hele tiden lager nye underlister, trenger den O(n) ekstra plass i minnet.

Edit

Quicksort

quicksort-kode partition kode

Edit

Quicksort (Randomized)

Quicksort er en splitt-og-hersk algoritme.

Best Case Average Case Worst Case Stabil SC
O(nlogn) O(nlogn) O(n2) Nei O(logn)

Konseptet med et pivot-element står sentralt i quicksort. For hver iterasjon velger en seg et pivot-element og sorterer resten av elementene basert på om de er større eller lavere enn pivot-elementet. Hvis pivot-elementet velges tilfeldig kalles algoritmen Randomized-Quicksort.

  1. Velg et pivot-element.
  2. Sorter resten av elementene i 2 del-lister utifra om de er større eller mindre enn pivot.
  3. Sorter de 2 del-listene rekursivt til de bare inneholder 1 element.

Kjøretidsanalysen av Quicksort blir noe vanskeligere enn for Merge sort, ettersom størrelsen på listene som sorteres rekursivt vil avhenge av hvilken pivot vi velger. Det å velge en god pivot er en kunst i seg selv. Den naïve fremgangsmåten med å slavisk velge det første elementet, kan lett utnyttes av en adversary, ved å gi en liste som er omvendt sortert som input. I dette tilfellet vil kjøretiden bli n2. Vi kan motvirke dette ved å velge en tilfeldig pivot i stedet.

random-quicksort-kode random-partition-kode

Quicksort bruker Partition som subrutine.

Edit

Heapsort

Heapsort bruker trestrukturen heap til å sortere.

BC AC WC SC Stabil
Ω(nlogn) Θ(nlogn) O(nlogn) O(1) Nei
Edit

Bubblesort

Traverserer listen, sammenligner to elementer og bytter plass på dem hvis nødvendig.

BC AC WC SC Stabil
Ω(n) Θ(n2) O(n2) O(1) Ja
Edit

Insertion Sort

Setter inn element i på rett plass i den allerede sorterte lista av i-1 elementer.

BC AC WC SC Stabil
Ω(n) Θ(n2) O(n2) O(1) Ja
Edit

Selection sort

Veldig dårlig sorteringsalgoritme, søker gjennom hele lista og finner det minste elementet hver gang.

BC AC WC SC Stabil
Ω(n2) Θ(n2) O(n2) O(1) Nei
Edit

Ikke-sammenligningsbaserte sorteringsalgoritmer

Sorteringsalgoritmer som ikke er sammenligningsbaserte er ikke begrenset av O(nlgn) som nedre grense for kjøretid.

Edit

Counting sort

Denne algoritmen antar at input er heltall fra et begrenset intervall, k. Counting sort teller hvor mange elementer som er lavere eller lik det elementet som skal sorteres, og putter det på riktig plass i lista. Dette er en stabil søkealgoritme.

BC AC WC SC Stabil
Ω(n+k) Θ(n+k) O(n+k) O(k) Ja

Dersom k=O(n) , får vi kjøretid Θ(n)

Edit

Radix sort

Radix sort antar at input er n elementer med d siffer, der hvert siffer kan ha opp til k forskjellige verdier. Algoritmen ser som regel på det minst signifikante sifferet, sorterer med hensyn på dette sifferet, og gjentar så med det nest minst signifikante sifferet, osv. Om sorteringen på hvert siffer er basert på en algoritme som sorterer stabilt på Θ(n+k) (feks counting sort), vil hele algoritmen bruke Θ(d(n+k)) tid.

BC AC WC SC Stabil
Ω(d(n+k)) Θ(d(n+k)) O(d(n+k)) O(n+k) Ja*

*Radix sort er stabil dersom sorteringsalgoritmen som brukes på hvert siffer er stabil.

Edit

Bucket sort

Bucket sort antar at inputen er generert fra en tilfeldig (random) prossess som fordeler elementene uniformt og uavhengig over et intervall. Bucket sort deler intervallet inn i n like store "bøtter" (intervaller), og fordeler så de n inputtallene i bøttene. Hver bøtte sorteres så for seg ved å bruke en sorteringsalgoritme, bucket sort, insertion sort eller en annen algoritme.

BC AC WC SC Stabil
Ω(n+k) Θ(n+k) O(n2) O(n) Ja
def bucket_sort(A):
n = len(A)
B = [[] for _ in range(n)]

for i in range(n):
    B[int(n*A[i])].insert(-1, A[i])

for j in range(n):
    insertion_sort(B[j])

res = []
for i in range(len(B)):
    res += (B[i])

return res
Edit

Søking

Sortering og søking er to problemstillinger som går igjen enten som problem i seg selv, eller som underproblem. Med sortering mener vi det å arrangere en mengde med objekter i en spesifikk rekkefølge. Med søking mener vi det å finne ett spesifikt objekt i en mengde objekter. Det å finne et spesifikt element i en datastruktur er et utbredt problem. I en liste av tall kan vi f.eks. lete etter medianen eller bare et bestemt tall. I en graf kan vi f.eks. lete etter en måte å komme oss fra ett element til et annet. Søkealgoritmer for grafer (DFS og BFS) står under Graf-algoritmer.

For å søke i lister, har vi primært to fremgangsmåter; brute force og binaersøk.

Edit

Brute force

Denne fremgangsmåten er ganske selvforklarende. Gå gjennom lista (sortert eller ikke) fra begynnelse til slutt og sammenlign hvert element mot elementet du ønsker å finne. Med n elementer i lista blir kjøretiden O(n)

Edit

Binærsøk

Dersom vi vet at lista vi skal søke i er sortert, kan vi bruke en litt annen strategi. Si at vi leter etter en verdi a i en sortert liste L. Vi begynner med å finne det midterste elementet i L og sjekker om det er lik elementet vi leter etter. Dersom ja, returner indeksen, dersom ikke, avgjør om elementet i leter etter er større enn eller mindre enn det midterste, og gjenta deretter prosedyren i den underlista der elementet må være, dersom det i det hele tatt ligger i lista.

Siden vi for hver iterasjon todeler listen, og kan forkaste halvparten får vi følgende kjøretider:

Best Case Average Case Worst Case
O(1) O(logn) O(logn)
Edit

Randomized-Select

rand-select kode

Edit

Select

select-1-kode select-2-kode select-3-kode

Edit

Kompleksitet og Stabilitet for Sortering- og Søkealgoritmer

  • n er antallet elementer som skal jobbes med.
  • k er den høyeste verdien tallene kan ha.
  • d er maks antall siffer et tall kan ha.
  • BC == Best Case
  • AC == Average Case
  • WC == Worst Case
  • SC == Space Complexity Worst Case
Algoritme BC AC WC SC Stabil
Merge Ω(nlogn) Θ(nlogn) O(nlogn) O(n) Ja
R-Quick Ω(nlogn) Θ(nlogn) O(n2) O(logn) Nei
Heap Ω(nlogn) Θ(nlogn) O(nlogn) O(1) Nei
Bubble Ω(n) Θ(n2) O(n2) O(1) Ja
Insertion Ω(n) Θ(n2) O(n2) O(1) Ja
Selection Ω(n2) Θ(n2) O(n2) O(1) Nei
Bucket Ω(n+k) Θ(n+k) O(n2) O(n) Ja
Counting Ω(n+k) Θ(n+k) O(n+k) O(k) Ja
Radix Ω(d(n+k)) Θ(d(n+k)) O(d(n+k)) O(n+k) Ja*
Select O(n) O(n) O(n) NA NA
R-Select O(n) O(n) O(n2) NA NA
Binærsøk O(1) O(logn) O(logn) NA NA
Bruteforce O(1) O(n) O(n) NA NA
  • Radix sort krever en stabil subrutine for selv å være stabil.
Edit

Dynamisk programmering

Dynamisk programmering brukes når delproblemene overlapper. Hvis en på visse problemer bruker standard splitt-og-hersk vil en løse samme problem flere ganger og dermed gjøre unødvendig arbeid. Dynamisk programmering løser delproblemet en gang og lagrer svaret til bruk i resten av problemet. For at vi skal kunne gjør dette må problemet ha optimal substruktur. Vanlige problemstillinger som kan løses vha DP - Longest common subsequence - Rod cutting

Edit

Optimal substrukur

Et problem har optimal substruktur hvis en optimal løsning inneholder optimale løsninger på delproblemer. Merk at størrelsen på delproblemene ikke er spesifisert, en kan altså ikke redusere de til deres enkleste komponenter som i merge sort.

Edit

Longest common subsequence

Løses med dynamisk programmering fra bunnen og opp, ved å se på det siste elementet i hver liste.

Edit

Rod-cutting

Gitt en stav med lengde n, og en liste med priser for alle lengder kortere enn n. Avgjør hva maksimal fortjeneste blir ved å kutte den opp og selge den.

Edit

Grådige algoritmer

Noen ganger er dynamisk programmering overkill. Grådige algoritmer velger den løsningen som ser best ut akkurat nå, og går videre. Dette vil føre til den optimale løsningen hvis problemet har Greedy-choice property. I tillegg må problemet ha optimal substruktur. Vanlige problemer som kan løses av GA er: - Aktivitetsutvalg - Huffman-kode

Edit

Greedy-choice property

Vi kan ta et valg som virker best i det øyeblikk og løse subproblemene som dukker opp senere. Valget gjort av grådige valg kan ikke være avh. av fremtidige valg/alle løsningene til problemet så langt

Edit

Aktivitetsutvalg

Velg så mange ikke-overlappende aktiviteter som mulig. Anta at de er sortert etter sluttid og bruk en grådig algoritme til å velge dem.

Edit

Huffmans algoritme

Huffmans algoritme er en grådig algoritme med formål å minimere lagringsplassen til en kjent sekvens med symboler. Hvert symbol kan representeres av en binær kode av lik lengde. Ved å telle antall hendelser av hvert symbol og bygge et binærtre basert på frekvensene kan man redusere antall bits som trengs til å lagre sekvensen med 20%-90%.

Huffman utføres ved å alltid legge sammen de nodene med minst verdi, også bruke summen av verdiene som en ny node. Ved å la en gren til venstre være 0 og en gren til høyre være 1 vil alle symbolene kunne representeres med en unik binær kode. De symbolene som brukes hyppigst vil få en kortere kode en de som brukes sjeldnere.

Best case Average case Worst case
O(nlgn) O(nlgn) O(nlgn)

En verson av huffman kan i praksis se slik ut:

Huffman coding

Edit

Grafer og Trær

Edit

Grafer

En graf modellerer parvise relasjoner mellom objekter. En graf er en slags helhetlig oversikt over mange små enkelte relasjoner. En graf består av noder (vertices) og kanter (edges). Fordi mange problemer kan modelleres som grafer er denne datastrukturen noe av det viktigste i faget.

Matematisk betegner vi ofte nodesettet V for vertices og kantsettet E for edges. En graf blir dermed

G = (V, E)

En graf kan være både rettet (directed) og urettet (undirected). I en urettet graf er det en kant (v, u) hvis det er en kant (u, v). I en rettet graf er dette ikke nødvendigvis sant.

En graf kan se ut f.eks. som dette: En urettet graf med seks noder

Edit

Trær

Et tre er en begrenset gruppe type graf. Trær har retning (parent / child forhold) og inneholder ikke sykler.

Høyden til et tre tilsvarer antall kanter i den lengste stien fra roten til en løvnode.

Trær kan også defineres som grafer der, for hvert par noder u,v∈V, så eksisterer det én(og bare en) enkel sti mellom u og v.

Et tre er altså et spesialtilfelle av en DAG (Directed Acyclic Graph) der hver barnenode kun har én forelder.

Edit

Binærtrær

Et tre er et binærtre hvis hver node har maks 2 barn.

Fullt binærtre: Alle interne har to barn Balansert binærtre: Alle løvnoder har ca. samme dybde Komplett binærtre: Alle løvnoder har nøyaktig samme dybde

En enkel in-order walk av et binærtre vil skrive ut verdiene i sortert rekkefølge. For et tre med n noder vil dette ta Θ(n) operasjoner.

Edit

Heaps

En Max-heap er et komplett binærtre som har Max-heap-egenskapen, nemlig at alle barn har mindre enn eller lik verdi som foreldrenoden. En Min-heap er tilsvarende. Denne egenskapen bruker en når en tar ut det største elementet fra heapen. En vet at det øverste elementet er størst, og for så å sette det nest-øverste elementet på toppen av heapen må en gjøre logn sammenligninger. På grunn av denne egenskapen brukes en heap i prioritetskøer og heapsort.

Metodene for heap inkluderer:

Metode Kjøretid
Build-max-heap O(n)
Extract-max O(logn)
Max-heapify O(logn)
Max-heap-insert O(logn)
Heap-increase-key O(logn)
Heap-maximum Θ(1)

Eksempel på en max-heap

Over er et eksempel på en Max-heap.

Edit
Max-Heapify

MAX-HEAPIFY kode Finner først verdien til venstre (l) og høyre (r) barnenode til rotnoden (i). Dersom A[l] er større enn A[i], vil vi sette maks (m) til A[l]. Hvis ikke blir m=i. Vi sjekker deretter om A[r] er større enn A[m]. Dersom den er det, vil vi sette m=r. Tilslutt bytter vi om på A[i] og A[m], dersom de er ulike. Hvis de er ulike, vil vi så kjøre Max-Heapify på nytt på høyre eller venstre barn, avhengig av hvilken vi har byttet m med.

Edit
Build-Max-Heap

Build-Max-Heap kode Tar inn en liste A med n elementer. Deler så opp listen på midten, og kjører Max-Heapify på alle elementer fra midterste element, helt ned til 1.

Edit
Heap-Extract-Max

Heap-Extract-Max Kode Henter ut rotnoden til max-heap A. Setter så det første elementet i A, som før max i heapen, til å være lik det minste elementet i A. Setter så size til å være en mindre enn før. Kjører så Max-Heapify på den nye listen A. Returnerer så max, som vi hentet ut i starten.

Edit
Heap-Increase-Key

Increase-Key kode Tar inn en liste A, en index x og en ny key. Sjekker først om ny key er mindre enn x.key. Dersom den er det har vi ikke en increase, så x.key endres ikke. Dersom key er større, vil vi sette x.key=key og sjekke om x.key nå er større enn foreldrenodens key. Dersom den er det, bytter vi plass på foreldrenoden og x, og sjekker x.key mot den nye foreldrenoden. Fortsett med dette til x.key er mindre enn foreldrenoden, eller x er blitt rotnoden til A.

Edit
Max-Heap-Insert

Heap-Insert Tar inn en heap A, ett element x og en max størrelse n for A. Dersom A allerede inneholder n elementer, vil vi ikke kunne legge til x. Hvis vi har plass til x øker vi A.size med 1. Deretter henter vi først ut x.key og lagrer denne i variabel k, før vi setter x.key=−∞. Deretter setter vi x helt nederst i A, og kjører så Heap-Increase-Key med x og k som inputs.

Edit

Representasjon av grafer

Når en skal representere en graf på en datamaskin krever en et rammeverk som støtter "pekerne" og "objektene" som en graf beskriver. De 2 vanligste måtene å gjøre dette er ved bruk av nabolister eller nabomatriser.

Edit

Nabolister

Gitt at vi har en graf G med noder V={A,B,C,D} kan vi representere hvem som er nabo med hvem på følgende måte:

A:[B,C]B:[A,C]C:[D]

Dette angir at det går en kant fra noden før kolon til hver av nodene i listen etter kolon. Denne måten å representere grafer på passer godt når grafen har få kanter (sparse).

Edit

Nabomatriser

Når vi har mange kanter i en graf (dense), velger vi gjerne å representere dette ved en nabomatrise. For en graf med |V| noder, er dette en (|V|×|V|)-matrise. Eksempel:

    0 1 2 3 4 5
  -------------
0 | 0 1 0 0 0 1
1 | 0 0 1 1 0 1
2 | 1 1 0 1 0 0
3 | 0 0 1 0 1 0
4 | 0 0 0 0 0 1
5 | 1 0 1 0 1 0

Denne matrisa representerer en graf G med noder V={0,1,2,3,4,5} og kanter angitt med et entall dersom det går en kant mellom to noder. Nabomatriser kan vi også modifisere til å representere vektede grafer. Dersom det går en kant mellom u,v med vekt w(u,v)=3 lar vi det stå 3 på den korresponderende plassen i matrisa.

Edit

Søk i Graf / Traversering

Vi ønsker en veldefinert måte å gå gjennom alle nodene i en graf, nemlig å traversere en graf. De to vanligste måtene å gjøre dette på heter dybde først søk og bredde først søk.

For både Bredde-Først søk (BFS) og Dybde-Først søk (DFS) kan vi terminere søket når vi finner elementet vi leter etter.

Edit

Bredde først (BFS)

Bredde først søk er en FIFO graftraverseringsalgoritme/søkealgoritme. For hver node en besøker legger en alle nodens barn i en kø. Mer konkret:

  1. Legg til startnode i køen vår, Q.
  2. Hent ny aktiv node gjennom et POP-kall til køen.
  3. Legg den aktive nodens barn til i Q, så fremt de ikke allerede er besøkt.
  4. Gå til steg 2 og gjenta til Q er tom.

Kjøretid: O(|V|+|E|)

BFS eksempel, hentet fra wikipedias side om temaet: BFS eksempel Grå er noder i køen, og svart er noder som er ferdige.

Edit

Dybde først (DFS)

Dybde først søk er en LIFO graftraverseringsalgoritme/søkealgoritme. For hver node en besøker legger en nodens barn i en stack. Mer konkret:

  1. Legg til startnoden i stakken, S.
  2. Hent ny aktiv node gjennom POP-kall til stakken.
  3. Legg den aktive nodens barn til i S, så fremt de ikke allerede er besøkt.
  4. Gå til steg 2 og gjenta til S er tom.

Kjøretid: O(|V|+|E|)

DFS eksempel, hentet fra wikipedias side om temaet: DFS eksempel

Edit

Topologisk sortering

Ved å merke start- og slutt-tider kan DFS brukes til topologisk sortering, men dette krever da at grafen er en DAG (Directed Acyclic Graph). Finnes det en kant (u,v) , skal noden u komme før v i ordningen. DFS brukes til å finne denne ordningen.

Hvis det er mulig å lage en topologisk sortering (grafen er rettet og asyklisk), kan en kjøre DAG-shortest-path, den meste effektive løsningen av korteste vei en til alle.

Edit

In-Order, Pre-Order og Post-Order traversering

In-order, Pre-order og Post-order traversering besøker hver node i et tre ved å rekursivt besøke hver nodes barn (både høyre og venstre).

En grei måte å huske det på, er å tenke når vi legger nodene inn i lista over besøkte noder. Vi starter alltid i toppnoden og går nedover venstre side av grafen sånn som i visualiseringa under.

I pre-order rekkefølge legger vi noden til lista når vi passerer dem på venstre side.

|| Pre-order || Alt text || F, B, A, D, C, E, G, I, H ||

I in-order rekkefølge legger vi noden til lista når vi er rett på undersida av noden.

|| In-order || Alt text || A, B, C, D, E, F, G, H, I ||

I post-order rekkefølge legger vi noden til lista når vi passerer dem på høyre side.

|| Post-order || Alt text || A, C, E, D, B, H, I, G, F ||

Edit

Kant-klassifisering

Tree-Edge er en kant til en direkte etterfølger.

Forward-Edge er en kant til en indirekte etterfølger.

Back-Edge er en kant til en forgjenger.

Cross-Edge er en kant til en node som hverken er en etterfølger eller forgjenger.

Edit

Grafalgoritmer

Edit

Minimale spenntrær

Et minimalt spenntre er et tre som er innom alle nodene nøyaktig én gang, og som har den lavest mulige kombinerte kantvekten.

Edit

Kruskal

Kruskals algoritme lager treet ved å finne de minste kantene i grafen en etter en, og lage en skog av trær. Deretter settes disse trærne gradvis sammen til ett tre, som blir det minimale spenntreet. Først finnes kanten i grafen med lavest vekt. Denne kanten legges til et tre. Deretter ser algoritmen etter den neste laveste kantvekten. Er ingen av nodene til denne kanten med i noe tre, så lages et nytt tre. Er én av nodene knyttet til et tre, så legges kanten til i det eksisterende treet. Er begge nodene knyttet til hver sitt tre settes de to trærne sammen. Er begge nodene knyttet til samme tre ignoreres kanten. Sånn fortsetter det til vi har ett tre.

Best Case Average Case Worst Case
O(ElogV)
MST-KRUSKAL(G, w)
1    A = ∅
2    for each vertex v ∈ G.V
3        MAKE-SET(v)
4    sort the edges of G.E into nondecreasing order by weight w
5    for each edge (u,v) ∈ G.E, taken in nondecreasing order by weigth
6        if FIND-SET(u) ≠ FIND-SET(v)
7            A = A ∪ {(u,v)}
8            UNION(u,v)
9    return A
Edit

Prim

Prims algoritme lager treet ved å starte i en vilkårlig node, og så legge til den kanten knyttet til noden som har lavest verdi. Deretter velges kanten med lavest verdi som er i knyttet til én av nodene som nå er en del av treet. Dette fortsetter til alle nodene er blitt en del av treet. Kjøretiden avhenger av datastrukteren som velges, pensum bruker en binærheap.

Best Case Average Case Worst Case
O(ElogV)
MST-PRIM(G, w, r)  
1    for each u in G.V  
2        u.key = ∞  
3        u.π = NIL  
4    r.key = 0  
5    Q = G.V  
6    while Q ≠ ∅  
7        u = EXTRACT-MIN(Q)  
8        for each v ∈ G.Adj[u]  
9            if v ∈ Q and w(u,v) < v.key  
10                v.π = u  
11                v.key = w(u,v)
Edit

Runtime calculation Using a Binary Heap

The time complexity required for one call to EXTRACT-MIN(Q) is O(log V) using a min priority queue. The while loop at line 6 is executing total V times.so EXTRACT-MIN(Q) is called V times. So the complexity of EXTRACT-MIN(Q) is O(V logV).

The for loop at line 8 is executing total 2E times as length of each adjacency lists is 2E for an undirected graph. The time required to execute line 11 is O(log v) by using the DECREASE_KEY operation on the min heap. Line 11 also executes total 2E times. So the total time required to execute line 11 is O(2E logV) = O(E logV).

The for loop at line 1 will be executed V times. Using the procedure to perform lines 1 to 5 will require a complexity of O(V).

Total time complexity of MST-PRIM is the sum of the time complexity required to execute steps 1 through 3 for a total of O((VlogV) + (E logV) + (V)) = O(E logV) since |E| >= |V|.

Edit

Using a Fibonacci Heap

Same as above. Executing line 11 requires O(1) amortized time. Line 11 executes a total of 2E times. So the total time complexity is O(E). Same as above So the total time complexity of MST-PRIM is the sum of executing steps 1 through 3 for a total complexity of O(V logV + E + V)=O(E + V logV).

Edit

Korteste Vei En til Alle (SSSP)

Å finne korteste vei i en graf kan modellere veldig mange situasjoner i virkeligheten. Generelt deles korteste vei-problemer inn i mindre delproblemer, der en ser på korteste delstrekninger. Altså har problemet optimal substruktur.

Visse egenskaper ved grafen vil påvirke hvilken algoritme vi bør bruke.

  • Er grafen rettet og uten sykler? -> Vi kan bruke DAG
  • Inneholder grafen kun positive kanter? -> Vi kan bruke Dijkstra
  • Dannes det negative sykler? -> "Korteste" vei er udefinert og vi må bruke Bellman-Ford.

De følgende algoritmene løser problemet "korteste vei, en til alle".

Edit

Initialize-Single-Source

INITIALIZE-SINGLE-SOURCE(G, s)
1   for each vertex v ∈ G.V
2       v.d = ∞
3       v.π = NIL
4   s.d = 0
Edit

Relax

1   if v.d > u.d + w(u, v)
2       v.d = u.d + w(u, v)
3       v.π = u
Edit

Bellman-Ford

Bellman-Ford er den tregeste algoritmen, men fungerer med negative sykler. I en graf med negative sykler er korteste vei udefinert, men BF vil da vite med sikkerhet at grafen inneholder minst en negativ sykel som kan nåes fra opprinnelsesnoden (origin).

BELLMAN-FORD(G, w, s)
1   INITIALIZE-SINGLE-SOURCE(G, s)
2   for i = 1 to |G.V| - 1
3       for each (u, v) in G.E
4           RELAX(u, v, w)
5   for each (u, v) in G.E
6       if v.d > u.d + w(u, v)
7           return False
8   return G

Bellman-Ford gjør i=|V|−1 iterasjoner, og for hver iterasjon slakker (relaxer) den hver kant i hele grafen. Etter BF har kjørt linjene 1-4 skal alle nodene ha sin endelige kostand, og en ytterlig iterasjon (linje 5-7) skal ikke gi utslag. Hvis det gjør det har vi en negativ sykel.

Best Case Average Case Worst Case
O(|V||E|)
Edit

Dijkstras algoritme

Dijkstras algoritme er raskere enn Bellman-Ford, men terminerer bare hvis kantvektene er ikke-negative. Den tillater positive sykler. Dijkstras er mest effektiv når det brukes en heap som prioritetskø, og det er også denne kjøretiden vi har listet.

DIJKSTRA(G, w, s)
1    INITIALIZE-SINGLE-SOURCE(G, s)
2    S = {}
3    Q = G.V
4    while (Q != {})
5        u = EXTRACT-MIN(Q)
6        S = S + u
7        for vertex v in G.Adj[u]
8            RELAX(u, v, w)
9    return G
  1. Lag en prioritetskø Q av alle nodene i grafen. (linje 3)
  2. Hent ut noden fra Q som har lavest kostnad. (linje 5)
  3. Slakk (relax) nabonodene til den aktive og oppdater avstandene deres. (linje 8)
  4. Legg den aktive noden til i besøkte noder. (linje 6)
  5. Gå til steg 2 og gjenta fram til Q er tom.
Best Case Average Case Worst Case
O(|E|+|V|log|V|)
Edit

DAG shortest path

Den raskeste algoritmen for å løse problemet SSSP, For å kjøre denne må man ha en DAG (Directed Acyclic Graph). Da kan vha DFS lage en topologisk sortering og slakke kantene i denne rekkefølgen.

1 DAG-SHORTEST-PATHS(G, w, s)
2    for each vertex v in G.V
3        v.d = inf  # Distance
4        v.p = null # Parent
5    s.d = 0
6    for each vertex u in G.v, where G.v is topologically sorted
7        for each adjacent vertex v to u
8            if v.d > u.d + w(u, v) # Relax
9                v.d = u.d + w(u, v)
10               v.p = u
Best Case Average Case Worst Case
Θ(|V|+|E|) Θ(|V|+|E|) Θ(|V|+|E|)
Edit

Korteste Vei Alle til alle (APSP)

Dette problemet er en direkte forlengelse av problemet korteste vei én til alle, for en kan jo selvfølgelig kjøre Bellman-Ford eller Dijkstra for hver node. Da får en hhv. kjøretiden |V|O(|E||V|=O(|E||V|2) og |V|O(|E|+|V|log|V|)=O(|V||E|+|V|2log|V|). Altså vil vi i en dense graf med negative kanter og mange kanter få en kjøretid på O(V4), fordi |E|=|V2|. Floyd-Warshall reduserer dette til O(V3). Merk at i en graf med negative sykler er korteste vei ikke definert og vi kan heller ikke bruke Floyd-Warshall.

Edit

Floyd-Warshall

Funker hvis det finnes negative kanter, men ingen negative sykler. Nodene må være lagret som en nabomatrise, ikke en naboliste.

Psuedokode fra wikipedia:

FLOYD-WARSHALL(W):
1    n = W.rows
2    D = W
3    for k = 1 to n
4        for i = 1 to n
5            for j = 1 to n
6                d_ij = min(d_ij, d_ik + d_kj)
7    return D

For hver tildeling av nodene i, j og k sjekker den om det finnes en raskere vei fra i til j som går gjennom k.

Best Case Average Case Worst Case
Θ(|V|3) Θ(|V|3) Θ(|V|3)

Algoritmen lager matriser D(k) der D(k)[i][j] gir korteste vei fra i til j som kun passerer noder nummerert k eller lavere.

Floyd-Warshall code

Edit

Transitive-Closure

Gjør akkurat det samme som Floyd-Warshall, men sjekker om det finnes en vei fra i til j eller ikke, den er altså ikke opptatt av vektene. Samme kjøretid som FW.

Edit

Maksimal flyt

Flyt kan visualiseres ved for eksempel et rørsystem for å levere vann i en by, eller som et nettverk med ulik kapasitet på de ulike kablene. Maks flyt er hvor mye som faktisk strømmer gjennom nettverket. Det kan finnes kanter med veldig liten kapasitet som hindrer flyt, så uansett om alle de andre kantene har stor kapasitet, vil maks flyt avhenge av den minste kanten dersom det ikke er noen vei rundt den. Maksimal flyt er nådd hvis og bare hvis residualnettverket ikke har flere flytforøkende stier.

Edit

Flytnettverk

Et flytnettverk er en rettet graf, der alle kantene har en ikke-negativ kapasitet. I tillegg er det et krav at dersom det finnes en kant mellom u og v, finnes det ingen kant motsatt fra v til u. Et flytnettverk har en kilde, s, og et sluk, t. Kilden kan sees på som startnode, og sluket som sluttnode. Grafen er ikke delt, så for alle v finnes en vei s ~ v ~ t. Alle kanter bortsett fra s har en kant inn. En node, bortsett fra kilden og sluket, har like mye flyt inn som den har flyt ut.

Et flytnetverk kan ha mange kilder og sluk. For å eliminere problemet, lager vi en superkilde og/eller et supersluk. Superkilden har en kant til hver av kildene, og kapasiteten på de kantene setter vi som uendelig. På samme måte lager vi supersluket. En kant fra hver av slukene, og setter kapasiteten til uendelig. Da er det et nytt nettverk, med kun en kilde og en sluk, og vi kan løse problemet som vanlig.

Edit

Residualnettverk

Residualnettverket er det som er igjen av kapasitet. Altså cf(u,v)=c(u,v)−f(u,v)

Å følge med på residualnettverket er nyttig. Hvis en sender 1000 liter vann fra u til v, og 300 liter fra v til u, er det nok å sende 700 liter fra u til v for å ha samme resultat.

Edit

Augmenting path

En flytøkende sti (augmenting path) er en sti fra starten til en node, som øker total flyt i nettverket. En augmenting path er en enkel sti fra s til t i residualnettverket. Per definisjon av residualnettverket kan vi øke f(u,v) i en augmenting path med cf(u,v) uten å gå over begrensningene.

Edit

Minimalt kutt

Et kutt i et flytnettverk er å dele grafen i to, S og T, og se på flyten gjennom kuttet. Altså

f(S,T)=∑s∈S∑t∈Tf(u,v)−∑s∈S∑t∈Tf(v,u)

Antall mulige kutt totalt i et nettverk med n noder:

|C|=2n−2

Av alle de mulige kuttene, ønsker vi å se på det kuttet som har minst flyt, da dette er "flaskehalsen" i nettverket.

Edit

Ford-Fulkersons metode

I hver iterasjon av Ford-Fulkerson finner vi en flytforøkende sti p, og bruker p til å modifisere f. Merk at Ford-Fulkersons ikke spesifiserer hvordan dette implementeres.

FORD-FULKERSON-METHOD(G, s, t)
1    initialize flow to 0
2    while there exists an augmenting path p in the residual network G_f
3        augment flow f along p
4    return f
Best Case Average Case Worst Case
O(VE2) O(Ef)
Edit

Edmonds-Karp

Edmonds-Karp er Ford-Fulkersons metode der BFS brukes for å finne augmenting path. Dette gir en kjøretid på O(VE2).

Edit

Heltallsteoremet

Om alle kapasiteter i flytnettverket er heltall, vil Ford-Fulkersons metode finne en maks flyt med en heltallsverdi, og flyten mellom alle nabonoder vil ha en heltallsverdi.

Edit

Problemkompleksitet

Edit

P, NP, NPC

Spørsmålet knyttet til om P=NP eller ikke er ett av de syv gjenværende Millennium-problemene.

Forholdene mellom P, NP, NPH og NPC Man vet at P er et subsett av NP, men man vet ikke om P=NP.

P Polynomial time
NP Nondeterministic polynomial time
NPC Nondeterministic polynomial time complete
NPH Nondeterministic polynomial time hard

At et problem er P, vil si at det er løsbart i polynomisk tid. Et NP-problem er et problem hvor vi gitt en løsning kan vi bevise at denne er sann (verifisere), i polynomisk tid. Dersom man klarer å falsifisere løsningen i polynomisk tid, er problemet en del av co-NP-klassen. NP-harde problemer kan ikke løses i polynomisk tid. Det sies å være en klasse av problemer som er minst like vanskelige som det vanskeligste problemet i NP-klassen. Sagt på en annen måte: problemer som lar seg redusere fra NP-problemer i polynomisk tid, men som ikke nødvendigvis lar seg verifisere i polynomisk tid med en gitt løsning. NP-harde problemer som lar seg verifisere i polynomisk tid kalles NP-komplette.

Edit

Redusibilitets-relasjon

For å forstå bevisteknikken som brukes for å bevise at et problem er NPC, er det et par begreper som må på plass. Ett av de er redusibilitets-relasjonen ≤P. I denne sammenhengen brukes det for å si at et språk (L1) er polynomisk-tid redusertbart til et annet språk (L2): L1≤pL2.

Boken (Cormen m.fl) trekker frem et eksempel der førstegradsligningen ax+b=0 kan transformeres til 0x2+ax+b=0. Alle gyldige løsninger for annengradsligningen er også gyldige løsninger for førstegradsligningen. Ideen bak eksempelet er å vise at et problem, X, kan reduseres til et problem, Y, slik at inputverdier for X kan løses med Y. Kan du redusere X til Y, betyr det at å løse Y krever minst like mye som å løse X, dermed må Y være minst like vanskelig å løse som X. Det er verdt å merke seg at reduskjonen ikke er gjensidig, du kan dermed ikke bruke X til å løse Y.

Edit

Noen kjente NPC problemer

Noen vanlige eksempler på problemer som er NPC:

  • Circuit-SAT
  • SAT
  • 3-CNF-SAT
  • Klikk-problemet (Clique)
  • Vertex-Cover
  • Hamiltonsykel (Hamilton cycle)
  • Traveling Salesman Problem (TSP)
  • Subset-Sum
Edit

En kort forklaring på hver av eksemplene

Circuit-SAT: Gitt en logisk krets med flere input og kun én output, finnes det en inputsekvens som gir 1 som output?

SAT: Gitt et logisk uttrykk, finnes det et sett inputverdier som gjør det totale uttrykket sant?

3-CNF-SAT: Gitt ett logisk uttrykk på CNF-form med tre distinkte ledd, finnes det et sett input verdier som gjør det totale uttrykket sant?

Klikk-problemet (Clique): Gitt en urettet graf G(V,E) og et heltall k, finnes det en komplett delgraf med k noder?

Vertex-Cover: Gitt en urettet graf G(V,E) og et heltall k, finnes det et nodedekke i G med k noder? Dvs., k noder som tilsammen ligger inntil alle kantene.

Hamiltonsykel: Gitt en urettet graf G(V,E), finnes det en sykel som inneholder alle nodene til G?

Traveling Salesman Problem (TSP): Gitt en komplett, vektet graf G(V,E) og et tall k, finnes det en sykel som inneholder alle nodene til G, med total vekt ≤k?

Subset-Sum: Gitt en mengde S med positive heltall, og et positivt heltall t, finnes det en delmengde av S med sum t?

Men hvordan kan vi bevise at et problem er NPC?

Boken (Cormen m.fl) deler det opp i to:

  1. Bevis at problemet hører til i NP-klassen. Bruk et sertifikat til å bevise at løsningen stemmer i polinomisk tid.
  2. Bevis at problemet er NP-hardt. Gjøres ved polynomisk tid reduksjon.

Gitt at P≠NP så kan vi si at et problem er NPC dersom det er både et NP-problem og et NP-hard-problem.

Sammenhengen er illustrert i figuren under: Sammenhengen mellom noen NPC problemer

Gitt at du skal bevise at TSP er et NPC-problem:

  1. Bevis at TSP∈NP. Serifikatet du bruker kan være en sekvens av n noder du skal besøke på turen. Dersom sekvensen kun består av unike noder (vi besøker ikke samme by flere ganger for eksempel), så kan vi summere opp kostnadene og verifisere om den totale kostnaden er mindre enn et gitt heltall k.
  2. Bevis at TSP er NP-hard. Hvis du har satt deg litt inn i hva de ulike problemene nevnt ovenfor beskriver, så kan det hende at du kjenner igjen deler av TSP som et Ham. cycle-problem (gitt en urettet graf G, finnes det en sykel som inneholder alle nodene kun én gang, og der sluttnode = startnode). Vi vet at TSP er minst like vanskelig å løse som Ham.-sykel problemet (da Ham.-sykel er et delproblem av TSP). Gitt at vi allerede har bevist at Ham.-sykel-problemet er NP-hard, kan vi ved polynomisk-tid reduksjon bevise at også TSP er NP-hard (Kan vi redusere Ham.-sykel til TSP i polynomisk tid, er TSP NP-H). HAM−CYCLE≤PTSP.

Da de aller fleste til nå tror at P≠NP, så følger det at NP-harde problemer ikke kan løses i polynoisk tid, ei heller NP-problemer. Klarer du derimot å bevise at P≠NP eller at P=NP, så har du krav på en rimelig stor pengepremie.

Edit

Kjøretider til pensum-algoritmer

Edit

Sortering og velging

  • n er antallet elementer som skal jobbes med.
  • k er den høyeste verdien tallene kan ha. (For Radix sort er dette høyeste verdi hvert siffer kan ha)
  • d er maks antall siffer et tall kan ha.
Algoritme Best case Average case Worst case
Insertion sort O(n) O(n2) O(n2)
Selection sort O(n2) O(n2) O(n2)
Merge sort O(nlgn) O(nlgn) O(nlgn)
Heapsort O(nlgn) O(nlgn) O(nlgn)
Quicksort O(nlgn) O(nlgn) O(n2)
Bubble sort O(n) O(n2) O(n2)
Bucket sort O(n+k) O(n+k) O(n2)
Counting sort O(n+k) O(n+k) O(n+k)
Radix sort O(d(n+k)) O(d(n+k)) O(d(n+k))
Select O(n) O(n) O(n)
Randomized select O(n) O(n) O(n2)
Edit

Heap-operasjoner

Operasjon Tid
Delete O(lgn)
Build-heap O(n)
Extract O(logn)
Heapify O(logn)
Heap-insert O(logn)
Heap-increase-key O(logn)
Heap-maximum Θ(1)
Edit

Graf-operasjoner

Algoritme Best case Average case Worst case
Topologisk sort Θ(V+E) Θ(V+E) Θ(V+E)
Dybde-først-søk O(E+V) O(E+V) O(E+V)
Bredde-først-søk O(E+V) O(E+V) O(E+V)
Prims O(ElgV) O(ElgV) O(ElgV)
Kruskals O(ElgV) O(ElgV) O(ElgV)
Bellmann-Ford O(VE) O(VE) O(VE)
Dijkstra O(VlgV+E) O(V2) O(V2)
Floyd-Warshall O(V3) O(V3) O(V3)
DAG-shortest-path O(V+E) O(V+E) O(V+E)
Edit

Eksempelkode i Python

Edit

Merge sort

def merge_sort(li):
    if len(li) < 2:    # Dersom vi har en liste med ett element, returner listen, da den er sortert
        return li
    sorted_l = merge_sort(li[:len(li)//2])
    sorted_r = merge_sort(li[len(li)//2:])
    return merge(sorted_l, sorted_r)

def merge(left, right):
    res = []
    while len(left) > 0 or len(right) > 0:
        if len(left) > 0 and len(right) > 0:
            if left[0] <= right[0]:
                res.append(left.pop(0))
            else:
                res.append(right.pop(0))
        elif len(left) > 0:
            res.append(left.pop(0))
        elif len(right) > 0:
            res.append(right.pop(0))
    return res
Edit

Quicksort

def quicksort(li):
    if len(li) < 2:
        return li
    pivot = li[0]
    lo = [x for x in li if x < pivot]
    hi = [x for x in li if x > pivot]
    return quicksort(lo) + pivot + quicksort(hi)
Edit

Binærsøk

def binary_search(li, value, start=0):
    # Dersom lista er tom, kan vi ikke finne elementet
    if not li:
        return -1
    middle = len(li) // 2
    middle_value = li[middle]
    if value == middle_value:
        return start + middle
    elif value < middle_value:
        # Første halvdel
        return binary_search(li[:middle], value, start)
    elif value > middle_value:
        # Siste halvdel
        return binary_search(li[middle + 1:], value, start + middle + 1)
Edit

Dijkstra

def dijkstra(G,start): #G er grafen, start er startnoden
    pq = PriorityQueue() # initialiserer en heap
    start.setDistance(0) # Initialiserer startnodens avstand
    pq.buildHeap([(node.getDistance(),node) for node in G]) # Lager en heap med alle nodene
    while not pq.isEmpty():
        currentVert = pq.delMin()    # velger greedy den med kortest avstand
        for nextVert in currentVert.getConnections():
            newDist = currentVert.getDistance() + currentVert.getWeight(nextVert) #kalkulerer nye avstander
            if newDist < nextVert.getDistance():
                nextVert.setDistance( newDist ) # oppdaterer avstandene
                nextVert.setPred(currentVert) # Oppdaterer foreldrene til den med kortest avstand
                pq.decreaseKey(nextVert,newDist) # lager heapen på nytt så de laveste kommer først.
Edit

Bellman-Ford

def bellman_ford(G, s) # G er grafen, med noder og kantvekter. s er startnoden.
    distance = {}
    parent = {}
    for node in G: # Initialiserer startverdier
        distance[node] = float('Inf')
        parent[node] = None
    distance[s]=0 # initialiserer startnoden

    # relax
    for (len(G)-1):
        for u in G:    # fra node u
            for v in G[u]:    # til naboene
                if distance[v] > distance[u] + G[u][v]: # Sjekker om avstanden er mindre
                    distance[v] = distance[u] + G[u][v]    # hvis ja, oppdaterer avstanden
                    parent[v]=u    # og oppdaterer parent

    # sjekk negative sykler
    for u in G:
        for v in G[u]:
            if distance[v] > distance[u] + G[u][v]:
                return False
Edit

Floyd-Warshall

def FloydWarshall(W):
    n = len(W)
    D = W
    PI = [[0 for i in range(n)] for j in range(n)]
    for i in range(n):
        for j in range(n):
            if(i==j or W[i][j] == float('inf')):
                PI[i][j] = None
            else:
                PI[i][j] = i

    for k in range(n):
        for i in range(n):
            for j in range(n):
                if D[i][j] > D[i][k] + D[k][j]:
                    D[i][j] = D[i][k] + D[k][j]
                    PI[i][j] = PI[k][j]
    return (D,PI)

Written by

Jens_Rensaa Stian Jensen niklasmh helenehs Gucci herleikh Ose Lasse Vad larshb MathiasPicker sjure audunsse torgersk CharlieTang jonasft mikalst mariasto jesperba oyvindrobertsen Lars Henrik Bolstad sigtot Vages thormartin91 Bortne iverjo fredrigy finninde Esso jornegj mii magnastr mmvergar Noah_Borch andervat duvholt testingLOL runenordmo BinaryDegree bendikiv estensen maikenflowers bendike atikagondal Brox largehendrix Kage jvals vebjorly Thyholt miaov thomahan
Last updated: 2 years ago.
  • Contact
  • Twitter
  • Statistics
  • Report a bug
  • Wikipendium cc-by-sa
Wikipendium is ad-free and costs nothing to use. Please help keep Wikipendium alive by donating today!