7 januari 2000
Numerisk analys och datalogi
Mikael Goldmann
Laborationer i
Algoritmer och komplexitet för SU
våren 2000
Laborationerna är obligatoriska. Ni får arbeta i två-personsgrupper eller ensamma. Om ni
arbetar i grupp måste ni redovisa tillsammans och båda ska behärska lösningen till fullo.
Läs igenom avsnittet Hederskodex i kursinformationen.
Det finns två lydelser för Laborationsuppgift 2 och ni väljer själva vilken av dem ni vill lösa.
Om du redovisar labben senast angivet bonusdatum får du en bonuspoäng på tentan. Vid
redovisningen har du dessutom möjlighet att redogöra för lösningen av labbens teoriuppgifter. Korrekt lösning av dessa ger två bonuspoäng på tentan. Det är frivilligt att redovisa
teoriuppgifterna, men för att klara av att göra labben bör du ha gjort dom. De kan bara redovisas tillsammans med labben, och senast vid angivet bonusdatum. Sen redovisning ger
ingen bonus för labb eller teoriuppgifter. Vid redovisningen sparar vi en kopia av programmet i ett arkiv med labblösningar.
Namn: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Personnummer: . . . . . . . . . . . . . . . . . . . . . . .
Godkänd labb 1: . . . . . . . . . . . . . . . . . . . . . . . . . .
Kvitteras: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Poäng teori 1: . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Kvitteras: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Godkänd labb 2: . . . . . . . . . . . . . . . . . . . . . . . . . .
Kvitteras: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Poäng teori 2: . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
Kvitteras: . . . . . . . . . . . . . . . . . . . . . . . . . . . . . .
1
2
Laboration 1 i Algoritmer och komplexitet, våren 2000
Bonusdatum: 2000–02–15
Flöden och matchningar
Du ska skriva ett program som får en bipartit graf som indata och producerar en matchning av maximal storlek som utdata. Din algoritm ska reducera problemet till ett flödesproblem och lösa detta med Ford-Fulkersons metod där den kortaste stigen i restflödesgrafen
hittas med breddenförstsökning (alltså Edmonds-Karps algoritm). Du får själv välja programspråk.
Indata
Programmet ska läsa från standard input. Grafen G = (X, Y , E) representeras som |E| + 3
rader.
Den första raden består av ett heltal som anger antalet hörn i X.
Den andra raden består av ett heltal som anger antalet hörn i Y .
Den tredje raden består av ett tal som anger |E|, det vill säga antalet kanter i grafen.
De följande |E| raderna består var och en av två heltal som svarar mot en kant.
Hörnen numreras från 1 och uppåt. Om man angett a hörn i X och b hörn i Y så låter vi
X = {1, 2, . . . , a} och Y = {a + 1, a + 2, . . . , a + b}. En kant anges med ändpunkterna (först
X-hörnet och sedan Y -hörnet).
Exempel: en graf kan till exempel kodas så här.
2
3
4
1
1
2
2
3
4
3
5
Denna graf har alltså X = {1, 2} och Y = {3, 4, 5}. Kantmängden E innehåller kanterna (1, 3),
(1, 4), (2, 3) och (2, 5).
Utdata
Ditt program presenterar den funna matchningen genom att mata ut tre tal och sedan ett
antal kanter på standard output. Först skrivs två rader som är desamma som de två första i
indata, och därefter en rad med ett heltal som anger antalet kanter i den funna matchningen.
Därefter skrivs en rad för varje kant som ingår i matchningen. Kanten beskrivs av ett talpar
på samma sätt som i indata.
Exempel: om vi har grafen ovan som indata så kan utdata se ut så här.
2
3
2
1 3
2 5
Programmet bipmatch på katalogen /info/sualko00/labb1 löser den givna labbuppgiften. Du kan alltså köra det för att få ett facit att jämföra med, men tänk på att det kan
finnas mer än en matchning av maximal storlek.
3
Krav
Ditt program ska hitta matchningar av maximal storlek. Dessutom ska det vara effektivt.
Prova ditt program på grafer grafer med (1000 + 1000) hörn och ca 2500 kanter. Det får inte
vara mer än 5 gånger så långsamt som /info/sualko00/labb1/bipmatch.
Testning
I katalogen /info/sualko00/labb1/ ligger programmen grafgen, bipmatch och matchtest som du kan köra för att testa ditt program.
Programmet grafgen genererar en slumpvis vald bipartit graf. Grafen skrivs på standard
output på ovan angivet format för indata till matchningsprogrammet.
/info/sualko00/labb1/grafgen nx ny p
ger en graf med nx hörn i X, ny hörn i Y och varje tänkbar kant finns med sannolikhet p.
nx och ny ska vara positiva heltal och p ska vara ett flyttal mellan 0.0 och 1.0. Om du ger
kommandot
/info/sualko00/labb1/grafgen 100 200 0.05
får du alltså en graf med 100 X-hörn, 200 Y -hörn och cirka 100 · 200 · 0.05 = 1000 kanter.
Programmet matchtest läser en graf följt av utdata från ett matchningsprogram (alltså,
först grafen och sedan matchningen) och kontrollerar att matchningen är maximalt stor.
Utdata skrivs på stdout och kan vara Matchning av maximal storlek, Matchning av mindre än
maximal storlek eller Ingen matchning.
Så här kan du använda grafgen och matchtest för att testa minlabb.
orange51> /info/sualko00/labb1/grafgen 200 200 0.1 > graffil
orange51> minlabb < graffil > matchfil
orange51> cat graffil matchfil | /info/sualko00/labb1/matchtest
Om du inte vet vad tecknen >, < och | betyder i exemplet ovan så fråga en handledare
eller din lärare på kursen.
Teoriuppgifter
1. Jämför tidskomplexiteten för algoritmen då grafen implementeras som en grannmatris
och då den implementeras med grannlistor. (För att satsen
f[v,u] = -f[u,v];
ska kunna implementeras effektivt måste grannlisteimplementationen utökas så att
varje kant har en pekare till den omvända kanten.)
Uttryck tidskomplexiteten i n och m där n är totala antalet hörn och m antalet kanter
i den bipartita grafen. Välj sedan den implementation som är snabbast då m = O(n),
alltså då grafen är gles.
2. Kalle menar att om vi börjar med en bipartit graf G och gör om den till en flödesgraf H
med ny källa s och nytt utlopp t så kommer avståndet från s till t att vara 3.
Kalle tycker därför att BFS-steget alltid kommer att hitta en stig från av längd 3 i restflödesgrafen (om det finns någon stig).
Det första påståendet är sant, men inte det andra. Varför har stigarna som BFS hittar i
restflödesgrafen inte nödvändigtvtis längd 3? Hur långa kan de bli?
3. Anledningen till att bipartit matchning kan reduceras till flöde är att en lösning till
flödesproblemet kan tolkas som en lösning till matchningsproblemet. Detta gäller bara
om det flöde som algoritmen ger är ett heltalsflöde (flödet i varje kant är ett heltal),
vilket i detta fall innebär att flödet längs en kant antingen är 0 eller 1. Som tur är så är
det på det sättet. Bevisa att Ford-Fulkerson alltid genererar heltalsflöden om kanterna
har heltalskapaciteter.
4
Laboration 2 i Algoritmer och komplexitet, våren 2000
Bonusdatum: 2000–03–14
Alternativ 1: Multiplikation av stora heltal
Du ska skriva ett program som får två positiva heltal givna som hexadecimala tal och
beräknar deras produkt.
Din algoritm ska multiplicera heltalen med någon effektiv metod (Karatsuba eller FFT)
och skriva resultatet som en sträng av hexadecimala siffror på standard output. Du får själv
välja programspråk.
Du får utgå ifrån att de två talen innehåller lika många siffror.
Indata
Programmet ska läsa från två filer. Varje fil innehåller en textsträng (en enda rad utan ’\n’)
med tecknen ’0’..’9’,’a’..’f’. Strängen kodar ett hexadecimalt tal. Ditt program ska
ta två argument på kommandoraden. Dessa är namnen på de två filerna där talen som ska
multipliceras ligger.
Utdata
Ditt program ska skriva resultatet av multiplikationen på standard output. Produkten skrivs
hexadecimalt på en enda rad utan ’\n’.
minmult talfil1 talfil2 > prod
innebär alltså att ditt program ska läsa ett tal från filen talfil1, ett tal från filen talfil2 och
sedan dirigeras resultatet till filen prod.
Krav
Algoritmen ska vara effektivare än skolboks-metoden, dvs multiplicera n-siffriga tal i tid
o(n2 ). Dessutom ska ditt program vara snabbare än programmet skolbok som ligger i katalogen /info/sualko00/labb2/multiplikation/. Varning: detta kan vara svårt att klara
med ett Java-program, men det går.
Du får inte använda färdiga programpaket för hantering av stora heltal.
Testning
I katalogen /info/sualko00/labb2/multiplikation/ ligger programmen talgen, mult,
skolbok och lika som du kan köra för att testa ditt program.
Programmet talgen genererar ett slumpvis hexadecimalt tal som skrivs på standard output.
/info/sualko00/labb2/multiplikation/talgen n
ger ett n-siffrigt hexadecimalt tal.
Programmen skolbok och mult multiplicerar två tal som ligger på filer och skriver resultatet
på standard output. skolbok använder skolboksmetoden och har alltså kvadratisk komplexitet, medan mult är skrivet med hjälp av gnu-projektets heltalspaket och mycket snabbt. Du
ska tävla mot skolbok, men mult kan vara bra att ha vid testningen.
Programmet lika jämför två filer och talar om ifall de är lika eller ej.
Så här kan du använda talgen och mult för att testa minmult.
orange51> /info/sualko00/labb2/multiplikation/talgen 10000 > tal1
orange51> /info/sualko00/labb2/multiplikation/talgen 10000 > tal2
5
orange51> /minmult tal1 tal2 > prod
orange51> /info/sualko00/labb2/multiplikation/mult tal1 tal2 > facit
orange51> /info/sualko00/labb2/multiplikation/lika prod facit
lika ska inte hitta olikheter i filerna. Kontrollera också att programmet gör rätt när faktorerna bara innehåller siffran f. Pröva till exempel att multlipicera tal som består av 128 f
med varandra.
Tips
Studera källkoden till skolbok.c som ligger på kursbiblioteket så får du en del tips om hur
du kan hantera inläsning och utmatning. Du får kopiera fritt ur detta program, men ange
varifrån du fått koden med kommentarer.
Det finns ett kort kompendium som beskriver snabb multiplikation av heltal. Läs det
innan du sätter igång. Kompendiet kommer att delas ut inför laborationen.
När programmet fungerar kan du genom att ge kompilatorn lämpliga väljare (till exempel
gcc -O4) få optimerad kod. Ofta gör detta stor skillnad! Vid utveckling däremot kan det vara
bra att genererera information för avlusaren (till exempel gcc -g).
Teoriuppgifter
1. Karatsubas algoritm multiplicerar två n-siffriga tal med 3 rekursiva multiplikarioner
av n/2-siffriga tal. Anta att du lyckas multiplicera två n-siffriga tal med k rekursiva
multiplikarioner av n/3-siffriga tal. Vad är det största värde k kan ha om din algoritm
ska vara snabbare än Karatsubas?
2. Ludvig har implementerat Karatsubas algoritm för att multiplicera heltal. Han lagrar
ett stort heltal som en vektor av integer, där varje integer svarar mot en hexadecimal
siffra. Tyvärr tar programmet dubbelt så lång tid som skolboksmetoden på 30000siffriga tal. Karina föreslår att Ludvig ska låta varje integer i vektorn innehålla mer än
en hexadecimal siffra. Ludvig provar med två hex-siffror per integer i vektorn. Ungefär
hur mycket snabbare blir hans program? Varför? Hur många hex-siffror ska han ha i
varje integer för att klara labbkraven? (Försumma tid för läsning och skrivning i dina
beräkningar).
3. För att få en effektiv implementation av Karatsubas algoritm är det viktigt att man inte
hela tiden anropar malloc() och free() (eller new i Java). Ett alternativ är att börja med
att allokera en stor minnesarea och använda delar av den i de rekursiva anropen. Hur
stor bör den vara och hur kan man utnyttja den? (När du anger storlek, uttryck den i n
som är antalet siffror i faktorerna.)
6
Laboration 2 i Algoritmer och komplexitet, våren 2000
Bonusdatum: 2000–03–14
Alternativ 2: Konkordans
En konkordans är en databas där man kan slå upp ord och då få se alla förekomster av
ordet tillsammans med orden närmast före och närmast efter i texten. Detta är ett stort
hjälpmedel för lingvister som vill undersöka hur olika ord används i språket.
I denna uppgift ska du skriva ett program som givet en text skapar en konkordansdatabas och ett program som frågar användaren efter ord, slår upp ordet och presenterar alla
förekomster av ordet i sitt sammanhang. Det är viktigt att varje sökning går mycket snabbt
så det gäller att det första programmet lagrar konkordansen på ett sådant sätt att det går
snabbt att göra en sökning.
Exempel på körning av sökprogrammet:
orange51> java Konkordans så
Det finns 5 förekomster av ordet.
rje sökning går mycket snabbt så det gäller att det första pr
ste vara någorlunda effektivt så att det kan skapa konkordanse
(mindre än fem minuter eller så). Använd gärna färdiga Unixve
et stor. Hur bör denna lagras så att den tar liten plats men ä
ra är en labb som inte ska ta så lång tid att göra. 3.
orange51>
Krav
Följande krav ställs på din lösning:
Konkordansen ska inte skilja på stora och små bokstäver. Användaren ska alltså kunna
skriva in alla sökfrågor med små bokstäver.
Konstruktionsprogrammet behöver inte vara jättesnabbt eftersom det bara ska köras
en gång, men det måste vara någorlunda effektivt så att det kan skapa konkordansen
på rimlig tid. Det får inte ta mer än fem minuter att skapa konkordansen på en Sun
Ultra-1.
I presentationen ska en förekomst av ordet presenteras på varje rad med till exempel
30 tecken före och 30 tecken efter. Om det finns fler än ungefär en skärmsida med förekomster bör programmet fråga användaren om hon vill ha förekomsterna utskrivna.
Man ska kunna söka efter ett ord, till exempel bil genom att i terminalfönstret ge kommandot konkordans bil (Om du använt C, Pascal eller liknande) eller java Konkordans bil (om du använt Java).
På en Sun Ultra-1 ska man få svaret inom ett par sekunder.
Sökprogrammet får inte använda speciellt mycket internminne. Internminnesbehovet
får inte växa snabbare än logaritmen för antalet distinkta ord i den ursprungliga texten.
7
Tips
Texten, som ligger på /info/sualko00/labb2/konkordans/korpus, är en stor fil och ska
inte i sin helhet läsas in i internminnet under sökningen. Istället bör sökprogrammet öppna
filen och hoppa till dom avsnitt som ska presenteras med seek (använd till exempel fseek i
stdio.h i C eller seek i java.io.RandomAccessFile i Java).
Ta ingen kopia av textfilen utan låt sökprogrammet använda ursprungstextfilen på kurskatalogen.
Om indexfilerna inte får plats på din skivminnesarea kan du skapa dom på temporärarean
/var/tmp eller /tmp och ta bort dom när du är klar.
Konstruktionsprogrammet måste skapa något slags index som talar om för varje ord på
vilka positioner i texten det förekommer. Detta index blir av samma storleksordning som
texten och sökprogrammet ska därför inte heller läsa in hela indexet. Låt det ligga på en fil
(eller flera filer) och positionera med hjälp av seek även i denna fil.
Använd gärna färdiga Unixverktyg som sort vid konstruktionen. En enkel tokeniserare
(ett program som läser en text och plockar ut dom enskilda orden samt deras position i
texten) finns på /info/sualko00/labb2/konkordans/tokenizer.c.
Du kan använda ett shell-skript för att starta flera program (t.ex. tokenizer och sort) när du
konstruerar konkordansen.
Java är numera ganska snabbt, ofta bara 3-5 gånger långsammare än C, men just vid
filhantering är det viktigt att man är noggrann när man använder Java. När du skapar konkordansen kommer du troligen att vilja skriva många gånger på en eller flera filer. Se till att
de strömmar du konstruerar för skrivning (och läsning) är buffrade (läsning och skrivning
på en RandomAccessFile kan inte buffras).
Om du inte vet hur man kommer åt argumentet till ett program, till exempel ordet så i
kommandot
java Konkordans så
så fråga handledare eller lärare på kursen.
Teoriuppgifter
1. Indexinformationen för ett ord (det vill säga i vilka teckenpositioner ordet förekommer
i den stora texten) kan bli mycket stor. Hur bör denna lagras så att den tar liten plats
men ändå kan läsas enkelt? Bör indexinformationen lagras tillsammans med själva ordet eller på ett separat ställe?
2. Diskutera för- och nackdelar med olika implementationer av konkordansen med avseende på följande egenskaper:
snabbhet (antal filläsningar och filpositioneringar per sökning),
utrymme på skivminnet,
utrymme i primärminnet,
enkelhet att konstruera och lagra på fil.
Ta åtminstone upp följande datastrukturer:
binärt sökträd,
sorterad array,
hashtabell,
trie (träd där varje nivå motsvarar en bokstav i ordet),
latmanshashning (trie implementerad som array för dom första bokstäverna i ordet och sorterad array för resten av bokstäverna).
8
Välj sedan den implementation som du anser vara bäst med tanke på ovanstående krav
och att detta bara är en labb som inte ska ta så lång tid att göra.
3. När du gjort din implementation, bestäm dess faktiska egenskaper (snabbhet, skivminnesbehov, primärminnesbehov och enkelhet) och jämför med din uppskattning i
uppgift 2. Om skillnaden mellan din uppskattning och verkligheten är stor, förklara
vad skillnaden beror på.
9