Vordefinierte Datenstrukturen

Werbung
8 Datenstrukturen
8- 1
Datenstrukturen
Inhalt
Vordefinierte Datenstrukturen
Eigene Datenstrukturen
Übungsaufgaben
Vordefinierte Datenstrukturen
Wir behandeln an dieser Stelle nur zwei Beispiele.
Vector
Die Klasse Vector funktioniert wie ein Array, das bei Bedarf wächst. Sie ist in java.util
erklärt und muß erst importiert werden.
import java.util.*;
Danach kann man einen Vektor wie folgt deklarieren
Vector v = new Vector();
Der Konstruktor akzeptiert auch eine Minimalgröße. Was kann man nun auf dem Vektor
speichern? Java unterstützt einen speziellen Datentyp Object, der entweder ein Array oder eine
Instanz einer Klasse sein kann (eigentlich eine Referenz auf eines von beiden). Um solch ein
Object in ein Array oder ein Klassen-Objekt (die Syntax ist hier etwas unglücklich, da
Objekte eigentlich nur Instanzen von Klassen bezeichnen) umzuwandeln, verwendet man
einen type cast.
Normalerweise verwendet man Vektoren zur Speicherung homogener Daten. Dies ist aber
nicht notwendigerweise so, wie das folgende Beispiel zeigt, das Daten verschiedener Typen
auf einem Vektor speichert.
import java.util.*;
public class Test
{
static public void main (String args[])
{
Vector v=new Vector();
// Speichern:
v.addElement("Test"); // Speichere String
v.addElement(new Integer(2)); // Speichere Integer-Objekt
v.addElement(new double[100]); // Speichere Array
8 Datenstrukturen
8- 2
// Auslesen:
System.out.println((String)v.elementAt(0)); // druckt: Test
System.out.println((Integer)v.elementAt(1)); // druckt: 2
System.out.println(((double[])v.elementAt(2)).length); // druckt:
100
}
}
Die Integer-Klasse ist eine sogenannte Wrapper-Klasse für int-Werte. Sie ist genau zu dem
Zweck gedacht, int-Werte in Strukturen wie Vektoren zu speichern. Sie besitzt eine sinnvolle
toString()-Methode und kann daher direkt ausgedruckt werden. Analog existiert Double,
Boolean etc.
Der type cast für ein Array hat also die Form (datentyp []). Man beachte das zusätzliche
Klammerpaar vor .length. Dieses Paar ist nötig, um den type cast auf das Objekt
v.elementAt(2) wirken zu lassen, bevor dessen Länge ausgegeben wird.
Macht man beim Unwandeln des Objektes einen Fehler, so gibt es eine
IllegalTypeCastException.
Hashtable
Hashtables sind ein Beispiel für ein fortgeschrittenes und nützliches Konzept. Sie dienen
dazu, Daten anhand eines Schlüssels abzuspeichern, unter dem Sie später wiedergefunden
werden können. Der Schlüssel kann ein beliebiges Objekt sein, das eine hashCode() Methode
hat. Meist jedoch wird einfach ein String verwendet.
Hashtables speichern die Objekte in einem Array unten einem Index, der sich aus dem
Hashcode des Schüssels ergibt. Dabei müssen natürlich eventuelle Kollisionen aufgelöst
werden, wofür es verschiedene Strategien gibt.
Folgendes Beispiel illustriert das Abpeichern und wiederfinden von Objekten.
import java.util.*;
public class Test
{
public static void main (String args[])
{
Hashtable H=new Hashtable();
H.put("eins","one");
H.put("zwei","two");
H.put("drei","three");
System.out.println(H.get("zwei")); // druckt: two
System.out.println(H.get("vier")); // druckt: null
}
}
Falls also ein Schlüssel nicht gefunden wird, liefert get() den Wert null.
Hashtables eignen sich also für Wörterbücher oder Eigenschaftlisten (Properties). Sie sind
sowohl beim Zugriff als auch beim Abspeichern sehr effektiv.
8 Datenstrukturen
8- 3
Eigene Datenstrukturen
Listen
Als Beispiel erzeugen wir eine einfach verkettete Liste. Jedes Listenelement ist ein Objekt auf
dem Heap, das ein Zeiger auf das nächste Listenelement enthält. Das letzte Listenelement
zeigt auf null. Dies ist sehr einfach mit folgender Klasse zu realisieren.
class ListElement
{
private ListElement Next;
public ListElement () // Listelement ohne Nachfolger
{
Next=null;
}
public ListElement (ListElement next) // mit Nachfolger next
{
Next=next;
}
public void setNext (ListElement next) // setze Nachfolger
{
Next=next;
}
public ListElement getNext () // lies Nachfoger
{
return Next;
}
}
Mit Hilfe dieser Klasse kann man einfach 10 Elemente erzeugen. Mit folgendem Code
werden einfach 10 Listenelemente erzeugt, wobei die neueste jeweils vorne an die Liste
gehängt wird.
public class Test
{
public static void main (String args[])
{
ListElement e=null;
for (int i=0; i<10; i++)
{
e=new ListElement(e);
}
}
}
Will man irgendeinen Inhalt in die Listenelemente speichern, so erzeugt man einfach einen
Erben, der das kann. Z.B.
class StringElement extends ListElement
{
private String S;
public StringElement (String s)
{
S=s;
}
public StringElement (String s, ListElement next)
{
super(next);
S=s;
}
public void setString (String s)
{
S=s;
}
public String getString ()
{
return S;
}
}
Man beachte den Aufruf des Konstruktors von ListElement mit Hilfe von super(next). Bei
8 Datenstrukturen
8- 4
dem Default-Konstruktor (super()) ist dieser Aufruf nicht notwendig, sondern wird
automatisch durchgeführt.
public class Test
{
public static void main (String args[])
{
ListElement e=null;
for (int i=0; i<10; i++)
{
e=new StringElement("Number "+i,e);
}
while (e!=null)
{
System.out.println(((StringElement)e).getString());
e=e.getNext();
}
}
}
Dieses Programm gibt die Zahlen in umgekehrter Reihenfolge aus, da das neueste ListenElement jeweils vorne angehängt wird, aber die Liste von vorne nach hinten gedruckt wird.
Alternativ könnte man auch ein Object-Datenfeld in ListElement aufnehmen und die
Strings darauf ablegen. Dies erspart die Deklaration eines Kindes für jedes aufzunehmende
Objekt. Die entsprechende Deklaration sieht so aus.
class ListElement
{
private ListElement Next;
Object Content;
public ListElement (Object content) // Listelement ohne Nachfolger
{
this(content,null);
}
public ListElement (Object content, ListElement next) // mit Nachfolger
next
{
Next=next;
Content=content;
}
public void setNext (ListElement next) // setze Nachfolger
{
Next=next;
}
public ListElement getNext () // lies Nachfoger
{
return Next;
}
public void setContent (Object content)
{
Content=content;
}
public Object getContent ()
{
return Content;
}
}
public class Test
{
public static void main (String args[])
{
ListElement e=null;
for (int i=0; i<10; i++)
{
e=new ListElement("Number "+i,e);
}
while (e!=null)
{
System.out.println((String)(e.getContent()));
e=e.getNext();
}
8 Datenstrukturen
8- 5
}
}
Beachten Sie den Aufruf des zweiten Konstruktors mittels this(). Im Hauptprogramm muß
hier getContent() erst nach String umgewandelt werden, bevor man es verwenden kann.
Bäume
Wir geben hier zum Studium nur das Listing von sortierten Binärbäumen an. Ein Baum
besteht aus Knoten, mit je zwei Kindern, die wieder Bäume sind (oder null). Der Knoten hat
als Inhalt einen String. Alle Knoten im linken Kindbaum haben einen alphabetisch kleineren
String als Inhalt und alle Knoten im rechten Kindbaum einen alphabetisch größeren.
Wir erzeugen einen solchen Baum, und geben die Inhalte sortiert wieder aus.
class StringNode
{
private String S;
protected StringNode Left,Right;
public StringNode (String s)
{
Left=Right=null;
S=s;
}
public void insert (String s) // Fuege s korrekt ein.
{
if (s.compareTo(S)>0) // dann rechts
{
if (Right==null) Right=new StringNode(s);
else Right.insert(s);
}
else // sonst links
{
if (Left==null) Left=new StringNode(s);
else Left.insert(s);
}
}
public String getString ()
{
return S;
}
public StringNode getLeft ()
{
return Left;
}
public StringNode getRight ()
{
return Right;
}
}
public class Test
{
public static void main (String args[])
{
StringNode tree=null;
for (int i=0; i<20; i++) // 20 Zusfallsstrings speichern
{
String s="Zufallszahl "+Math.random();
if (tree==null) tree=new StringNode(s);
else tree.insert(s);
}
print(tree); // Sortiert wieder ausdrucken
}
public static void print (StringNode tree)
// Rekursive Druckfunktion
{
if (tree==null) return;
print(tree.getLeft());
System.out.println(tree.getString());
8 Datenstrukturen
8- 6
print(tree.getRight());
}
}
Übungsaufgaben
1. Ändern Sie das Hauptprogramm für die verkettete Liste (mit StringElement)
so um, daß die neuen Listenelemente jeweils an das Ende der Liste angehängt
werden.
2. Schreiben Sie eine Klasse StringList, die eine Liste von Strings verwaltet.
Sie soll eine Zeiger auf das erste und das letzte Element enthalten, und Strings
vor und hinter die Liste hängen können.
3. Schreiben Sie ein rekursives Unterprogramm, das eine Liste von Strings von
hinten nach vorne ausgibt.
4. Schreiben Sie ein rekursives Unterprogramm, das alle Vorkommen eines
Strings aus der Liste streicht, etwa definiert als
static StringElement remove(StringElement e, String s);
5. Schreiben Sie
void remove (String s);
als Methode der Klasse StringList
aus 2. Hier muß am Schluß noch Last
aktualisiert werden.
Lösung.
Aufgaben ohne Lösung
1. Schreiben Sie eine Klasse StringList, die eine vorwärts und rückwärts
verkettete Liste verwaltet. Dazu muß natürlich ListElement einen PreviousVerweis bekommen, der dann auch verwaltet werden muß.
2. Schreiben Sie eine iterative Version von remove(String s), die den String s
aus eine StringList entfernt.
Herunterladen