3.4 Hashing

Hashtabeller med kjeding, kollisjoner, lastfaktor og hva som gjør en hashfunksjon god — et voksende tema (3/3 i 2022–23).

45 min
8 oppgaver
Hashing
Din fremgang i kapitlet
0 / 8 oppgaver
Kapitlets plass i kurset

Forkunnskaper

- kap. 1.1 — de asymptotiske symbolene. Du trenger
særlig skillet mellom Θ\Theta (tett grense, både over og under) og OO (bare
øvre grense), og hva det vil si at noe er en forventet kjøretid framfor en
garantert.

Dette sto der: Θ(g(n))\Theta(g(n)) brukes når du har vist at kjøretiden både er minst
og høyst av størrelsesorden g(n)g(n), mens O(g(n))O(g(n)) bare lover et tak. En
«forventet» kjøretid er et gjennomsnitt over noe tilfeldig — her over hvordan
nøklene fordeler seg — og sier ingenting om hva som skjer i det verste tilfellet.

Trenger du en mykere inngang til OO-notasjonen først, ligger den i
Algoritmedefinisjon, pseudokode og kompleksitet (Big-O).

Du trenger ikke hauger eller søketrær for dette kapitlet. Hashtabellen er en
helt egen struktur, og den sammenlignes med søketreet først helt til slutt.

Notasjons- og pseudokodeliste

Hashtabellen, kollisjoner og kjeding (~15 min)

Et idrettslag låner ut nøkkelbrikker til klubbhuset. Hver brikke har et
femsifret nummer, og i løpet av et år er det kanskje tre hundre brikker i
omløp. Skal du kunne slå opp «hvem har brikke 40 231?» raskt, har du to
åpenbare, dårlige alternativer.

Du kan lage én plass for hvert mulige brikkenummer. Det gir oppslag på ett
øyeblikk, men krever hundre tusen plasser for tre hundre brikker. Eller du kan
legge brikkene i en liste og lete gjennom den. Det tar minimalt med plass, men
oppslaget koster Θ(n)\Theta(n).

Hashtabellen er kompromisset: du velger et håndterbart antall plasser, og
lar en regel bestemme hvilken plass hver nøkkel hører hjemme i. Regelen er
hashfunksjonen. Med tre hundre brikker holder det med for eksempel 401 plasser —
og oppslaget blir raskt likevel.

Hashtabell

en tabell T med mm plasser der plassen til en nøkkel bestemmes av en regel i
stedet for av innsettingsrekkefølgen. Plassene kalles bøtter. Tabellen bruker
Θ(m+n)\Theta(m + n) plass, altså langt mindre enn én plass per mulige nøkkel, og
støtter innsetting, søk og sletting.

Hashfunksjon

regelen hh som avgjør hvilken bøtte en nøkkel havner i: h(k)h(k) er et tall
mellom 00 og m1m-1. Den må være deterministisk (samme nøkkel gir samme bøtte
hver gang), rask å regne ut, og den bør spre nøklene jevnt over bøttene.
En hashfunksjon oversetter altså fra et stort nøkkelunivers til et lite
tabellområde.

Kollisjon

at to ulike nøkler får samme bøtte, altså h(k1)=h(k2)h(k_1) = h(k_2) for
k1k2k_1 \ne k_2. Kollisjoner er uunngåelige når nøkkeluniverset er større enn
antall bøtter, og en hashtabell er derfor alltid en struktur pluss en
kollisjonsstrategi. Spørsmålet er aldri om kollisjoner oppstår, men hva du gjør
når de gjør det.

Kjeding

kollisjonsstrategien der hver bøtte holder en lenket liste av alle nøklene
som hashet dit. Innsetting legger den nye nøkkelen fremst i lista og koster
O(1)O(1); søk må gå gjennom lista i den ene bøtta. Alle nøklene ligger altså inne i
tabellen, og antall nøkler kan overstige antall bøtter.

📜Pseudokode-kontrakt: `Chained-Hash-Insert` og `Chained-Hash-Search`
Antagelser om representasjon. T er en tabell med mm plasser, indeksert
T[0] til T[m-1]. Hver plass peker til en lenket liste, som er tom i
utgangspunktet. Hashfunksjonen h gir et heltall i 0..m-1 for enhver nøkkel.

Prebetingelse: T er en gyldig hashtabell med kjeding. Postbetingelse for
innsetting:
x ligger fremst i lista T[h(x.key)], og ingen andre elementer
er flyttet.

Chained-Hash-Insert(T, x)
  Input:  hashtabell T med kjeding, element x med noekkel x.key
  Output: x lagt inn fremst i lista T[h(x.key)]
  sett x fremst i den lenkede lista T[h(x.key)]
  Kjoretid: O(1)

Chained-Hash-Search(T, k)
  Input:  hashtabell T med kjeding, noekkel k
  Output: elementet med noekkel k, eller NIL
  i = h(k)
  for hvert element y i den lenkede lista T[i]
      if y.key == k
          return y
  return NIL
  Kjoretid: Theta(1 + alfa) forventet, Theta(n) verste

Grunnideen i én setning: hashfunksjonen gjør om et søk i hele datamengden til
et søk i én enkelt kjede, og alt henger på hvor korte de kjedene er.

Kjøretid: innsetting er O(1)O(1) fordi den ikke leter — den legger elementet
fremst uten å sjekke om nøkkelen finnes fra før. Søk koster én hashutregning
pluss gjennomgangen av kjeden, altså Θ(1+α)\Theta(1+\alpha) forventet under enkel
uniform hashing, og Θ(n)\Theta(n) når alle nn nøklene ligger i samme bøtte.

✏️Eksempel 1: Seks nøkler inn i en tabell med sju bøtter

En hashtabell har m=7m = 7 bøtter og bruker divisjonsmetoden h(k)=kmod7h(k) = k \bmod 7.
Sett inn nøklene 26, 13, 40, 9, 18, 31 i denne rekkefølgen.

a) Hvordan ser tabellen ut etterpå?
b) Hva er lastfaktoren?
c) Hvor mange sammenligninger koster et mislykket søk etter nøkkelen 5?

a) Vi regner ut h(k)h(k) for hver nøkkel og legger den fremst i sin bøtte.

StegNøkkel kh(k) = k mod 7Kollisjon?Bøtta etter innsettingen
12626 mod 7 = 5nei26
21313 mod 7 = 6nei13
34040 mod 7 = 5ja (1 element der fra før)40 -> 26
499 mod 7 = 2nei9
51818 mod 7 = 4nei18
63131 mod 7 = 3nei31

Tabellen til slutt:
T[0]: tom
T[1]: tom
T[2]: 9
T[3]: 31
T[4]: 18
T[5]: 40 -> 26
T[6]: 13
b) α=n/m=6/70,86\alpha = n/m = 6/7 \approx 0{,}86.
c) h(5)=5h(5) = 5, så søket går til bøtte 5. Der ligger 40 og 26. Begge må
sammenlignes med 5 før søket kan svare at nøkkelen ikke finnes: to
sammenligninger
.
Legg merke til rekkefølgen i bøtte 5. Nøkkelen 40 kom sist, men ligger

først. Chained-Hash-Insert legger alltid det nye elementet fremst, fordi det er

O(1)O(1) — å legge det bakerst ville krevd at man gikk gjennom hele lista.

Legg merke til hva et mislykket søk koster. Det er dyrere enn et vellykket

søk i gjennomsnitt, fordi det alltid må gå gjennom hele kjeden. Et vellykket
søk kan stoppe underveis.

📝Oppgave 1
Eksamensnivå, sjanger C

En hashtabell med kjeding har m=5m = 5 bøtter og bruker h(k)=kmod5h(k) = k \bmod 5. Sett
inn nøklene 14, 29, 7, 20, 33, 11 i denne rekkefølgen.

a) Oppgi innholdet i hver av de fem bøttene.
b) Hva er lastfaktoren?

Lastfaktoren og hva et søk faktisk koster (~15 min)

Hashtabellens hastighet er ikke en egenskap ved strukturen alene. Den avhenger
av hvor full tabellen er, og av hvor jevnt nøklene har fordelt seg. Det første
måles av lastfaktoren.

Lastfaktor

forholdet α=n/m\alpha = n/m mellom antall lagrede nøkler og antall bøtter, altså
gjennomsnittlig kjedelengde. Med kjeding kan α\alpha være både mindre og større
enn 1. Lastfaktoren er den ene knappen du kan skru på: holder du α\alpha
konstant ved å øke mm når nn vokser, holder du også den forventede søketiden
konstant.

📜Forventet søketid med kjeding
Antakelsen kalles enkel uniform hashing: hver nøkkel er like sannsynlig
å hashe til hver av de mm bøttene, uavhengig av hvor de andre nøklene havner.
Dette er en antakelse om nøklene og hashfunksjonen sammen — ikke noe algoritmen
kan garantere.

Under den antakelsen er den forventede lengden på en kjede lik lastfaktoren
α=n/m\alpha = n/m. Et søk koster da

Θ(1+α),\Theta(1 + \alpha),

der leddet 1 er hashutregningen og oppslaget i tabellen, som du betaler uansett,
og leddet α\alpha er gjennomgangen av kjeden.

Konsekvensen er den viktigste setningen i kapitlet: holder du α\alpha
begrenset av en konstant — for eksempel ved å velge mm slik at
n=O(m)n = O(m) — blir forventet søketid Θ(1)\Theta(1). Det er nettopp derfor
hashtabeller er raske i praksis.

Verste tilfelle er noe helt annet. Havner alle nn nøklene i samme bøtte, er
tabellen redusert til én lenket liste, og et søk koster Θ(n)\Theta(n). Det er ikke
et teoretisk grensetilfelle: velger du m=6m = 6 og alle nøklene er delelige med 6,
skjer det.

Derfor: søk i en hashtabell med kjeding er Θ(1+α)\Theta(1+\alpha) forventet
og Θ(n)\Theta(n) i verste tilfelle. Uttrykket «garantert O(1)O(1)» er galt, og
det er den feilen dette temaet oftest tester.

✏️Eksempel 2: Samme antall nøkler, mye dårligere fordeling

En hashtabell har m=9m = 9 bøtter og h(k)=kmod9h(k) = k \bmod 9. Sett inn nøklene
18, 27, 45, 13, 36, 22, 31.

a) Hvordan ser tabellen ut?
b) Hva er lastfaktoren, og hva koster et mislykket søk etter nøkkelen 40?
c) Hva forteller dette om forskjellen mellom forventet og verste tilfelle?

a)

StegNøkkel kh(k) = k mod 9Kollisjon?Bøtta etter innsettingen
11818 mod 9 = 0nei18
22727 mod 9 = 0ja (1 element der fra før)27 -> 18
34545 mod 9 = 0ja (2 elementer der fra før)45 -> 27 -> 18
41313 mod 9 = 4nei13
53636 mod 9 = 0ja (3 elementer der fra før)36 -> 45 -> 27 -> 18
62222 mod 9 = 4ja (1 element der fra før)22 -> 13
73131 mod 9 = 4ja (2 elementer der fra før)31 -> 22 -> 13

T[0]: 36 -> 45 -> 27 -> 18
T[1]: tom
T[2]: tom
T[3]: tom
T[4]: 31 -> 22 -> 13
T[5]: tom
T[6]: tom
T[7]: tom
T[8]: tom
b) α=7/90,78\alpha = 7/9 \approx 0{,}78. Et mislykket søk etter 40 går til bøtte
40mod9=440 \bmod 9 = 4, og må gjennom hele kjeden der: tre sammenligninger.
c) Lastfaktoren er lavere enn i Eksempel 1 (0,780{,}78 mot 0,860{,}86), men
tabellen er klart dårligere. Sju av ni bøtter står tomme, og de to kjedene er
fire og tre lange. Lastfaktoren er et gjennomsnitt, og gjennomsnittet sier
ingenting om hvordan nøklene faktisk har spredt seg.
Hvorfor det gikk galt her. Fire av nøklene er delelige med 9, så de gir alle

h(k)=0h(k) = 0. Med m=9m = 9 blir hashfunksjonen dermed helt avhengig av om nøklene

tilfeldigvis har en felles faktor med 9. Det er ikke tilfeldig at anbefalingen er
å velge mm som et primtall som ikke ligger nær en potens av 2: da er det

langt færre nøkkelmønstre som kan kollapse på denne måten.

📝Oppgave 2
Eksamensnivå, sjanger C

En hashtabell med kjeding har m=8m = 8 bøtter og h(k)=kmod8h(k) = k \bmod 8. Sett inn
nøklene 41, 17, 25, 33, 6, 19 i denne rekkefølgen.

a) Oppgi innholdet i hver bøtte.
b) Hva er lastfaktoren, og hva er den lengste kjeden?
c) Hvor mange sammenligninger koster et mislykket søk etter nøkkelen 12?

📝Oppgave 3
Eksamensnivå, sjanger C…

En hashtabell med kjeding har m=6m = 6 bøtter og h(k)=kmod6h(k) = k \bmod 6. Sett inn
nøklene 12, 18, 24, 30, 7.

a) Oppgi innholdet i hver bøtte.
b) Hva koster et mislykket søk etter nøkkelen 42, målt i sammenligninger?
c) Hva er kjøretiden for søk i denne tabellen i verste tilfelle, uttrykt i
nn?

📝Oppgave 4
Eksamensnivå, sjanger C…

En hashtabell med kjeding har m=11m = 11 bøtter og h(k)=kmod11h(k) = k \bmod 11. Sett inn
nøklene 52, 19, 41, 30, 63, 8.

a) Oppgi innholdet i hver bøtte.
b) Er dette verstetilfellet for en hashtabell med kjeding og n=6n = 6? Svar ja
eller nei, og begrunn med én setning.
c) Betyr resultatet at m=11m = 11 er et dårlig valg? Begrunn med én setning.

Hva gjør en hashfunksjon god? (~15 min)

Kravene er tre, og de kan alle formuleres i én setning hver. Dette er
definisjonssjangeren i praksis: du blir bedt om å nevne kravene, ikke om å bevise
noe.

Uniform fordeling. Nøklene skal spres jevnt over bøttene, slik at ingen
kjede blir mye lengre enn de andre. Dette er det som gjør antakelsen om enkel
uniform hashing rimelig, og dermed det som gjør Θ(1+α)\Theta(1+\alpha) til noe du kan
regne med i praksis.

Determinisme. Samme nøkkel må gi samme bøtte hver gang. Uten det kan du
sette inn en nøkkel og aldri finne den igjen. Kravet virker banalt, men det
utelukker enhver hashfunksjon som bruker tilfeldighet eller tid.

Hastighet. Utregningen av h(k)h(k) må koste O(1)O(1). Er hashfunksjonen dyrere
enn å gå gjennom en kort kjede, har du tapt det du kom for.

Divisjonsmetoden

hashfunksjonen h(k)=kmodmh(k) = k \bmod m, altså resten når nøkkelen deles på antall
bøtter. Den er rask og enkel, og den er den du regner med for hånd på eksamen.
Kvaliteten avhenger helt av valget av mm: et primtall som ikke ligger nær en
potens av 2 er anbefalingen, fordi mm som potens av 2 gjør at bare de nederste
bitene i nøkkelen teller.

Multiplikasjonsmetoden

en alternativ hashfunksjon der nøkkelen først multipliseres med en konstant
mellom 0 og 1, og desimaldelen av produktet deretter skaleres opp til
0..m10..m-1. Fordelen er at valget av mm ikke er kritisk — metoden fungerer også
når mm er en potens av 2. Du trenger å kjenne den som begrep og vite hva den
løser; det er divisjonsmetoden du regner med.

📝Oppgave 5
Eksamensnivå, sjanger F

Stemmer det at søk i en hashtabell med kjeding alltid er O(1)O(1)?

📝Oppgave 6
Eksamensnivå, sjanger F

Svar ja eller nei på hver av påstandene, og begrunn hver med én setning.

a) Lastfaktoren i en hashtabell med kjeding kan ikke være større enn 1.
b) En hashfunksjon må være deterministisk.
c) Kollisjoner kan unngås helt hvis hashfunksjonen er god nok.

📝Oppgave 7
Eksamensnivå, sjanger D
a) Definer lastfaktoren i en hashtabell.
b) Oppgi forventet søketid for en hashtabell med kjeding, med den antakelsen
som kreves, og verste søketid.
📝Oppgave 8
Eksamensnivå, sjanger E…

To hashtabeller bruker begge kjeding, har begge m=6m = 6 bøtter og lagrer begge
fem nøkler. Tabell 1 har nøklene 12, 18, 24, 30, 7; tabell 2 har nøklene
1, 2, 3, 4, 5. Begge bruker h(k)=kmod6h(k) = k \bmod 6.

a) Hva er lastfaktoren i hver av tabellene?
b) Hva er lengste kjede i hver av dem?
c) Stemmer det at to hashtabeller med samme lastfaktor har samme søketid i
verste tilfelle? Svar ja eller nei, og begrunn med én setning.

Kjøretidene, samlet

OperasjonForventetVersteKrav / egenskap
Chained-Hash-InsertO(1)O(1)O(1)O(1)legger fremst i kjeden, sjekker ikke om nøkkelen finnes fra før
Chained-Hash-SearchΘ(1+α)\Theta(1+\alpha)Θ(n)\Theta(n)forventningen krever enkel uniform hashing
Chained-Hash-DeleteO(1)O(1)O(1)O(1)forutsetter dobbeltlenket liste og at elementet allerede er funnet
PlassbrukΘ(m+n)\Theta(m+n)Θ(m+n)\Theta(m+n)mm bøtter pluss nn lagrede nøkler

Til sammenligning: et binært søketre gir O(h)O(h) på søk, der høyden hh er
Θ(lgn)\Theta(\lg n) forventet for et tilfeldig bygd tre og Θ(n)\Theta(n) i verste
tilfelle — se kap. 3.2. Hashtabellen er raskere i det
typiske tilfellet, men søketreet gir deg noe hashtabellen ikke gir: nøklene i
sortert rekkefølge, i Θ(n)\Theta(n), via Inorder-Tree-Walk. Trenger du orden,

hjelper ingen hashfunksjon deg.

Begrepsbank

Begrepsbanken er flashcard- og repetisjonsstoff — den gjentar det du nettopp har
lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder
kjernestoffet.

Bøtte

én av de mm plassene i hashtabellen. Med kjeding holder hver bøtte en lenket
liste over alle nøklene som hashet dit, og en bøtte kan være tom, ha ett element
eller ha mange. Antall bøtter er mm, og det er den ene størrelsen du styrer selv
når du dimensjonerer tabellen.

Enkel uniform hashing

antakelsen om at hver nøkkel er like sannsynlig å hashe til hver av de mm
bøttene, uavhengig av hvor de andre nøklene havner. Det er denne antakelsen som
gjør forventet søketid Θ(1+α)\Theta(1+\alpha). Den er en antakelse om nøklene og
hashfunksjonen sammen, ikke en garanti algoritmen kan gi.

Forventet søketid med kjeding
Θ(1+α)\Theta(1+\alpha) under enkel uniform hashing: ett ledd for hashutregningen og
tabelloppslaget, og ett ledd for gjennomgangen av kjeden. Holdes α\alpha
begrenset av en konstant, er forventet søketid Θ(1)\Theta(1) — men det er en
forventning, ikke en garanti.
Verste søketid med kjeding
Θ(n)\Theta(n), som inntreffer når alle nn nøklene har hashet til samme bøtte og
tabellen dermed er redusert til én lenket liste. Dette skjer ikke bare i teorien:
m=8m = 8 med nøkler som ligger 8 fra hverandre holder.
Åpen adressering

den andre hovedstrategien for kollisjoner, der en kolliderende nøkkel plasseres
i en annen ledig bøtte i selve tabellen i stedet for i en liste utenfor. Alle
nøklene ligger da inne i tabellen, og lastfaktoren kan derfor ikke overstige 1.
Kjeding og åpen adressering skal ikke blandes i et svar.

Mislykket søk

et søk etter en nøkkel som ikke finnes. Det koster alltid gjennomgang av
hele kjeden i den bøtta h(k)h(k) peker på, mens et vellykket søk kan stoppe
underveis. Blir du bedt om å telle sammenligninger, er det derfor kjedelengden
som er svaret.

Valg av mm i divisjonsmetoden

tommelfingerregelen er å velge mm som et primtall som ikke ligger nær en potens
av 2. Er mm en potens av 2, avgjøres bøtta bare av de nederste bitene i
nøkkelen, og all annen informasjon i tallet kastes bort. Valget av mm er den ene
designbeslutningen som avgjør om divisjonsmetoden sprer godt.

Hashtabell mot binært søketre

hashtabellen gir forventet Θ(1+α)\Theta(1+\alpha) på søk, men ingen orden:
nøklene kan ikke leses ut sortert, og det finnes ingen naturlig «neste nøkkel».
Søketreet gir O(h)O(h) på søk og sortert utskrift i Θ(n)\Theta(n). Trenger du
rekkefølge eller intervallsøk, velger du treet.

Hvorfor kollisjoner er uunngåelige

fordi nøkkeluniverset nesten alltid er mye større enn antall bøtter: med flere
mulige nøkler enn plasser må minst to nøkler dele plass. En hashtabell er derfor
alltid en tabell pluss en kollisjonsstrategi, og påstanden «med en god nok
hashfunksjon slipper man kollisjoner» er gal.

Å holde lastfaktoren konstant

strategien der tabellen forstørres når nn vokser, slik at α=n/m\alpha = n/m holder
seg under en fast grense. Da forblir forventet søketid Θ(1)\Theta(1) uansett hvor
mange nøkler som kommer inn. Forstørringen koster å gjøre — det er samme
mekanikk som i den dynamiske tabellen i kap. 3.5.

Sjanger F — «stemmer dette?»

oppgavetypen der du får en påstand og skal avgjøre om den holder. Svarformen er
fast: ja eller nei først, deretter én presis setning som begrunner. Hashing
er et av temaene som oftest kommer i denne formen, og påstanden handler nesten
alltid om at søk skulle være garantert konstant.

Repetisjonsoppgaver

Dette kapitlet er skrevet av Anthropics toppmodeller (Claude Opus og Claude Fable) og er foreløpig ikke manuelt gjennomgått — kvalitetskontrollen gjøres av uavhengige KI-agenter, og innmeldte feil rettes fortløpende. Funnet en feil? Meld fra, så retter vi den. Les mer om hvordan innholdet lages.

Skolesaga er en uavhengig læringsressurs og er ikke tilknyttet eller godkjent av Norges teknisk-naturvitenskapelige universitet. Dette er ikke offisielt studiemateriell. Les mer.