Föreläsning 10
ALGORITMER MED VEKTORER: SÖKNING OCH REGISTRERING
Algoritmer med vektorer
Sökning: problemet
Vi skulle vilja ha en metod som söker upp ett givet tal i en vektor. Låt oss kalla den indexOf.
Om talet finns i vektorn vill vi ha reda på dess index.
Om talet inte finns får vi istället –1 (som aldrig är index) som svar.
Exempel:
int[] nbrs = new int[1000];
// vi låter nbrs få värden på något sätt
// på vilken plats ligger talet 4526?
int pos = indexOf(nbrs, 4526);
System.out.println("talet hittades på plats " + pos);
Sökning
Uppgift: Sök upp givet element i en följd av element.
Lösning: Gå igenom elementen i tur och ordning, kontrollera för varje element om det är det sökta. Avbryt om det sökta
elementet påträffats.
Algoritm (linjärsökning) i pseudokod:
i = "platsen för det första elementet"
while ("fler element kvar att söka igenom" &&
"elementet på plats i inte är det vi söker") { i = platsen för nästa element
}
Linjärsökning
/** Sök efter talet nbr i vektorn v. Om nbr finns returneras platsen för nbr, annars -1 */
public static int indexOf(int[] v, int nbr) { int i = 0;
while (i < v.length && v[i] != nbr) { i++;
}
if (i < v.length) { return i;
} else {
return -1;
} }
Att fundera på:
Varför måste delvillkoret i < v.length stå först? (Läs om short-circuit-evaluering, Think
Java Kap. 5, ”Logical Operators”.)
Att fundera på:
Varför kan man inte använda villkoret v[i] ==
nbr för att avgöra om man funnit talet nbr eller ej?
Linjärsökning, snarlik lösning. Men vad är detta?
/** Sök efter talet nbr i vektorn v. Om nbr finns returneras platsen för nbr, annars -1 */
public static int indexOf(int[] v, int nbr) { int i = 0;
while (i < v.length && v[i] != nbr) { i++;
}
return (i < v.length) ? i : -1;
}
Villkorsuttryck
Värdet av det logiska uttrycket anger vilket av de båda uttrycken som ska beräknas.
logiskt uttryck ? uttryck 1 : uttryck 2
Detta kan alltid skrivas om med if/else-satser.
Du ska emellertid förstå uttryck av detta slag när du ser dem.
Beräknas om det logiska uttrycket är falskt
Beräknas om det logiska uttrycket är sant
Linjärsökning, en variant till
Eftersom metoden inte ska göra något annat än att returnera ett index, så kan man göra det direkt om man vill.
/** Sök efter talet nbr i vektorn v. Om nbr finns returneras platsen för nbr, annars -1 */
public static int indexOf(int[] v, int nbr) { for (int i = 0; i < v.length; i++) {
if (v[i] == nbr) { return i;
} }
return -1;
}
…men varning för fel!
public static int indexOf(int[] v, int nbr) {
for (int i = 0; i < v.length; i++) { if (v[i] == nbr) {
return i;
} else {
return -1;
} } }
Varför fungerar inte det här? (Vanligt fel på tentor.)
Exempel:
klass för att representera en användare för passerkort
/** Skapar en ny användare med givet kortnummer och namn. */
User(int cardNbr, String name);
/** Hämtar användarens kortnummer. */
int getCardNbr();
/** Hämtar användarens namn. */
String getName();
User
Linjärsökning bland objekt
User[] users = new User[100];
... // Satser för att skapa 100 User-objekt
Uppgift: Sök efter användaren med kortnummer 64319 och skriv ut användarens namn.
int i = 0;
while (i < 100 && users[i].getCardNbr() != 64319) { i++;
}
if (i < 100) {
System.out.println(users[i].getName());
}
Varför fungerar inte detta?
User[] users = new User[100];
... // Satser för att skapa 100 User-objekt
Uppgift: Sök efter användaren med kortnummer 64319 och skriv ut användarens namn.
int i = 0;
while (i < 100 && ) { i++;
}
if (i < 100) {
System.out.println(users[i].getName());
}
users[i] != 64319
Andra sökalgoritmer
Linjärsökning är enkelt,
men inte alltid mest effektivt.
I en sorterad mängd är binärsökning effektivare.
Hur gör du själv när du söker upp ett tal eller ett ord i en sorterad mängd?
Binärsökning: idén
v 2 9 22 56 61 72 76 97
v 2 9 22 31 42 56 61 72 76 97
Idé: välj det mittersta av de återstående elementen. Antingen hittar vi rätt element, eller så kan vi utesluta hälften.
Exempel: Sök upp talet 61 i vektorn v.
v 2 9 22 31 42 56 61 72 76 97
v 2 9 22 31 42 56 61 72 76 97
31 42
?
?
?
!
Binärsökning: formulera algoritmen
v 2 9 22 56 61 72 76 97
mid=4
mid=5
low=5 high=6
v 2 9 22 31 42 56 61 72 76 97
Sök upp talet 61 i vektorn v.
low=0 high=9
v 2 9 22 31 42 56 61 72 76 97
low=5 mid=7 high=9
mid=6
low=6 high=6
v 2 9 22 31 42 56 61 72 76 97
Sätt mid = (low + high) / 2 Är v[mid] == 61? Nej, 42 42 < 61, så sätt low = mid + 1
Sätt mid = (low + high) / 2 Är v[mid] == 61? Nej, 72 72 > 61, så sätt high = mid – 1
Sätt mid = (low + high) / 2 Är v[mid] == 61? Nej, 56 56 < 61, så sätt low = mid + 1
Sätt mid = (low + high) / 2 Är v[mid] == 61? Ja!
31 42
Binärsökning: Java-kod
Metoden söker i int-vektorn v efter talet nbr.
Returnerar indexet om nbr hittas, annars -1.
public static int binarySearch(int[] v, int nbr) { int low = 0; // undre gräns
int high = v.length - 1; // övre gräns while (low <= high) {
int mid = (low + high) / 2; // mittpunkt if (v[mid] == nbr) {
return mid;
} else if (v[mid] < nbr) { low = mid + 1;
} else {
high = mid - 1;
} }
return -1;
}
Hur lång tid tar sökningen?
Anta att vi söker bland N element.
• Linjärsökning: t ~ N
• Binärsökning: t ~ log N
På den dubbla tiden kan vi
• linjärsöka 2N element
• binärsöka N2 element
(Tecknet ~ betyder här ”proportionellt mot”)
N
t k1 · N
k2 · log N
Registrering
Registrering
Uppgift: Räkna antal element av olika slag.
Lösning: Använd en vektor av typ int[] för att lagra de olika antalen.
Exempel: Räkna mynt.
Vi behöver en låda för enkronor, en för 5-kronor och en för 10-kronor:
int[] nbrCoins = new int[3];
enkronor 5-kronor 10-kronor
Exempel: räkna olika tärningsslag
Programmet på nästa sida räknar antalet 6:or som en tärning slår.
Hur kan man ändra detta till att samla statistik för alla
tärningens utfall, det vill säga räkna hur många 1:or, 2:or, 3:or, 4:or, 5:or och 6:or som slås?
Räkna sexor
(jämför gärna med exemplet på föreläsning 7)
public class DieTest {
public static void main(String[] args) { Die d = new Die();
Scanner scan = new Scanner(System.in);
System.out.println("Antal tärningskast: ");
int nbr = scan.nextInt();
int nbr6 = 0;
for (int i = 0; i < nbr; i++) { d.roll();
if (d.getResult() == 6) { nbr6++;
} }
System.out.println("Det blev en 6:a i " +
((double)nbr6/nbr * 100) + "% av fallen");
} }
Hur spara samtliga tärningsresultat?
int[] res = new int[6];
for (int i = 0; i < nbr; i++) { d.roll();
int a = d.getResult();
res[a - 1]++;
}
[0] [1] [2] [3] [4] [5]
res 16454 16512 16786 16725 16731 16792
antal 1:or antal 2:or antal 6:or
Exempel: registrera studenters poäng på prov
antag att vi har en klass Student:
Student
(Klassen Student innehåller säkert även en konstruktor och andra metoder – vi bortser här från dessa.)
/** Tar reda på studentens poäng på provet. */
int getPoints();
Exempel: registrera studenters poäng på prov
public class Test {
private Student[] students; // studenterna
private int n; // antalet studenter /** Skapar ett prov med plats för max studenter. */
public Test(int max) {
students = new Student[max];
n = 0;
}
/** Lägger till studenten s. */
public void add(Student s) { students[n] = s;
n++;
}
/** Skriver ut antalet studenter som har 0,1,...,50 poäng på provet */
public void printStatistics() { ... } }
Exempel: registrera studenters poäng på prov
Antag att vi vill registrera hur många som hade 0, hur många som hade 1, hur många som hade 2 poäng etc. etc. Maxpoäng är 50.
public void printStatistics() { int[] nbrs = new int[51];
for (int i = 0; i < n; i++) {
int index = students[i].getPoints();
nbrs[index]++;
}
System.out.println("Poäng\tAntal studenter ");
for (int i = 0; i < nbrs.length; i++) { System.out.println(i + "\t " + nbrs[i]);
} }
Exempel: registrera studenters poäng på prov
Antag att vi vill registrera hur många som hade 0-9 poäng, 10-19 poäng, 20-29 poäng, 30-39 poäng och 40-50 poäng. Detta är ett (nästan) regelbundet
intervall och kan därför lösas relativt enkelt.
public void printStatistics() { int[] nbrs = new int[5];
for (int i = 0; i < n; i++) {
int index = students[i].getPoints() / 10;
if (index == 5) { // specialfall: om 50 poäng index = 4;
}
nbrs[index]++;
}
// ... skriv ut antalen }
Exempel: registrera studenters poäng på prov
Tänk istället att 0-24 poäng ger U, 25-34 poäng betyg 3, 35-42 poäng betyg 4 och 43-50 poäng betyg 5. Då har vi ett oregelbundet intervall som vi får hantera
annorlunda.
Antalet U-betyg registreras i nbrs[0], antalet 3-betyg i nbrs[1], osv.
int[] nbrs = new int[4]; // plats för U, 3, 4, 5 //För varje student
int points = students[i].getPoints();
int index;
if (points < 25) { index = 0;
} else if (points < 35) { index = 1;
} else if (points < 43) { index = 2;
} else {
index = 3;
}
nbrs[index]++;