Träd
Träd är en icke linjär struktur. Ett träd består av en huvudnod (roten) som har noll eller flera subträd. En nod som saknar barn kallas löv. En gren är en serie noder förbundna med varandra.
Binära träd
Ett binärt träd är ett träd där varje nod har högst två barn.
Strikt binärt träd
Ett strikt binärt träd är ett träd där varje nod har noll eller två barn.
Binärt sökträd
Ala värden som finns i vänster subträd är mindre än värdet som finns i föräldern. Alla värden som finns i höger subträd är större än värdet som finns i föräldern.
Dubbletter tillåts inte!
Balanserade binära sökträd
Ett binärt träd är balanserat om det för varje nod i trädet gäller att höjdskillnaden mellan dess båda subträd är högst ett.
Traversering
Att genomsöka alla noderna i ett träd kallas att traversera trädet. Detta kan göras genom post-, pre- eller inordertraversering.
Preorder: Först roten, sedan vänster subträd i preorder, därefter höger subträd i preorder. M, H, A, K, S, N, T
Inorder: först vänster subträd i inorder, sedan roten och därefter höger subträd i inorder (detta blir växande ordning i ett binärt sökträd). A, H, K, M, N, S, T
Postorder: först vänster subträd i postorder, sedan höger subträd i postorder och därefter roten. A, K, H, N, T, S, M
Interfacet Set
En mängd (Set) får inte innehålla några dubbletter.
Interfacen Collection
och Set
har i Java samma operationer.
Skillnaden är att i Set
får inga dubbletter förekomma.
Set
får innehålla null element, men bara ett null-element, pga
dubblettförbudet.
Klasser som implementerar interfacet Set:
TreeSet (balanserat träd)
HashSet (använder hashtabell)
Några metoder som kan vara bra att känna till:
| | |
|--------------------------------------|---------------------------------------------------------------------------------------|
| boolean add(E obj)
| Lägger till obj om det inte redan finns |
| boolean addAll(Collection<E> coll)
| Lägger till alla element om de inte redan finns, returnerar true om set:et är ändrat. |
| boolean contains(Object obj)
| Returnerar true om set:et innehåller ett element som är identiskt(equal) till obj |
Interfacet Comparator
public interface Comparator<T> {
/**
*Compares its two arguments for order.
*Returns a negative integer, zero, or a positive
*integer as the first argument is less than,
*equal to, or greater than the second.
*/
int compare(T e1, T e2);
}
Comparator är ett interface med en metod compare
som ska användas för
att jämföra två olika objekt av typen T. När man anropar sort skickar
man med ett objekt av en klass som implementerar Comparator. Inuti sort
används compare
för jämförelser
En klass som implementerar interfacet Comparable
implementerar en
metod compare
för jämförelse.
Comparator kan vara bra när man vill jämföra objekt av en klass på flera olika sätt.
- Ex: En klass Person. Vi vill både kunna sortera personer i alfabetisk ordning och efter personnummer.
public class Person {
private String name;
private String pNbr;
...
}
public class NameComparator implements Comparator<Person> {
public int compare(Person p1, Person p2) {
return p1.getName().compareTo(p2.getName());
}
}
/* This tree will be sorted by name */
TreeSet<Person> nameTree = new TreeSet<Person>(new NameComparator());
Interfacet Map
Element är tvådelade - en nyckel(key) med tillhörande värde(value). Nyckeln är unik, men inte värdena.
Klasser som implementerar interfacet Map:
TreeMap (implementerar interfacet SortedMap, dvs elementen kommer att vara sorterade)
HashMap (använder en hashtabell)
Några metoder som kan vara bra att känna till:
| | |
|-------------------------|--------------------------------------------------------------------------|
| V get(Object key)
| Returnerar värdet associerat med key. Returnerar null om det inte finns. |
| V put(K key, V value)
| Associerar key med value i denna mapen. Dvs lägger till i mapen. |
| V remove(Object key)
| Tar bort objektet med nyckeln key i mapen. |
Hashtabell
En hashtabell är en datastruktur där data sparas tillsammans med en nyckel. Positionen i strukturen beräknas med en hashfunktion. Ofta behöver man en datastruktur som kan hantera både insättningar och sökningar effektivt.
Sluten hashtabell: En vektor används för att lagra elementen. Hanterar kollisioner med t.ex. linjär teknik eller kvadratisk teknik.
Öppen hashtabell: En vektor av listor. Kolliderande objekt placeras i samma lista.
Exempel på på sätt att hantera kollisioner:
Linjär teknik: första lediga plats efter. Problem med linjär teknik:
- Ger upphov till primär klustring i tabellen.
- Objekt vars hashkoder är nära ett ”vanligt” hashvärde kommer att drabbas av kollisioner och bygga ut klustret.
- Stora kluster gör sökningen långsam.
Kvadratisk teknik: undviker primär klustring (att alla värden samlas intill varandra i vektorn). Funkar inte nästa plats $1^2$ hoppar den till $2^2$ steg bort, funkar inte det går den $3^2$ osv. Alltså: Primärt hashvärde = $pHash$ = värdet där den ”egentligen” borde läggas in. $pHash, pHash + 1^2, pHash +2^2, pHash +3^3, dots, pHash +i^2$
Man måste skugga hashCode
och equals
i den klass vars objekt ska
fungera som en nyckel i en hashtabell.
I Javas klassbibliotek används öppna hashtabeller i klasserna HashSet
och HashMap
.
Fyllnadsgraden i en hashtabell är $\frac{\text{antal element}}{\text{antal tabellplatser}}$.
Om man skall skriva en for-each sats över en hashtabell:
Set<String,Object> map = new Hashmap<String,Object>();
for (Map.Entry<String, Object> entry : map.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
// ...
}
Observera att element i en hashtabell inte är sorterade. Ett elements plats i tabellen bestäms av dess hashvärde.
Rekursiva metoder
En rekursiv metod måste ha:
En eller flera parametrar
Ett eller flera basfall
Ett eller flera rekursiva anrop. De rekursiva anropen måste leda till att ett basfall så småningom nås.
Skyddsnivåer
public:
Attribut och metoder som vi skriverpublic
framför kan anropas och användas varifrån som helst.Ingen markering:
Attribut och metoder som vi inte skriver något framför kan bara anropas och användas av andra klasser i samma katalog.private:
Attribut och metoder som vi skriverprivate
framför kan bara anropas och användas inifrån den egna klassen.protected:
Klassen själv, andra klasser i samma paket, och this i subklasser har tillgång till dem
Överskuggning och överlagring
Överskuggning innebär att vi ersätter en gammal (ärvd) operation med en ny som har samma signatur - vi får alltså ingen ny operation.
Överlagring innebär att vi lägger till en ny operation som har samma namn som en ärvd operation, men annan signatur. (t.ex olika antal parametrar eller olika typer på sina parametrar)
Vi kan även använda den överskuggade operationen när vi överskuggar med
hjälp av super
.
public void forward(int n){
super.forward(n);
count += n;
}
Vi skriver super.forward(n)
för att få kompilatorn att förstå att det
är operationen forward(int)
i superklassen som skall anropas. Om vi
hade skrivit forward(int)
hade vi anropat oss själva - sådana anrop
kallas rekursiva och är väldigt användbara i många sammanhang, men här
hade det bara inneburit at operationen hade anropat sig själv tills
minnet tar slut (och vi får java.lang.StackOverflowError
).
Exceptions
Det finns två slag av Exceptions:
Unchecked Exceptions Ð används då felet beror på programmeraren Ex: NullPointerException eller ArrayIndexOutOfBoundsException
Checked Exceptions Ð används då felet inte beror på programmeraren Ex: FileNotFoundException om man försöker öppna en fil som inte finns
När man anropar en metod som genererar en checked exception måste man ta hand om det. Normalt fångar man det i en try-catch-sats:
Scanner scan = null;
try {
// try to open file fileName
scan = new Scanner(new File(fileName));
} catch (FileNotFoundException e) {
System.err.println("Couldn't open file " + fileName);
System.exit(1);
}
... use scan ...
Om exception inträffar, avbryts exekveringen av satserna i try-blocket och satserna i catch-blocket exekveras.
I satsen catch(Exception e)
kan t.ex. följande metoder användas för
att få mer information:
e.printStackTrace();
som skriver ut information om raden där felet inträffat och den/de metodanrop som lett till denna rad.e.getMessage();
som returnerar en sträng med meddelande om felets art.
Generera egna exceptions:
throw new IllegalArgumentException("amount to deposit < 0");
Mönstret är:
throw new ExceptionClass();
throw new ExceptionClass(message);
Detta gör att exception-objekt skapas, exekvering av metoden avbryts. Javasystemet letar efter fångade catch-block.
Exempel:
/**
* Deposits the specified amount.
* pre: The specified amount is >= 0
* post: The specified amount is added to balance
* @param n The amount to deposit
* @throws IllegalArgumentException if the specified amount is < 0
*/
public void deposit(int n) {
if (n < 0) {
throw new IllegalArgumentException("amount to deposit < 0");
}
balance = balance + n;
}
Sortering
Varför sortera?
Göra sökning enklare.
Förenkla vissa algoritmer.
Mergesort
En söndra och härda-algoritm. Först sorteras vänstra halvan, sedan den högra. Sedan samsorteras de båda sorterade halvorna. Mergesort har tidskomplexiteten $O(n \log n)$ oavsett indata.
Quicksort
Välj ut ett element (förslagsvis i mitten), detta element kallas för pivotelementet. Flytta element som är mindre än pivotelementet till vänster om det och flytta element som är större till höger. Quicksort har tidskomplexiteten $O(n \log n)$, men $O(n^2)$ i värsta fall.
Heapsort
Heapsort har tidskomplexiteten $O(n \log n)$, är vektorn sorterad tar det $n$ för att sätta in och $\log n$ för att ta ut, alltså ändå $O(n \log n)$.
Insättningssortering (insertion sort)
Element på plats $k$ i vektorn sätts in på rätt plats bland de redan sorterade elementen på plats $0 \rightarrow(k-1)$. Tidskomplexiteten är $O(n^2)$\ Insättningssortering är bra om vektorn nästan är sorterad från början.
Urvalssortering (selection sort)
Den kanske enklaste metoden att sortera är att börja med att leta upp det minsta värdet i vektorn och sätta in det först. Därefter letar vi upp det näst minsta elementet (som är det minsta av de återstående elementen) och sätter in det näst först, och så fortsätter vi att leta upp och sätta in det tredje minsta, fjärde minsta, etc, tills alla element är i rätt ordning.
public static void selectionSort(int[] a, int n) {
for (int i = 0; i < n; i++) {
int smallest = i;
for (int k = i+1; k < n; k++) {
if (a[k] < a[smallest]) {
smallest = k;
}
}
int tmp = a[i];
a[i] = a[smallest];
a[smallest] = tmp;
}
}
För att få ett mått på hur effektiv denna metod är kan vi räkna antalet jämförelser som behövs för att sortera en vektor med $n$ tal. I första varvet i den yttre for-loopen gör vi $ n - 1$ jämförelser (de görs inuti den inre for-loopen), i det andra varvet i den yttre for-loopen gör vi $n - 2$ jämförelser, etc. Totalt får vi:
$ T(n) = (n -1) + (n-2) + \dots + 2 + 1 = \frac{n(n-1)}{2} = O(n^2)$
jämförelser. dvs om vi dubblar antalet element i vektorn så krävs det ungefär fyra gånger fler jämförelser för att sortera elementen.
Sammanfattning sortering
| | | |-------------------------------|-----------------------------------------------------------------------------------------------------------------------------------------------------------------------| | Urvalssortering $O(n^2)$ | Långsam för stora n. Efter k pass är de k minsta sorterade. | | Insättningssortering $O(n^2)$ | Bra för nästan sorterad indata (linjär då). | | Heapsort $O(n \log n)$ | Kan utformas så att inget extra minnesutrymme krävs. I praktiken långsammare än Quicksort. Efter k pass är de k största elementen sorterade. | | Mergesort $O(n\log n)$ | Kräver extra minnesutrymme. I praktiken långsammare än Quicksort. Kan utformas iterativa och användas för att sortera element som finns på fil. | | Quicksort $O(n \log n)$ | Men $O(n^2)$ i värsta fall. Inget extra minnesutrymme för temporär vektor krävs. Bäst av de nämnda i praktiken om man väljer pivot och utför partionering förnuftigt. |
Lista
Implementering av listor:
Vektor, föregångare och efterföljare finns ”naturligt”.
Länkad datastruktur. Består av noder som har information om efterföljande element(eventuellt också föregående = dubbellänkad lista)
En lista är en ordnad följd av element.
Det finns en före-efter-relation mellan elementen
Begrepp som ”första element i listan”, ”efterföljaren till visst element i listan” är meningsfulla. Det finns alltså ett positionsbegrepp.
Vi kan skapa en Lista med interfacet list:
List<String> names;
och på så vis vänta med att välja vilken typ av lista vi vill ha. Två exempel på olika klasser som implementerar List.
ArrayList
, som är särskilt bra på att snabbt hämta värden med get-operationen. Insättningar bör man bara göra sist i listan, då insättningar i andra positioner orsaker flyttningar av element.LinkedList
, som är särskilt bra på att sätta in och ta bort element i början av listan.
Vi skapar en lista såhär:
List<String> names = new ArrayList<String>();
Observera:
// works but considered ugly:
ArrayList<String> names = new ArrayList<String>();
// better! :
List<String> names = new ArrayList<String>();
Kö
En kö kallas även FIFO-lista. First In First Out.\ Insättningar göras alltid sist i följden, och bortagning avser alltid första elementet.\ Analogi: En (vanlig) kö när man handlar mat på ICA...
Några metoder som kan vara bra att känna till:
| | |
|-------------------------|-----------------------------------------------------------|
| boolean offer(E item)
| Sätter in item sist i kön. |
| E poll()
| Tar bort och returnerar första elementet i kön |
| peek
| Returnerar första elementet i kön (utan att ta bort det). |
Prioritetskö
En prioritetskö är en samling element där varje element har en prioritet (som används för att jämföra elementen med). Elementen plockas ut i prioritetsordning till skillnad mot en vanlig kö där elementen plockas ut i den ordning de satts in i kön. De operationer man ska kunna göra på en prioritetskö är
sätta in element.
ta reda på det högst prioriterade elementet (minsta elementet).
ta bort det högst prioriterade elementet (minsta elementet).
Det finns inget speciellt interface för prioritetsköer. Man använder
interfacet Queue<E>
.
Stack
En stack kallas även LIFO-lista. Last In First Out. Operationer sker på toppen av stacken, dvs. nya element läggs till längst upp, och element tas bort längst upp. Analogi: En tallriks dispenser, en hög med papper.
[bild1]
Några metoder som kan vara bra att känna till:
| | |
|-------------------------------------|-----------------------------------------------------------|
| void push(E obj)
| Lägger obj överst på stacken. |
| E pop()
| Tar bort och returnerar översta elementet från stacken. |
| E peek()
| Returnerar första elementet i kön (utan att ta bort det). |
| boolean isEmpty()
| Undersöker om stacken är tom. |
Heap
Heap - ett komplett binärt träd där varje nod innehåller ett element som är $\leq$ barnens element. Alltså: Trädets grenar är så lika långa som möjligt. I fall det inte är möjligt så fylls den nedersta nivån på från vänster (vänsterbalanserat).
För varje nod gäller det att roten innehåller det minsta elementet.
En heap kan med fördel lagras i en vektor.
Roten finns på plats 0.
Barnen till noden på plats $i$ finns på platserna $2i +1$ och $2i +2$ i vektorn. Noden på plats $i$ har alltså sin förälder på plats $(i-1)/2$.
Offer (insättning i heap) görs genom att:
Nya element placeras på första lediga plats i vektorn. Detta ger rätt form på trädet.
Sedan byten uppåt tills rätt ordning.
Interfaces
Ett interface(svenska: gränssnitt) är som ett skal med metoder som den som implementerar måste använda. En klass kan implementera flera interface. Alla metoder är även implicit publika.
public interface InterfaceName{
/** Returns size */
int getSize();
}
public class ClassName implements InterfaceName{
// attributs, methods, constructor as usual, but all methods in InterfaceName must be included
}
Om det skiljer mycket mellan metoderna i klasserna så använd interface istället för en superklass.
Java Collections Framework
Är en hierarki av interface, abstrakta klasser och konkreta klasser för samlingar av element.
Finns i paketet
java.util.
JFC - interface hieraki
| | | |------------|-------------------------------------------------------------------------------------------------------------------| | Collection | en samling av element, där dubbletter tillåts | | Queue | en samling av element som utgör en kö | | List | en samling element där dubbletter tillåts och där positionerna är möjlig (första, sista, element på plats i, ...) | | Set | en samling element där dubbletter är förbjudna | | SortedSet | som Set men med kravet att elementen går att jämföra | | Map | en samling element där varje element har en nyckel och ett värde | | SortedMap | som Map men med krav att nycklarna går att jämföra |
| Interface | Implementering | |-----------|---------------------------| | Queue | LinkedList, PriorityQueue | | List | ArrayList, LinkedList | | Set | HashSet | | SortedSet | TreeSet | | Map | HashMap | | SortedMap | TreeMap |
Klassen Object
Det finns en klass Object
, som är ”alla klassers moder”, om vi inte
uttryckligen ärver någon annan klass så ärver vi automatiskt klassen
Object
.
Klassen Object
innehåller en del metoder som kan vara bra att känna
till:
toString
public String toString()
ger en sträng som beskriver objektet. Denna operation anropas automatiskt om vi skulle försöka skriva ut ett objekt, och implementationen i klassen
Object
skriver ut klassnamnet och ett heltal (objektets hash-kod)Metoden
equals
används för att jämföra om två objekt är lika.public boolean equals(Object obj);
Metoden returnerar true om och endast om de jämföra objekten är identiska. Om man istället vill att innehållet inuti objekten ska jämföras måste man skugga
equals
.public boolean equals(Object obj){ if (obj instanceof Person){ return idNbr == ((Person) obj).idNbr; }else{ return false; } }
- Observera att parametern till
equals
måste vara av typenObject
, annars blir det inte skuggning. Därför måste också typomvandlig till Person ske när man ska använda obj:s idNbr. - Uttrycket
obj instanceof Person
returnerar true om obj:s typ är av Person eller någon subklass till Person. - Uttrycket
obj instanceof Person
returnerar false om obj har värdet null.
- Observera att parametern till
Förutom operationerna ovan innehåller även
Object
en operationpublic int hashCode()
som ger en så kallad hash-kod, dvs ett slags numeriskt fingeravtryck för ett objekt. Detta innebär att alla objekt har en hashkod. Exempel:
public int hashCode() { return isbn.hashCode(); }
Algoritmers tidsåtgång
Storleksordning för funktioner:
| $f(n)$ | namn | |------------|--------------| | $1$ | konstant | | $\log n$ | logaritmisk | | $n$ | linjär | | $n \log n$ | log-linjär | | $n^2$ | kvadratisk | | $n^3$ | kubisk | | $2^n$ | exponentiell |
Tidskomplexitet för några algoritmer
Sortera $n$ tal med urvalssortering, bubbelsortering eller insättningssortering: $O(n^2)$
Sortera med Mergesort och Quicksort: $O(n \log n)$
Söka bland $n$ osorterade element: $O(n)$
Söka med binärsökning bland $n$ sorterade element: $O(\log n)$
Sätta in element först i en lista med $n$ element: $O(1)$.