Bäume in C [Bäume in C] (Bäume in C), Lektion, Seite 722304
https://www.purl.org/stefan_ram/pub/baeume_in_c (Permalink) ist die kanonische URI dieser Seite.
Stefan Ram

Bäume in C 

Unter einem Binärbaum von Werten  soll hier eine Entität verstanden werden, die entweder

A' „leer “ ist (der leere Baum ) oder

B' zwei verschiedene Bäume, die direkter linker Unterbaum  und direkter rechter Unterbaum  genannt werden, und einen Wert  enthält.

Wenn im Fall B' die beiden Unterbäume leer sind, dann wird der Baum Blatt  oder Blattbaum  genannt.

Ein Baum heißt direkter Unterbaum  eines anderen Baums, wenn er der direkte linke Unterbaum oder der direkte rechte Unterbaum dieses anderen Baums ist.

Der Übergang von einem Baum zu einem seiner direkten Unterbäume wird hier ein „Schritt “ genannt. Eine Folge von Schritten heißt ein „Pfad “. Ein Baum heißt Unterbaum  eines anderen Baumes, wenn er durch einen Pfad von diesem anderen Baum aus erreicht werden kann.

Von einem leeren Baum oder einem Blattbaum führt kein Weg zu einem anderen Baum.

Die Definition  eines Baumes ist rekursiv, da im Fall B' wieder auf Bäume bezug genommen wird. Ein Baum ist jedoch selber keine zirkuläre  Datenstruktur, das heißt, es gibt keinen nichtleeren Pfad, der von einem Baum zu sich selbst führt.

Ein solcher Baum kann in der Programmiersprache C  durch einen Zeiger  repräsentiert (dargestellt) werden, der entweder

A gleich 0 ist oder

B auf eine Struktur mit zwei weiteren solcher Zeiger und einen Wert zeigt.

Bei Blättern wird etwas Speicherplatz für zwei Nullzeiger „verschenkt“, jedoch vermeidet diese Darstellung eines Baum die Komplikationen der Polymorphie, die sich ergeben würden, wenn statt dessen eine spezielle andere Struktur für Blätter verwendet werden würde.

Statische angelegte Bäume und ihre Ausgabe

Das folgende Beispielprogramm legt einen kleinen Baum an und gibt ihn wieder aus.

Anlegen und Ausgeben eines Baumes

#include <stdio.h> /* printf, putchar */
#include <stdlib.h> /* EXIT_SUCCESS */

typedef struct tree
{ struct tree * left; int value; struct tree * right; } * TREE;

void print( TREE const tree )
{ if( tree ){ putchar( '(' ); print( tree->left );
printf( "%d", tree->value ); print( tree->right ); putchar( ')' ); }}

extern struct tree t1, t0, t2, t4; struct tree t3 =
{ &t1, 3, &t4 },
t1 ={ &t0, 1, &t2 }, t4 ={ 0, 4, 0 },
t0 ={ 0, 0, 0 }, t2 ={ 0, 2, 0 };

int main( void ){ print( &t3 ); putchar( '\n' ); return EXIT_SUCCESS; }

output
(((0)1(2))3(4))

Die folgende Veranschaulichung erhält man, indem man in dem obigen Quelltext von der Adresse »&t1« eine Linie zur darunter befindlichen Definition von »t1« zieht, und dies für die anderen Namen »t0«, »t2«, »t3« und »t4« entsprechend wiederholt.

Veranschaulichung
      3
/ \
/ \
1 4
/ \
/ \
0 2

Entsprechend der obenstehenden Definition eines Baumes wurde hier mit »typedef« ein Zeiger auf eine Struktur  als Baumtyp »tree« definiert, denn nur ein Zeiger kann auch 0 sein, wie im Fall „A“ festgelegt.

Man beachte wie die Struktur der Funktion »print« der Definition der Datenstruktur „Baum“ folgt: Es wird die Fallunterscheidung berücksichtigt und dann werden gegebenenfalls die einzelnen Komponenten behandelt.

Rob Pike's “Rule 5”
Data dominates. If you've chosen the right data structures and organized things well, the algorithms will almost always be self-evident. Data structures, not algorithms, are central to programming.

Daß dann der Name des Parameters  der print -Funktion gleich dem Namen dieses Typs gewählt wurde, ist sicher nicht jedermanns Geschmack, aber es ist technisch gesehen möglich (dann bezeichnet »tree« innerhalb des Funktionsrumpfs nur den Parameter »tree«, aber nicht mehr den Typ »tree«).

Die Initialisierung der Struktur  muß in C90  außerhalb des Funktionsrumpfs erfolgen, wenn sie Initialisierer (wie »{ &t0, 1, &t2 }«) verwenden sollen, weil Initialisierer in C90  nur zur Schreibzeit bekannte Werte enthalten dürfen und die Adressen (wie »&t0«) nur bei Objekten mit statischer Speicherdauer konstant sind. Wenn die Adressen nicht konstant wären, dann wären nämlich nur Zuweisungen, wie in »t1.left = &t0; t1.right = &t2;«, möglich. In C99  könnten die Definitionen der fünf Variablen auch im Rumpf der Funktion »main« stehen, was eigentlich schöner wäre, da sie nur dort lokal benötigt werden.

Zwei Deklarationen, wie »int a; int b;« können auch als »int a, b;« zusammengefaßt werden, doch bedeutet »char* a, b;« »char *a; char b;« und nicht »char *a; char *b;«.

Da die Initialisierung einiger Baumstrukturen Vorwärtsbezüge  auf andere Bäume enthält, wurden die Baumstrukturen zuvor mit Hilfe des Schlüsselwortes »extern« deklariert. Die Bäume wurden dann so definiert, daß die Anordnung der Initialisierer im Quelltext der Veranschaulichung der Baumstruktur ähnelt.

Die Ausgabefunktion »print« ist rekursiv. Rekursive Datenstrukturen legen oft entsprechend rekursive Algorithmen nahe. Man beachte, daß die Struktur der print -Funktion der Struktur der Datenstruktur »tree« recht ähnlich ist.

/    Präsenzaufgabe Baum durchsuchen
Schreiben Sie eine Funktion »contains« die einen Baum und eine Zahl als Argumentwerte akzeptiert und genau dann 0 ergibt, wenn die Zahl nirgends in dem Baum vorkommt, andernfalls soll sie einen Unterbaum aus dem Baum ergeben, der diese Zahl direkt darstellt (enthält). (Hierbei soll nicht  angenommen werden, daß die Werte in dem Baum irgendwie „sortiert“ sind.)
/    Übungsaufgabe Vorhandene Bibliotheken nutzen
Wie bei Listen ist es auch beim Bäumen manchmal besser, vorhandene Bibliotheken zu nutzen. Finden Sie eine C -Bibliothek für (binäre) Bäume, erstellen Sie damit den oben veranschaulichten Baum und geben Sie diesen (möglichst mit Hilfe dieser Bibliothek) aus.
Hinweis Es wurde nicht geprüft, ob es tatsächlich eine passende Bibliothek gibt.

Die Textdarstellung eines Baums als Text, wie beispielsweise »(((0)1(2))3(4))« folgt der folgenden Grammatik mit dem Startsymbol tree .

tree 〉 ::=
[〈entry 〉].
entry 〉 ::=
'(' 〈tree 〉 〈number 〉 〈tree 〉 ')'.
number 〉 ::=
'0' | '1' | '2' | '3' | '4' | '5' | '6' | '7' | '8' | '9'.

Die durch diese Grammatik beschriebene Sprache wird hier „Tree “ genannt..

Der rekursiven Struktur der darzustellenden Daten entspricht die rekursive Struktur dieser Grammatik.

Bei der Ausgabe (oder allgemeiner: beim Behandeln von Bäumen) werden verschiedene Möglichkeiten unterschieden:

Entsprechende Darstellungen arithmetischer Terme nennt man dann „Präfix/Infix/Postfix-Notation“: +(1,2); 1+2; 1,2,+. Eine Variante der ersteren wird als Cambridge-Notation  in LISP verwendet, eine Variante der letzteren als „UPN“ bei einigen (heute wohl historischen) Taschenrechnern.

/    Präsenzaufgabe Durchlaufen eines Baumes
Schreiben Sie die Funktion »print« so um, daß sie ein “pre-order traversal ” verwendet. (Die Ausgabe des Programms sollte dann die Zahlen in der Reihenfolge „3,1,0,2,4“ enthalten – das genaue Format der Darstellung kann bei dieser Übungsaufgabe frei gewählt werden.

Dynamische angelegte Bäume und ihr Einlesen

Das folgende Programm zeigt das Einlesen und Ausgaben eines Baumes. Zum Einlesen wird ein rekursiver syntaktischer Abstiegsanalysator  (“recursive descent parser ”), der aus den beiden Funktionen »tree_« und »entry« besteht verwendet. Seine Struktur entspricht der obigen Grammatik.

Zur Vereinfachung wird der Baum nicht aus einer Datei gelesen, sondern aus dem im Programm selber enthaltenen Text »(((0)1(2))3(4))«. Zum Einlesen lexikalischer Einheiten dient der lexikalische Analysator  (“scanner ”) »get«.

Die Fehlerbehandlung ist vereinfacht oder fehlt, und es werden auch nur einstellige Zahlen verarbeitet.

Einlesen und Ausgeben eines Baumes

#include <stdio.h> /* printf, putchar */
#include <stdlib.h> /* EXIT_SUCCESS, abort */

/* tree */

typedef struct tree
{ struct tree * left;
int value;
struct tree * right; } * TREE;

TREE newtree( TREE const left, int const value, TREE const right )
{ TREE const result = malloc( sizeof *result );
if( !result )abort();
result->left = left;
result->value = value;
result->right = right;
return result; }

/* scanner */

char get( int const move )
{ static char const * const source = "(((0)1(2))3(4))";
static int pos = 0;
char const result = source[ pos ];
if( result )pos += !!move;
return result; }

/* parser */

int number( void ){ return get( 1 )- '0'; }

TREE tree( void );

TREE entry( void )
{ TREE left, right; int value;
get( '(' );
left = tree();
value = number();
right = tree();
get( ')' );
return newtree( left, value, right ); }

TREE tree( void )
{ return get( 0 )== '(' ? entry() : 0; }

/* serializer */

void print( const TREE const tree )
{ if( tree )
{ putchar( '(' ); print( tree->left ); printf( "%d", tree->value );
print( tree->right ); putchar( ')' ); }}

/* main */

int main( void )
{ print( tree() ); putchar( '\n' );
return EXIT_SUCCESS; }

stdout
(((0)1(2))3(4))

Man beachte eine Vorabdeklaration  („Vorwärtsdeklaration“) der Funktion »tree«, die nötig ist, weil »tree« und »entry« sich gegenseitig aufrufen (damit ist jede dieser beiden Funktionen indirekt  rekursiv).

Man beachte auch die konstruierenden Funktionen („Konstruktoren“) »newtree« und »newleaf« (siehe unten) zum Erzeugen eines Baums beziehungsweise eines Blatts. Diese beiden Funktionen zeigen also, wie Bäume zur Laufzeit  angelegt werden können, während das erste Programmbeispiel einen statischen Baum enthielt.

newleaf
TREE newleaf( int const value ){ return newtree( 0, value, 0 ); }
?    Übungsfrage static
Welchen Sinn hat die Verwendung von »static« in der Methode »get«.
?    Übungsfrage !!
Welchen Sinn hat die Verwendung von »!!« in der Methode »get«?
?    Übungsfrage entry
Könnten die Funktion »entry« auch folgendermaßen (etwas kürzer) geschrieben werden?
tree entry( void )
{ tree result, left, right;
get( '(' );
result = newtree( tree(), numeral(), tree() );
get( ')' );
return result; }

Serialisierung

Dieses Programm zeigt eine ohne Verlust relevanter Informationen vorgenommene Hin-und-her-Wandlung  zwischen einer struct -Repräsentation  eines Baumes (»struct tree«) und einer Textdarstellung  desselben Baumes in der Sprache Tree . Relevante Informationen interner Datenstrukturen werden oft in Texte gewandelt, um sie außerhalb eines Programms und seiner Laufzeit in eine Textdatei oder Datenbank ablegen („persistieren “) zu können. Die Wandlung in eine Textdarstellung (oder Byte -Sequenz) wird auch „Serialisierung “ genannt (“serialization ”, “marshalling ” [manchmal auch mit etwas speziellerer Bedeutung], oder seltener: “pickling ” [wörtlich: soaking something in vinegar to preserve it ] oder “shelving ”), die Wandlung einer Textdarstellung in eine interne Darstellung „Deserialisierung“ (“deserialization ”, “unmarshalling ”, “demarshalling ”).

XML  ist ein Standardformat für Daten, das Baumstrukturen durch die Möglichkeit der Verschachtelung von XML -Elementen direkt unterstützt.

/    Übungsaufgabe Lesen aus Strom
Schreiben Sie das obige Programm so um, daß es die derzeit als Text im Programm enthaltene Baumdarstellung von der Standardeingabe »stdin« liest.
/    Übungsaufgabe Größere Zahlen
Schreiben Sie das obige Programm oder das Ergebnis der vorherigen Übungsaufgabe so um, daß in der Textdarstellung auch Numerale ganzer Zahlen, die größer als 9 sind, vorkommen können und richtig eingelesen werden.

Binäre Suchbäume

Bei binären Suchbäumen  sind die (stets voneinander verschiedenen) Werte in aufsteigender  Reihenfolge angeordnet. Dies ist in dem oben als Beispiel verwendeten Baum schon zu sehen, obwohl des oben noch nicht vorausgesetzt wurde.

Durch die Sortierung ist eine schnelle, binäre Suche nach Werten möglich. Diese Werte können auch Schlüssel  zum Erreichen anderer Daten  sein. Etwa Kennzahlen von Datensätzen, wobei dann ein Baum neben seinem Wert auch noch einen Verweis auf den zugehörigen Datensatz enthalten könnte. Die Suche nach Werten in solchen sortierten Bäumen ist meist schneller als die Suche nach Werten in linearen Listen.

In der Anwendungsprogrammierung empfiehlt es sich im allgemeinen nicht, Standardbehälter, wie binäre Suchbäume, erneut  zu implementieren. Statt dessen sollte eine vorhandene Implementation (Bibliothek) oder eine Datenbank verwendet werden. Dadurch wird Zeit gespart, und die Qualität sorgfältig ausgewählter Fremdprodukte übertrifft oft die selbstgeschriebener Implementationen. Man würde normalerweise ja auch den verwendeten Rechner nicht selber bauen.

/    Übungsaufgabe Binäre Suchbäume
Auf verschiedenen Webseiten, wie etwa
http://de.wikipedia.org/wiki/Bin%C3%A4rer_Suchbaum
finden sich Beschreibungen typischer Operationen auf Binärbäumen wie der Suche oder dem Einfügen.
Das Einfügen können Sie auch an Hand der Operation “Insert ” mit dem Applet auf der Seite
http://www.cs.umd.edu/~egolub/Java/BinaryTree.html
beobachten, indem Sie wiederholt unterschiedliche Zahlen in das Textfeld schreiben und mit der Tastfläche »Insert« einfügen.
Implementieren Sie diese Operationen (Suchen, Einfügen  und Löschen ) in C, indem Sie das zuletzt vorgestellte Programm zunächst an den von Ihnen bevorzugten Stil anpassen und dann entsprechend erweitern.
Beim Löschen  eines Baumes sollte auch daran gedacht werden, nicht mehr benötigten Speicher mit allozierter Lebensdauer wieder freizugeben.
/    Übungsaufgabe Behälter veränderlicher Größe [wichtige Aufgabe, da ausdrücklich gewünscht! ]
Ein Programm soll die Menge der Benutzernamen einer Benutzergruppe modellieren. Da Benutzer aus der ganzen Welt über eine Web-Schnittstelle ein- oder austreten können, könnte die Anzahl der Benutzer auch groß werden. Andererseits soll zu jedem Zeitpunkt nicht mehr Speicher reserviert oder verwendet werden als benötigt wird.
Das Programm akzeptiert Zeilen, die mit einem Plus »+«, einem Fragezeichen »?« oder einem Minus »-« beginnen, dem direkt der Benutzername als Rest der Zeile folgt. (Ein Benutzername darf kein Zeilenendzeichen enthalten und besteht nur aus druckbaren Zeichen.)
+ Ein Benutzername, dem ein Pluszeichen vorangestellt ist, soll zur Benutzermenge hinzugefügt werden. Falls dies möglich war, soll nichts ausgegeben werden, falls der Name aber schon in der Menge enthalten ist oder das Einfügen scheiterte, soll eine Zeile mit einer entsprechenden Meldung ausgegeben werden.
? Wenn ein Benutzername, dem ein Fragezeichen vorangestellt ist, in der Benutzermenge enthalten ist, soll die Zeile »1« ausgegeben werden, wenn nicht, die Zeile »0«.
- Ein Benutzername, dem ein Minuszeichen vorangestellt ist, soll aus der Benutzermenge entfernt werden. Falls dies möglich war, soll nichts ausgegeben werden, falls der Name aber schon vorher nicht in der Menge enthalten war oder das Entfernen scheiterte, soll eine Zeile mit einer entsprechenden Meldung ausgegeben werden.
Zur Vereinfachung der Lösung kann angenommen werden, daß fehlerhafte Eingaben bereits durch ein anderes Programm ausgefiltert wurden, so daß alle Eingaben syntaktisch korrekt sind.
Die Menge soll hierbei als (verkettete) Liste oder als binärer Baum oder als binärer Suchbaum realisiert werden. Da mit dieser Aufgabe die Verwaltung von Speicher mit allozierter Lebensdauer geübt werden soll, soll in diesem Fall keine Bibliothek anderer Autoren verwendet werden.
(Hinweis: In bestimmten Umgebungen kann der Speicher durch viele kleine Anforderungen und Freigaben ungünstig fragmentiert werden, besonders, wenn diese auch noch unterschiedliche Größe haben. Dann kann es besser sein, den Speicher teilweise innerhalb des Programms zu verwalten, was aber hier nicht behandelt werden kann. In der Praxis würde man für solche Aufgaben oft eine Datenbank verwenden.)
(Hinweis: In der Mathematik sind Mengen nicht zeitlich veränderlich. Daher sollte oben genau genommen von einem „Mengenspeicher“ und nicht von einer „Menge“ gesprochen werden.)

Seiteninformationen und Impressum   |   Mitteilungsformular  |   "ram@zedat.fu-berlin.de" (ohne die Anführungszeichen) ist die Netzpostadresse von Stefan Ram.   |   Eine Verbindung zur Stefan-Ram-Startseite befindet sich oben auf dieser Seite hinter dem Text "Stefan Ram".)  |   Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram. Schlüsselwörter zu dieser Seite/relevant keywords describing this page: Stefan Ram Berlin slrprd slrprd stefanramberlin spellched stefanram722304 stefan_ram:722304 Bäume in C Stefan Ram, Berlin, and, or, near, uni, online, slrprd, slrprdqxx, slrprddoc, slrprd722304, slrprddef722304, PbclevtugFgrsnaEnz Erklärung, Beschreibung, Info, Information, Hinweis,

Der Urheber dieses Textes ist Stefan Ram. Alle Rechte sind vorbehalten. Diese Seite ist eine Veröffentlichung von Stefan Ram.
https://www.purl.org/stefan_ram/pub/baeume_in_c