3.3 DRILL — Linear-probing håndkjøring og insert-pseudokode
Full drill på sjanger E (håndkjør linear-probing-innsetting, oppgi hele tabellen) og I (skriv insert-prosedyren korrekt).
Dette er drillkapitlet for de to hashing-sjangrene, og begge har vært på alle
sju settene (100 %):
- Sjanger E — håndkjøring, altså at du utfører innsettingene steg for steg og
oppgir hele tabellen som sluttilstand.
- Sjanger I — hashing i pseudokode, altså at du skriver Insert eller
Contains selv og oppgir kjøretiden.
Si det som det er: dette er tyngre enn i et generisk algoritmepensum. Ved
NTNUs TDT4120 er hashing et faktapunkt i under halvparten av settene. Her er det
en utførelsesferdighet som testes i hvert eneste sett, på to måter samtidig.
Kommer du fra en generisk algoritmebok, har du sannsynligvis lest hashing som et
kapittel om «forventet » og gått videre. Det holder ikke her.
Den gode nyheten: håndkjøring er den mest mekaniske ferdigheten i hele faget.
Det finnes ingen innsikt å få — det finnes en oppskrift, og den kan trenes til den
er feilfri. Det er nettopp det dette kapitlet er til for.
Prioritet: høyeste.
Slik er kapitlet lagt opp (80 min): oppskriften og et sensor-kommentert case
først (ca. 20 min), så tolv oppgaver i tre bolker à ca. 20 min. Bolkene er
uavhengige — ta gjerne én per kveld. Etter hver bolk står en boks som sier hva
oppgavene testet.
To oppskrifter, én for hver sjanger. Første steg er å se hvilken du har.
Sjanger E — håndkjøring. Du får nøkler, en tabellstørrelse og en
hashfunksjon, og skal levere sluttilstanden.
1. Tegn tabellen først. Skriv indeksene til på én linje og la det
være plass under hver. Nå kan du ikke miste tellingen.
2. Regn ut for ALLE nøklene, i en egen linje, før du setter inn noe.
Dette ene grepet fjerner de fleste regnefeilene, og det gir deg et regnestykke
å kontrollere mot etterpå.
3. Sett inn én om gangen. Er plassen opptatt av en annen nøkkel, gå til
. Er den opptatt av samme nøkkel, skriv over og stopp.
4. Skriv opp probing-sekvensen for hver nøkkel mens du går. Det koster tre
sekunder og gir delvis uttelling hvis sluttilstanden blir feil.
5. Lever hele tabellen, kommaseparert, med _ for tomme plasser.
Sjanger I — pseudokode. Du skal skrive prosedyren og oppgi kjøretiden.
1. Navngi problemet: «innsetting i hashtabell med lukket hashing og lineær
probing».
2. Oppgi antagelser om representasjon: array med N plasser fra 0, hver plass
tom eller med én nøkkel, h(k) = k mod N, tabellen ikke full.
3. Skriv algoritmen. De to detaljene som gir poeng, ligger begge i
while-linja: mod N i oppdateringen og and T[i] er ulik k i betingelsen.
4. Oppgi kjøretiden som matcher koden: forventet, verste,
med definert som antall lagrede nøkler.
De to kontrollene som tar to sekunder hver, og som fanger nesten alt:
- Er noen indeks i sporingen din større enn ? Da har du glemt mod N.
- Stemmer antall nøkler i tabellen med antall ULIKE nøkler du fikk utdelt? Er
det flere, har du duplisert en nøkkel i stedet for å overskrive.
(Eksamensnivå, sjanger E og I.) Et sett har denne oppgaven, verdt 6 poeng:
a) (3 p) Sett inn nøklene 31, 20, 42, 9, 53 og 64 i denne rekkefølgen i en tom
hashtabell med og , med lineær probing. Oppgi hele
tabellen.
b) (2 p) Skriv Insert(T, k) i pseudokode, og oppgi kjøretiden.
c) (1 p) Hva er load-faktoren etter innsettingene, og hva sier antall prøver om
hvor godt hashfunksjonen fungerte her?
Steg 2 i oppskriften først — regn ut alle hashverdiene:
, , ,
, , .
Alle seks gir . Det er en opplysning verdt å ha før du begynner: du
vet nå at dette blir én lang klynge, og at wraparound kommer i spill, siden 9 er
nest siste plass.
| Steg | Nøkkel | Prøvde indekser | Tabell etter steget | |
|---|---|---|---|---|
| 1 | 31 | 9 | 9 | _, _, _, _, _, _, _, _, _, 31, _ |
| 2 | 20 | 9 | 9 -> 10 | _, _, _, _, _, _, _, _, _, 31, 20 |
| 3 | 42 | 9 | 9 -> 10 -> 0 | 42, _, _, _, _, _, _, _, _, 31, 20 |
| 4 | 9 | 9 | 9 -> 10 -> 0 -> 1 | 42, 9, _, _, _, _, _, _, _, 31, 20 |
| 5 | 53 | 9 | 9 -> 10 -> 0 -> 1 -> 2 | 42, 9, 53, _, _, _, _, _, _, 31, 20 |
| 6 | 64 | 9 | 9 -> 10 -> 0 -> 1 -> 2 -> 3 | 42, 9, 53, 64, _, _, _, _, _, 31, 20 |
Sluttilstand:
indeks: 0 1 2 3 4 5 6 7 8 9 10
T: 42 9 53 64 _ _ _ _ _ 31 20På én linje: 42, 9, 53, 64, _, _, _, _, _, 31, 20Sensornotat, a) — 3 poeng. Typisk fordeling: 1 p for riktige hashverdier,
1 p for riktig probing med wraparound, 1 p for at hele tabellen er oppgitt med
tomme plasser markert.
Delvis riktig tilstand gir delvis uttelling. Har du riktig sekvens menbommer på én plassering, får du som regel to av tre poeng. Det siste poenget
henger på to ting: at ingen indeks er utenfor til , og at tabellen
er levert hel, ikke som en liste over hvor nøklene havnet.
Steg 3 er der poenget vinnes eller tapes: fra plass 10 går sekvensen til plass
0, ikke til plass 11.
b) Pseudokoden.
Problemet: innsetting i hashtabell med lukket hashing og lineær probing.
Antagelser om representasjon: T er et array med N plasser indeksert fra 0;
hver plass er tom eller inneholder én nøkkel; h(k) = k mod N; tabellen er ikke
full.
Procedure Insert(T, k)
Input: hashtabell T som array med N plasser (indeks fra 0), noekkel k
Output: T med k satt inn; lik noekkel overskrives i stedet for aa dupliseres
N = T.length
i = k mod N
while T[i] er ikke tom and T[i] er ulik k:
i = (i + 1) mod N
T[i] = kKjøretid: forventet, i verste tilfelle, der er antallnøkler i tabellen.
Sensornotat, b) — 2 poeng. 1 p for korrekt løkke med begge detaljene(
mod Nog sjekken for lik nøkkel), 1 p for kjøretiden med begge tilfellene.
En besvarelse med riktig kode men uten kjøretid får 1 p. En som skriver
«» uten «forventet», mister som regel et halvt. En som glemmermod N,mister kodepoenget helt — det er felle #11, probing som går utenfor
til .
c) Load-faktor og prøvetelling.
.
Antall prøver: , altså 3,5 per innsetting.
Med en hashfunksjon som spredte nøklene jevnt, ville snittet ligget nær 1. Her ga
alle seks nøklene samme hashverdi, og tabellen degenererte til et lineært søk.
Det er nøyaktig verste tilfelle , og det illustrerer hvorfor kjøretiden
alltid må ha ordet «forventet» foran seg.
Sensornotat, c) — 1 poeng. Løsningen får poenget for load-faktoren alene;observasjonen om prøvetallet er det som skiller en god besvarelse fra en
tilstrekkelig. Merk at nøklene her er valgt slik at de alle er modulo —
et eksamenssett gjør sjelden det ved et uhell.
Margnotat om delvis uttelling. Denne oppgaven er verdt 6 poeng, og du kan få
4 av dem uten å ha en eneste feilfri tabell: pseudokoden, kjøretiden og
load-faktoren er alle uavhengige av at håndkjøringen stemmer. La aldri en
deloppgave stå tom.
Bolk 1 — grunnleggende håndkjøring (ca. 20 min)
Fire oppgaver som bygger opp fra ingen kollisjoner til full klynge. Bruk
oppskriften hver gang, også når oppgaven ser triviell ut — det er vanen du skal
ha på eksamensdagen.
og 11 i denne rekkefølgen.
a) Regn ut for alle fire først.
b) Oppgi hele tabellen.
c) Hvor mange prøver kostet innsettingene til sammen?
a) Regn ut for alle fire.
b) Oppgi hele tabellen.
c) Sammenlign antall prøver med oppgave 1. Hva forklarer forskjellen?
5.
a) Oppgi hele tabellen.
b) Hvilke av innsettingene brukte wraparound?
c) Hvorfor havnet ikke 5 i klyngen?
i denne rekkefølgen.
a) Oppgi hele tabellen.
b) Hvor mange nøkler ligger i tabellen til slutt?
c) Hva ville tabellen sett ut som hvis Insert ikke hadde sjekket for lik
nøkkel?
De fire oppgavene rullerte grunnmekanikken:
- Oppgave 1 testet det enkleste tilfellet — ingen kollisjoner — og at du
likevel bruker oppskriften.
- Oppgave 2 testet full klynge fra plass 0, og at du ser forskjellen mellom en
tabell som ser klumpete ut og en som er det.
- Oppgave 3 testet wraparound over slutten av tabellen, som er felle #11.
- Oppgave 4 testet overskriving ved lik nøkkel, som er den andre halvdelen av
samme felle.
Klarte du 3 og 4 uten å nøle, sitter de to detaljene som gir flest poeng.
Bommet du på en av dem, er de to kontrollene fra oppskriften verdt å gjøre til en
vane: ingen indeks over , og antall nøkler = antall ulike nøkler.
Bolk 2 — søk, større tabeller og rehashing (ca. 20 min)
— naturlig pausepunkt —
Fire oppgaver som utvider repertoaret: søk i en ferdig tabell, større , og en
rehashing.
22.
a) Oppgi hele tabellen.
b) Hva er load-faktoren etterpå?
c) Hvor mange klynger har tabellen?
Bruk tabellen fra oppgave 5:18, 27, _, _, 4, 13, 22, _, _ med .
a) Søk etter 13. Hvilke indekser prøves?
b) Søk etter 31. Hvilke indekser prøves, og hva blir svaret?
c) Søk etter 8. Hvilke indekser prøves, og hva blir svaret?
d) Hvorfor er søket i c) så mye kortere enn i b)?
og 1.
a) Oppgi hele tabellen.
b) Hvor mange prøver kostet det til sammen?
c) Tabellen har 13 plasser og bare 5 nøkler. Hvorfor ble det likevel så mange
prøver?
Tabellen 14, 21, 28, 35, _, _, _ med
er resultatet fra oppgave 2. Terskelen for rehashing er .
a) Er terskelen overskredet?
b) Rehash tabellen til . Regn ut de nye hashverdiene og vis
reinnsettingen, med rekkefølge venstre mot høyre i den gamle tabellen.
c) Oppgi den nye tabellen, den nye load-faktoren og antall prøver.
- Oppgave 5 testet at du ser hvor klyngegrensene går, og hvorfor to korte
klynger er bedre enn én lang.
- Oppgave 6 testet Contains på alle tre utfallene: funnet, ikke funnet etter
lang probing, og ikke funnet med én gang.
- Oppgave 7 testet at load-faktor og faktisk kostnad er to forskjellige ting.
Det er et poeng en drøftingsoppgave gjerne ber om.
- Oppgave 8 testet rehashing med korrekt reinnsetting, og hvorfor de gamle
indeksene er verdiløse.
Oppgave 7 er den som oftest overrasker: en tabell som er under halvfull kan
likevel oppføre seg som verste tilfelle, hvis hashfunksjonen er dårlig for nettopp
de nøklene.
Bolk 3 — pseudokode og kanttilfeller (ca. 20 min)
Fire oppgaver på sjanger I. Her er formen like viktig som innholdet: problemet
navngitt, antagelser oppgitt, algoritmen, og kjøretiden som matcher.
Skriv Insert(T, k) for lukket hashing med lineær
probing.
a) Oppgi antagelser om representasjon.
b) Skriv prosedyren.
c) Oppgi kjøretiden, og forklar hvorfor den har to tilfeller.
Skriv Contains(T, k), som avgjør om nøkkelen k
finnes i tabellen.
a) Skriv prosedyren.
b) Hvorfor kan søket avsluttes på en tom plass?
c) Hva skjer hvis tabellen er full og nøkkelen ikke finnes? Hvordan sikrer du
at prosedyren terminerer?
En besvarelse har levert dette:
Procedure Insert(T, k)
Input: hashtabell T, noekkel k
Output: T med k satt inn
i = k mod T.length
while T[i] er ikke tom:
i = i + 1
if i == T.length:
i = 0
T[i] = k
Kjoeretid: O(1)a) Er wraparound håndtert riktig?
b) Hvilke feil gjenstår?
c) Vis konkret hva som går galt med , tabellen _, 13, 19, _, _, _ og
nøkkelen 13.
10, 15, 20 og 25.
a) Oppgi hele tabellen og load-faktoren.
b) Hva skjer hvis du nå prøver å sette inn nøkkelen 30 med Insert fra
kap. 3.1?
c) Hva skjer hvis du prøver å sette inn 30 og tabellen i tillegg er helt full?
d) Hvordan bør en robust implementasjon håndtere dette?
Samlet, i den rekkefølgen de koster mest poeng. De to første er begge felle
#11 i bokas feilregister — probing som går utenfor til , eller som
ikke overskriver en lik nøkkel.
- Manglende mod N i probing-steget. Når klyngen når plass , må neste
plass være 0. Kontrollen: ingen indeks i sporingen din skal være større enn
.
- Manglende sjekk for lik nøkkel. Løkkebetingelsen må ha and T[i] er ulik k.
Kontrollen: antall nøkler i tabellen skal stemme med antall ulike nøkler
du fikk utdelt.
- Å levere bare de innsatte nøklene. Sensor ber om hele tabellen med _
for tomme plasser. De tomme plassene er en del av svaret.
- Regnefeil i . er 1, ikke 3. Regn ut alle hashverdiene i en
egen linje først — det er det enkleste grepet i hele kapitlet, og det fanger
flest feil.
- uten «forventet». Kjøretiden må matche algoritmen: forventet,
verste. Begge skal med.
- Manglende kanttilfelle i Contains. Uten telleren antall < N terminerer
ikke løkka i en full tabell.
- og brukt om hverandre. er tabellstørrelsen, er antall lagrede
nøkler. Å bruke dem uten å definere dem er felle #10.
Og den viktigste vanen: skriv ned probing-sekvensen for hver nøkkel mens du
går. Blir sluttilstanden feil, viser sekvensen at metoden var riktig — og delvis
riktig gir delvis uttelling.
Begrepsbank
Begrepsbanken er flashcard-/repetisjonsstoff — den gjentar det du nettopp har
lest. Hopp trygt over ved førstegangslesing; tidsanslaget for kapitlet gjelder
kjernestoffet.
Fem steg: 1) tegn tabellen med indeksene til ; 2) regn ut
for alle nøklene før du setter inn noe; 3) sett inn én om gangen med
ved kollisjon og overskriving ved lik nøkkel; 4) noter
probing-sekvensen underveis; 5) lever hele tabellen med _ for tomme plasser.
Steg 2 er det som fanger flest feil, og steg 4 er det som sikrer delvis uttelling
når sluttilstanden likevel blir gal.
mod N glemt.Kontroll 2: stemmer antall nøkler i tabellen med antall ulike nøkler du
fikk utdelt? Er det flere, er en nøkkel duplisert.
Til sammen fanger de begge halvdelene av felle #11, og de tar to sekunder
hver.
Sjanger E gir poeng per delmoment: riktige hashverdier, riktig probing med
wraparound, og hele tabellen levert med tomme plasser markert.
Derfor er det verdt å skrive ned probing-sekvensen selv om du er usikker på
sluttilstanden, og derfor skal ingen deloppgave stå tom. Det siste poenget henger
typisk på wraparound og på formatet.
Fire nøkler på rad i tabellen betyr ikke nødvendigvis en klynge. En klynge er
nøkler som har blitt forskjøvet fordi plassen deres var opptatt.
Ligger fire nøkler på rad, hver på sin egen hashverdi, er kostnaden 1 prøve per
innsetting. Ligger de på rad fordi alle fire hashet til den første plassen, er
kostnaden 10. Sluttilstanden ser lik ut; kostnaden gjør det ikke.
En tabell kan være under halvfull og likevel oppføre seg som verste tilfelle, hvis
alle nøklene hasher til samme plass.
Løsningen på det er ikke en større tabell — det ville gitt samme problem med mer
plass — men en bedre hashfunksjon. Skillet mellom «for lite plass» og «feil
fordeling» er et poeng en drøftingsoppgave gjerne ber om.
Et søk etter en nøkkel som ikke finnes, koster like mye som lengden på
probing-sekvensen fra til første tomme plass.
Derfor er klyngedannelse dyrt på to måter: den gjør innsettinger dyrere, og den
gjør mislykkede søk dyrere for enhver nøkkel som hasher inn i klyngen.
Telleren antall < N sikrer at søket stopper etter prøver, selv i en full
tabell der ingen plass er tom.
Uten den går probingen rundt i ring for alltid. Dette er et kanttilfelle sensor
gir eksplisitt delpoeng for å ha håndtert.
Insert slik den er skrevet i boka, forutsetter at minst én plass er tom.Er tabellen full og nøkkelen ikke finnes, terminerer ikke løkka.
Prosedyren er korrekt gitt sin prebetingelse. En robust implementasjon
rehasher før tabellen fylles opp, eller legger inn en teller som melder fra. Å
nevne dette er verdt et delpoeng.
Sensor krever at løsningen er lett forståelig, entydig og presis — ikke at den
ser ut som en bestemt fasit. En wraparound skrevet som if i == N: i = 0 er like
riktig som (i + 1) mod N.
Det motsatte gjelder også: en løsning som er teknisk riktig, men uklar, kan bli
ignorert. Felle #12 i bokas feilregister er nettopp uklar eller unødig lang
pseudokode.
algoritmen. 4) Oppgi kjøretiden som matcher koden, med definert.
Alle fire er poenggivende hver for seg. Riktig kode uten kjøretid gir typisk halv
uttelling — og for hashing må kjøretiden ha begge tilfellene, forventet og
verste.
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 Universitetet i Oslo. Dette er ikke offisielt studiemateriell. Les mer.