Av erfaring vet vi at søking kan utføres langt mer effektivt når vi har en datamengde som er sortert, jf. oppslag i ordbok eller leksikon som ikke er elektronisk. Det finnes mange forskjellige algoritmer (framgangsmåter) for sortering. Noen er enkle og lett forståelige. Andre er mer kompliserte å forstå, men desto mer effektive på store datamengder. Sorteringsmetoder blir studert inngående i et seinere kurs (Algoritmer og datastrukturer). Her skal vi nøye oss med å se på en enkel sorteringsmetode, slik at vi kjenner til en metode i tilfelle vi har behov for sortering.
De enkle, rett-fram-metodene har tilfredsstillende effektivitet når
datamengdene ikke er for store, det vil si opp til noen hundre elementer,
avhengig av hvor store elementene er. (Store dataelementer er det mer
tidkrevende å flytte.) Vi skal ta for oss den enkle
metoden som på engelsk kalles Selection Sort. På norsk kan vi kanskje kalle
den "sortering ved utvelging". Navnet kommer av at vi hele tida velger det
minste av de usorterte elementene og bytter dette elementet med det første
blant de usorterte. (Det er her forutsatt at vi skal sortere i voksende
rekkefølge.) Ved sortering generelt, blir de
størrelsene som sammenliknes for å avgjøre rekkefølge kalt for
sorteringsnøkler, eller kort og godt nøkler. (I tillegg
til tall, kan det for eksempel være String
-objekter.)
Et overordnet krav ved all sortering er for øvrig det vi kan kalle økonomisk bruk av tilgjengelig lagerplass. Ved array-sortering betyr dette at nødvendig permutasjon av elementer må finne sted i arrayen selv. Metoder som flytter elementer fra en gitt array over til en resultatarray er av mindre interesse. Dette med økonomisering av lagerplassen var særlig viktig tidligere, da datamaskinene hadde mye mindre kapasitet enn vi er vant med. Likevel brukes det fortsatt mest sorteringsmetoder som foretar all flytting innenfor den array som skal sorteres.
Sorteringsprinsipp (skal sortere n
elementer
d0, d1, ..., dn-1
):
d0
.n - 1
gjenværende elementene,
deretter med n - 2
elementer, etc. inntil bare ett element
— det største — er igjen.Det kan være instruktivt å se hvordan prosessen virker på et konkret sett av nøkkelverdier. I tabellen nedenfor er øverste rad de opprinnelige nøklene som skal sorteres. Hver ny rad viser rekkefølgen etter et nytt skritt i sorteringsprosessen. (I programmeringssammenheng vil det bety etter et nytt gjennomløp av en løkke.) Elementet med minst nøkkel i hvert skritt er gjengitt med uthevet skrift. Elementene som er ferdig sortert er gjengitt med skråskrift.
44 55 12 42 94 18 6 67 6 55 12 42 94 18 44 67 6 12 55 42 94 18 44 67 6 12 18 42 94 55 44 67 6 12 18 42 94 55 44 67 6 12 18 42 44 55 94 67 6 12 18 42 44 55 94 67 6 12 18 42 44 55 67 94
Dersom arrayen som inneholder elementene heter d
og det er
n
elementer som skal sorteres (fra starten av arrayen, i voksende
rekkefølge), kan vi i pseudokode formulere algoritmen slik:
for ( int i = 0; i < n - 1; i++ ) { < finn indeks min til den minste verdi blant d[i], ..., d[n-1] > < bytt om d[i] og d[min] > }
Algoritmen er implementert i
static
-metoden sorter
i klassen
Arraymetoder
som er gjengitt nedenfor.
Sorteringsmetoden mottar som parameter den arrayen som skal sorteres.
Klassen inneholder også en
static
-metode som skriver ut en array i et tekstområde.
Array og tekstområde mottas som parametre. Programmet
Sorteringstest.java
som er gjengitt etter klassen Arraymetoder
, tester sorteringsmetoden
på de samme data som er vist ovenfor.
Resultatet kan du se i følgende bilde av programmets utskrift.
1 import javax.swing.JTextArea; 2 3 public class Arraymetoder 4 { 5 //sorterer tabellen data ved bruk av algoritmen Selection Sort 6 public static void sorter(int[] data) 7 { 8 for (int i = 0; i < data.length - 1; i++) 9 { 10 int min = i; 11 //finner det minste element blant de usorterte 12 for (int j = i + 1; j < data.length; j++) 13 { 14 if (data[ j] < data[ min]) 15 { 16 min = j; 17 } 18 } 19 20 //bytter om elementer 21 int temp = data[ min]; 22 data[ min] = data[ i]; 23 data[ i] = temp; 24 } 25 } 26 27 //skriver ut array i tekstområde med 10 tall per linje 28 public static void skrivTabell(JTextArea utskrift, int[] tabell) 29 { 30 for (int i = 0; i < tabell.length; i++) 31 { 32 utskrift.append(tabell[ i] + " "); 33 if ((i + 1) % 10 == 0) 34 { 35 utskrift.append("\n"); 36 } 37 } 38 } 39 }
1 //Sorterer en tabell ved bruk av metoden Selection Sort. 2 //Skriver ut tabellinnhold før og etter sortering. 3 import javax.swing.JTextArea; 4 import javax.swing.JOptionPane; 5 6 public class Sorteringstest 7 { 8 public static void main( String[] args ) 9 { 10 JTextArea utskrift = new JTextArea(); 11 utskrift.setEditable( false ); 12 int[] tabell = { 44, 55, 12, 42, 94, 18, 6, 67 }; 13 utskrift.setText( "Tabell for sortering:\n" ); 14 Arraymetoder.skrivTabell( utskrift, tabell ); 15 16 //sorterer tabellen: 17 Arraymetoder.sorter( tabell ); 18 //skriver ut sortert tabell: 19 utskrift.append( "\n\nSortert tabell:\n" ); 20 Arraymetoder.skrivTabell( utskrift, tabell ); 21 JOptionPane.showMessageDialog( null, utskrift, "Sorteringstest", 22 JOptionPane.PLAIN_MESSAGE ); 23 } 24 }
Enkleste søkemetode i en array er sekvensiell søking: For å finne elementet som vi leter etter, besøker vi elementene etter tur i den rekkefølge de står i arrayen. I snitt må da halve arrayen gjennomsøkes før det søkte element blir funnet, eller eventuelt det kan fastslås at det ikke finnes. Sekvensiell søking er den mest ineffektive søkemetoden. Men dersom arrayen ikke er sortert, eller elementene ikke er plassert på en annen systematisk måte, er det ingen bedre måte å utføre søkingen på.
Dersom det søkte elementet ikke finnes, må søkemetoden returnere et passende signal for det, for eksempel verdien -1, siden -1 ikke kan være arrayindeks. En søkemetode for sekvensiell søking i en array av heltallsverdier kan derfor skrives slik:
int search( int[] kilde, int nøkkel ) { for ( int i = 0; i < kilde.length; i++ ) if ( kilde[ i ] == nøkkel ) return i; return -1; }
Legg merke til at dersom den søkte verdien blir funnet, vil metoden
returnere arrayindeksen for denne umiddelbart og avslutte søket. Verdien -1
vil bare bli returnert
i det tilfelle at metoden kommer gjennom hele for
-løkka uten å
ha funnet den søkte verdien.
I sorterte arrayer finnes det søkemetoder som er langt mer effektive enn sekvensiell søking og som derfor bør foretrekkes. En slik metode er binær søking, som vi nå skal se på. Som i tilfelle sortering, kaller vi nøkkelverdier, eller kort nøkler, de verdiene vi søker etter.
Forutsetning: sortert array.
Søkestrategi: Se først midt i arrayen. I tilfelle ikke funn: avgjør ved
sammenlikning av nøkkelverdier om søkingen skal fortsette i venstre eller
høyre del av arrayen. Benytt samme strategi om igjen på denne.
Søkestrategien er implementert i
static
-metoden binærsøk
som er gjengitt nedenfor. Metoden er tilføyd til klassen
Arraymetoder
som vi brukte i programeksemplet Sorteringstest
foran.
40 //Foretar binærsøking etter nøkkel i arrayen tabell. 41 //Returner indeksposisjon, eller -1 i tilfelle ikke funn. 42 public static int binærsøk( int[] tabell, int nøkkel ) 43 { 44 int venstre = 0, høyre = tabell.length - 1, midt; 45 while ( venstre <= høyre ) 46 { 47 midt = (venstre + høyre) / 2; //skal lete midt i tabellen 48 if ( tabell[ midt ] < nøkkel ) 49 venstre = midt + 1; //dropper venstre halvdel av tabellen 50 else if ( tabell[ midt ] > nøkkel ) 51 høyre = midt - 1; //dropper høyre halvdel av tabellen 52 else 53 return midt; //fant den. Returnerer indeks for funnsted og går ut 54 } 55 return -1; //nøkkel fantes ikke. Signaliserer dette ved retur av -1. 56 } 57 } 58
Vi skal teste metoden for binærsøking i programmet
BinarySearch.java
som er gjengitt nedenfor. (NB! Programmet er et hendelsesbasert
vindusprogram og forutsetter at du har gjort deg kjent med stoffet i
kapitlet Vindusbaserte programmer.)
Programmet oppretter en array som inneholder alle
partall fra og med 0 til og med 1000. Brukeren kan søke etter tall ved å skrive
tallet inn i et tekstfelt og trykke Retur-tast. Programmet svarer med å
skrive ut indeksposisjon for tallet i tilfelle det finnes i arrayen, eller melding om at tallet
ikke finnes. Bildet nedenfor viser programvinduet etter at det er søkt på
tallet 998. Driverklasse med main
-metode for programmet finnes
i fila
Searchtest.java
1 import java.awt.*; 2 import java.awt.event.*; 3 import javax.swing.*; 4 5 public class BinarySearch extends JFrame implements ActionListener 6 { 7 private JTextField input; 8 private JLabel output; 9 private int[] testliste; 10 public static final int ANTALL = 501; //arraystørrelse 11 12 public BinarySearch() 13 { 14 super("Test på binærsøking"); 15 Container c = getContentPane(); 16 c.setLayout(new FlowLayout()); 17 c.add(new JLabel( 18 "Søker i sortert array med alle partall fra 0 til 1000.")); 19 c.add(new JLabel("Dvs. partallet k befinner seg i posisjon k/2.")); 20 c.add(new JLabel("Velg tall det skal søkes etter: ")); 21 input = new JTextField(10); 22 c.add(input); 23 output = new JLabel(); 24 c.add(output); 25 genererListe(); 26 input.addActionListener(this); 27 } 28 29 private void genererListe() 30 { 31 testliste = new int[ANTALL]; 32 for (int i = 0; i < testliste.length; i++) 33 { 34 testliste[ i] = 2 * i; 35 } 36 } 37 38 public void actionPerformed(ActionEvent e) 39 { 40 int nøkkel = Integer.parseInt(input.getText()); 41 int pos = Arraymetoder.binærsøk(testliste, nøkkel); 42 if (pos >= 0) 43 { 44 output.setText(nøkkel + " finnes i posisjon " + pos); 45 } 46 else 47 { 48 output.setText(nøkkel + " finnes ikke."); 49 } 50 } 51 }
Ved binær søking halverer vi for hvert nytt skritt det antall elementer som
det søkes blant. Dersom vi i utgangspunktet skal søke blant n
elementer, vil derfor det maksimale antall halveringer vi kan foreta før vi
står igjen med en tom liste av elementer, være det minste tallet k
vi kan bruke slik at 2k
er lik eller større enn
n
. For eksempel har vi at 24 er lik 16. For 16
elementer trenger vi derfor maksimalt 4 "oppslag". Videre er 210
lik 1024. Når vi bruker binær søking på inntil 1024 elementer, trenger vi
derfor maksimalt 10 "oppslag" for å finne elementet, eller fastslå at det ikke
finnes. 220 er lik 1048576. Det betyr at vi maksimalt trenger
20 "oppslag" ved binær søking på en million elementer. Det må vi vel kunne
si er svært effektiv søking!
a) Metoden
public static void sorter( int[] data )
som er beskrevet ovenfor,
sorterer arrayen data
i voksende
rekkefølge. For at metoden
skal virke riktig, må imidlertid arrayen være full - det kan ikke være noen
ledige plasser. Du får som oppgave å modifisere metoden, slik at den også
virker riktig når det er ledige plasser i arrayen. Metoden må da få vite hvor
mange tall som er lagt inn i arrayen. Det kan den få vite via en ekstra
parameter. Du skal altså skrive en metode
public static void sorter( int[] data, int antall )
der antall
er antall tall som er lagt inn i
arrayen data
. (Vi forutsetter at eventuelle ledige plasser er på
slutten av arrayen.) Metoden skal sortere tallene
i voksende rekkefølge.
Metoden
public static int binærsøk( int[] tabell, int nøkkel )
som er beskrevet ovenfor, foretar binærsøking etter tallet
nøkkel
i arrayen
tabell
. (Arrayen må på forhånd være sortert.)
Metoden virker imidlertid riktig bare i det tilfelle at arrayen er full.
Du får som oppgave å modifisere metoden, slik at den også virker riktig når det
er ledige plasser på slutten av arrayen. Du skal altså skrive en metode
public static int binærsøk( int[] tabell, int nøkkel, int antall )
som foretar binærsøking i arrayen tabell
etter tallet nøkkel
. Antall tall i arrayen er lik
verdien til parameteren antall
.
Metoden skal returnere array-indeksen til
nøkkel
dersom tallet finnes i arrayen, -1 dersom
tallet ikke finnes.
Du skal nå gjøre bruk av metodene
sorter
og
binærsøk
som du skrev i oppgave 1 og 2. Det skal du
gjøre ved at du modifiserer programmet du skrev til
oppgave 4 i kapittel 7. Programmet gikk ut på å lese inn 20 forskjellige tall fra brukeren
og lagre dem i en array.
Tallene skulle være fra intervallet 20 til 100. For hvert innlest tall skulle det sjekkes om tallet
allerede fantes i arrayen. Dersom det ikke fantes, skulle det legges inn på
første ledige plass.
Du skal modifisere programmet ved at arrayen som inneholder de innleste
tallene hele tida holdes sortert i voksende rekkefølge. Det oppnår du ved at
du for hvert nytt tall som er lagt inn i arrayen gjør kall på metoden
sorter
, brukt på arrayen.
Du skal også modifisere programmet ved at metoden
finnes
skal bruke binærsøking for å sjekke
om det innleste tallet allerede finnes i arrayen. (Inne i
finnes
gjør du kall på metoden
binærsøk
for å sjekke om tallet
finnes i arrayen. Returverdien fra
binærsøk
bruker du for å finne ut om
returverdien fra finnes
skal være
true
eller
false
.) Dersom du programmerer riktig, vil du
se at når programmet skriver ut tallene som er lagret i arrayen, så vil de
alltid komme i voksende rekkefølge.
Copyright © Kjetil Grønning og Eva Hadler Vihovde, revidert 2014