Når Merge sort og Insertion sort samarbeider Lars Sydnes 8. november 2014 1 Innledning Her skal vi undersøke to algoritmer som brukes til å sortere lister, Merge sort og Insertion sort. Det at Merge sort har linearitmisk kjøretid og Insertion sort har kvadratisk kjøretid betyr at Merge sort generelt sett er raskere enn Insertion sort, når vi arbeider med tilstrekkelig store lister. Kjøretidsestimatene kan ikke svare på følgende spørsmål: Hvor store må listene være for at Merge sort skal være raskere enn Insertion sort? Hvilken algoritme er raskest for små lister? Vi må altså være åpne for at Insertion sort kan sortere små lister raskere enn Merge sort, hvis vi ikke vet bedre. Vi skal undersøke en implementasjon av Merge sort som tar hensyn til at Insertion sort kan være raskere når listene er små. Algoritmen velger hvilken strategi som skal brukes ved å sammenligne antallet elementer som skal sorteres med konstanten int insertionsortthreshold. Når antallet elementer som skal sorteres er mindre enn insertionsortthreshold sorteres elementene med Insertion sort. Når antallet elementer som skal sorteres er høyere, sorteres elementene ved Merge sort, det vil si at listen deles i to deler som sorteres hver for seg før de flettes sammen til en sortert liste. Den implementasjonen vi ser på er rekursiv. Det betyr at de to delene som skal sorteres hver for seg blir sortert med den samme metoden. Det spørsmålet vi ønsker å få svar på her er: Hvilken verdi for insertionsortthreshold gir den mest effektive sorteringsalgoritmen? 2 Eksperimentet Vi målte kjøretid og antall kall av compareto for sorterte lister av typen 1, 2, 3,..., n og usorterte lister uten duplikater av størrelse 1000, 2000, 3000,..., 200 000, 1
med ulike verdier av insertionsortthreshold. Vi undersøkte verdier av insertionsortthreshold i intervallet fra 2 til 50. Målingene ble gjort i tilfeldig rekkefølge. 3 Resultater 3.1 Usorterte lister Kjøretid i sekunder 0.0190 0.0200 0.0210 10 20 30 40 50 InsertionSortThreshold Figur 1: Gjennomsnittlig kjøretid, målt for usorterte lister. Figur 1 viser gjennomsnittlig kjøretid for ulike verdier av insertionsortthreshold. Det ser ut til at kjøretiden først avtar, for så å øke. Hvilken verdi av insertionsortthreshold som gir kortest kjøretid er vanskelig å si, men det later til at den befinne seg mellom 5 og 20. Hvis vi ser på gjennomsnittlig antall kall av compareto, er bildet enda tydeligere. Figur 2 viser tydelig at antall kall av compareto er lavest når insertionsortthreshold er lik 6. 3.2 Sorterte lister Figur 3 og 4 viser at kjøretiden avtar når insetionsortthreshold øker. Målingene av gjennomsnittlig antall kall av compareto viser noe interessant: Gjennomsnittlig antall kall av compareto er i alle tilfellene lik 5024900. 2
Antall sammenligninger 1600000 1660000 5 10 15 InsertionSortThreshold Figur 2: Gjennomsnittlig antall kall av compareto, målt for usorterte lister. Kjøretid i sekunder 3e 04 6e 04 9e 04 5 10 15 InsertionSortThreshold Figur 3: Gjennomsnittlig kjøretid, målt for sorterte lister 3
Kjøretid i sekunder 4e 04 8e 04 0 50 100 150 200 InsertionSortThreshold Figur 4: Gjennomsnittlig kjøretid, målt for sorterte lister 3.3 Antall kall av compareto som kostnadsmodell De to foregående avsnittene belyser forholdet mellom antall kall av compareto og kjøretiden. Figur 5 viser sammenhengen mellom kjøretiden og antall kall av compareto i eksperimentene. 4
Figur 5: Plott av kjøretid mot antall kall av compareto, for usorterte lister. Hvert datapunkt svarer til én måling. 5
4 Diskusjon 4.1 insertionsortthreshold Vi kan oppsummere måleresultatene slik: For usorterte lister bør insertionsortthreshold har en lav verdi. For sorterte lister bør insertionsortthreshold har en høy verdi. Det betyr: Hvis vi velger en lav verdi av insertionsortthreshold, så blir vi straffet når vi møter en ferdigsortert liste. Hvis vi velger en høy verdi av insertionsortthreshold, så blir vi straffet når vi møter en usortert liste. En brukbar almenn sorteringsalgoritme må altså finne et brukbart balansepunkt. La oss se på hvordan vi kan finne et slikt balansepunkt. Figur 3 og 4 forteller oss at vi neppe bør la insertionsortthreshold være mindre enn 5; da blir kjøretiden dramatisk forverret når vi sorterer lister som allerede er sortert. På den annen side forteller figur 1 at insertionsortthreshold ikke bør overstige 30. I motsatt fall vil kjøretiden bli forverret når vi sorterer usorterte lister. I en optimal versjon av denne algoritmen vil altså 5 < insertionsortthreshold < 30. Dersom vi fokuserer på antallet kall av compareto, så kan figur 2 gi oss enda klarere svar, nemlig at den optimale verdien kan ligge mellom 5 og 10. Det er dog ikke klart at antall kall av compareto er noe vi bør legge vekt på. 4.2 Antall kall av compareto som kostnadsmodell Figur 5, som viser målinger for usorterte lister, viser noe som er i nærheten av en lineær sammenheng mellom minimal kjøretid og antall kall av compareto. Det later til å være en tilsvarende sammenheng mellom maksimal forventet kjøretid og antall kall av compareto. Slik sett, kan vi si at antall kall av compareto fungerer som en god kostnadsmodell. Dette understrekes av at figur 1 og figur 2 gir omtrent samme inntrykk, selv om figur 2 viser mye mer regelmessige målinger. Situasjonen er mer komplisert når vi arbeider med ferdig sorterte lister. Her er gjennomsnittlig antall kall av compareto uavhengig av 6
insertionsortthreshold. Forklaringen på dette er meget enkel: Når listen er sortert ender sorteringsalgoritmen opp med å sammenligne alle naboelementer 1 gang. I en liste med n elementer vil det altså foregå n 1 sammenligninger. Når vi bruker Insertion sort er dette alt som skjer. Når vi bruker Merge sort vil det i tillegg foregå en hel del unødvendig kopiering: Hvis insertionsortthreshold= k og listen har n elementer, vil det foregå log 2 (n/k) kopieringer, som hver for seg har lineær kjøretid. Utifra denne analysen skal kjøretiden T (n) A n + B n log 2 (n/k), der det første leddet stammer fra compareto, mens det andre leddet stammer fra kopieringen. Dette forklarer hvorfor antall kall av compareto er uavhengig av insertionsortthreshold og kjøretiden avtar når insertionsortthreshold øker. Til tross for denne detaljerte diskusjonen er konklusjonen meget enkel: Antall kall av compareto er konstant, og er derfor en ubrukelig kostnadsmodell når vi skal sammenligne ulike verdier av insertionsortthreshold for sorterte lister. Dette betyr dog ikke at denne kostnadsmodellen generelt sett er ubrukelig. Det hele avhenger av hvilket spørsmål man stiller. 5 Begrensninger Her vil jeg nevne noen innvendinger jeg har mot denne undersøkelsen. Disse innvendingene kan ganske enkelt oppsummeres ved å peke på at følgende spørsmål henger i løse luften. Hva vil det si at en sorteringsalgoritme er en optimal almenn sorteringsalgoritme? Vi kan peke på følgende konkrete innvendinger: Undersøkelsen tok kun for seg sorterte og usorterte lister. Det er vanskelig å si hvordan det står til med delvis sorterte lister og lister man møter i det virkelige Liv Undersøkelsen tok kun for seg lister med størrelse 1000, 200 000. Kan det tenkes at konklusjonene blir annerledes om vi undersøker større eller mindre lister? Undersøkelsen tok kun for seg lister av Integer-objekter. comparetometoden til disse objektene er serdeles rask. Dersom man gjorde en tilsvarende undersøkelse for String-objekter, ville kjøretiden til compareto antageligvis få større betydning. Kjøretiden knyttet til kopiering vil dog være tilnærmet uforandret i og med at det kun innebærer å flytte referanser til objekter. Vi har ikke funnet den optimale insertionsortthreshold. Til gjengjeld vet vi en hel del mer om dette spørsmålet. Det som står fast er at lave verdier av 7
insertionsortthreshold straffer seg når listen man sorterer en ferdigsortert liste, og at høye verdier av insertionsortthreshold straffer seg når listen ikke er sortert fra før. Det er denne balansegangen man må beherske. 8