struct Knoten{
int Data1; /*Normales Feld*/
char *Data2;
struct Knoten *Naechster;
/*Verweis auf eine weitere Struktur "Knoten"*/
};
Die Zeigervariable Naechster enthält normalerweise die Adresse des nächsten
Listenelements oder NULL, wie Abb.8.1 zeigt. Der allgemeine
Zeigerwert NULL wird verwendet, um das Ende der Liste anzugeben und ist der einzige
Wert, der mit jedem Zeiger verwendet werden darf.
{
struct Knoten s,t,u;
struct Knoten *Liste;
s.Data1 = 1; s.Data2 = "Alboin";
t.Data1 = 2; t.Data2 = "Gisulf";
u.Data1 = 3; u.Data3 = "Theodolinda";
}
definieren 3 alleinstehende Knoten, d.h. die keine Beziehung zueinander haben. Die
Verkettung wird durch die Anweisungen
s.Naechster = &t; t.Naechster = &u; u.Naechster = NULL; Liste = &s;ausgeführt Die dadurch entstandene Listenstruktur wird in Abb.8.2 gezeichnet.
s.Naechster = (struct Knoten *)malloc(sizeof(struct Knoten)); s.Naechster.Data1 = 4; s.Naechster.Data2 = "Childebert"; s.Naechster.Naechster = t;Das Ergebnis sieht man nun in Abb.8.3. Die obigen Listenoperationen werden etwas übersichtlicher, wenn man eine zusätzliche Zeigervariable als Hilfsvariable heranzieht:
struct Knoten *KnotenZeiger; KnotenZeiger = (struct Knoten*)malloc(sizeof(struct Knoten)); KnotenZeiger->Naechster = s.Naechster; s.Naechster = KnotenZeiger;Um mit Listenstrukturen konsequent umzugehen, empfiehlt es sich eine Header - Datei `list.h' zu definieren und mittels der Include - Anweisung
#include "list.h"im Programm zu verwenden:
(*) typedef ... DATA; /*Datentyp nach Bedarf hier*/
struct Knoten{
DATA d;
struct Knoten *Naechster;
};
typedef struct Knoten ELEMENT;
typedef ELEMENT *LIST_POINTER;
Will man, z.B. Datenfelder wie in den obigen Beispielen, so setze man statt (*):
typedef struct {
int Data1;
char *Data2;
} DATA;
und im Verwendungsprogramm:
LISTPOINTER Header,p;
{
Header = (LISTPOINTER)malloc(sizeof(ELEMENT));
Header->d.Data1 = 1;
Header->d.Data2 = "Alboin";
Header->Naechster = NULL;
...
}
Diese Art Operation will man allerdings nicht im Anwendungsprogramm definieren, sondern im
Header - Datei list.h schreiben, um dem Anwender diese Art Details zu ersparen.
void einfuegen( LISTPOINTER p, LISTPOINTER q)
{
q->Naechster = p->Naechster;
p->Naechster = q;
}
void einfuegen( LISTPOINTER *p, LISTPOINTER *q)
{
if (*p == NULL) *p = *q;
else {
(*q)->Naechster = (*p)->Naechster;
(*p)->Naechster = *q;
}
}
#include "list.h" typedef QUEUEELEMENT ELEMENT; typedef LISTPOINTER QUEUEPOINTER; QUEUEPOINTER VORN; QUEUEPOINTER HINTEN;Dann werden folgende Operationen für eine Schlangenstruktur erlaubt:
#include "queue.h"
int main()
{
DATA GothenKnoten;
SchlangeInit();
GothenKnoten.Data1 = 25;
GothenKnoten.Data2 = "Theodohad";
SchlangeInsert(GothenKnoten);
...
}
#include <stdlib.h>
#ifndef NULL
#define NULL 0
#endif
typedef ... DATA;
struct StackElement {
DATA d;
struct StackElement *next;
};
typedef struct StackElement STACKELEMENT;
typedef STACKELEMENT *STACK_POINTER;
STACK_POINTER SP; /*good for one stack*/
/* Now some typical stack operations */
int istLeer()
{
return (SP == NULL);
}
DATA pop()
{
DATA temp;
STACK_POINTER where;
if (!istLeer())
{
temp = SP->d;
where = SP;
SP = SP->next;
free(where);
return temp;
}
else{
printf("Stack is empty!");
exit(1);
}
}
void push (DATA x)
{
STACK_POINTER temp;
temp = (STACK_POINTER)malloc(sizeof(STACKELEMENT));
temp->d = x;
temp->next = SP;
SP = temp;
}
void init_stack()
{
SP = NULL;
}
typedef struct{
enum {operator, value} type;
union {
int val;
char op;
} field;
} DATA;
Also wird jede Komponente eines Ausdrucks entweder eine ganze Zahl oder ein Operand `+', `-',
`*' oder `/'. Der Eingabewert `0' bedeutet: `Ein Operand folgt' und `1' heißt: 'Eine
Operation folgt'.
#include <stdio.h>
#include "stack.h"
const int max = 25;
DATA polish_exp[25];
int count = 0;
void read_polish()
{
char str[max];
int intvalue = 0,i;
printf("Bitte Zeichenkette in umgekehreten Polnisch eintippen und
'$' am Ende\n");
scanf("%[0-9+ -*/$]", str);
for (i = 0; str[i] != '\0'; i++){
switch (str[i]){
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
intvalue = str[i] - '0';
while (str[i+1] == '0'||
str[i+1] == '1'||
str[i+1] == '2'||
str[i+1] == '3'||
str[i+1] == '4'||
str[i+1] == '5'||
str[i+1] == '6'||
str[i+1] == '7'||
str[i+1] == '8'||
str[i+1] == '9'){
intvalue = (str[i+1]-'0') + intvalue*10;
i++;
}
polish_exp[count].type = value;
polish_exp[count].field.val = intvalue;
count++;
break;
case '+':
case '-':
case '*':
case '/':
polish_exp[count].type = operator;
polish_exp[count].field.op = str[i];
count++;
break;
case ' ':
}
}
}
int evaluate_polish()
{
char binop;
int i, operand1, operand2;
DATA buffer, temp;
for (i = 0; i < count; i++){
if (polish_exp[i].type == operator){ /*calculate operator on 2 topmost
stack elements, i.e. values */
binop = polish_exp[i].field.op; /*get the operator*/
buffer = pop(); /*get second operand*/
operand2 = buffer.field.val;
buffer = pop(); /*get first operand*/
operand1 = buffer.field.val;
temp.type = value;
switch(binop) {
case '+' : temp.field.val = operand1 + operand2;
break;
case '-' : temp.field.val = operand1 - operand2;
break;
case '*' : temp.field.val = operand1 * operand2;
break;
case '/' : temp.field.val = operand1/operand2;
}
push(temp); /*put result on stack*/
}
else /*type is val--just push*/
push(polish_exp[i]);
} /*for*/
return pop().field.val;
}
void print_polish()
{
int i;
printf("count = %d\n", count);
for(i=0; i <count;i++)
if (polish_exp[i].type == value) printf("%d",polish_exp[i].field.val);
else if (polish_exp[i].type == operator) printf("%c",polish_exp[i].field.op);
else printf("Operand/Operator unknown");
printf("\n");
}
int main(void)
{
read_polish();
printf("Value of reverse polish string is %d\n", evaluate_polish());
print_polish();
return 0;
}
typedef ... DATA;
struct Dlliste_knoten {
DATA d;
struct Dlliste_knoten *Naechster;
struct Dlliste_knoten *Vorheriger;
};
typedef struct Dlliste_knoten DLISTEKNOTEN;
typedef DLISTEKNOTEN *ZDLISTEKNOTEN;
ZDLISTEKNOTEN HEADER;
Für den Datentyp
vier Grundoperationen:
void einfuegen(ZDLISTENKNOTEN p, DATA *q);wobei p ein Zeiger auf einen Knoten ist, wonach der Knoten mit Datenfeld *q eingefügt wird.
DATA loeschen(ZDLISTENKNOTEN p);
Wenn die Reihenfolge aller Unterbäume von B wichtig ist, dann spricht man von einem geordneten Baum. Alle Bäume sind geordnete Bäume, es sei denn sie werden anders bezeichnet. Ein Wald ist eine Menge von 0 oder mehr disjunkten Bäumen. Also bilden die Nicht - Wurzel - Knoten eines Baums einen Wald.
Es gibt zwei Arten von Abstammungsbäume:
Für Datenstrukturen orientert man sich an der zweiten Art: Jede Wurzel ist der Vater ihrer Unterbäume. Die Unterbäume sind die Söhne des Vaters und untereinander Brüder. Die Wurzel eines Baums hat keinen Vater.
typedef ..., DATA;
typedef struct Bknoten{
DATA d;
struct Bknoten *links;
struct Bknoten *rechts;
} BKNOTEN;
typedef BKNOTEN *BAUMZEIGER;
BAUMZEIGER Wurzel;
Wie üblich werden diese Typ- und Datendeklarationen in eine Header - Datei `baum.h'
geschrieben.
Es gibt sehr viele Programm Operationen mit Binärbäumen. Nur einige davon werden hier ohne
Implementationsdetails aufgezählt:
void einfuegen(BAUMZEIGER *wurzelptr, int n)
{
if ((*wurzelptr) == NULL){
*wurzelptr = (BAUMZEIGER)malloc(sizeof(BKNOTEN));
(*wurzelptr)->d = n;
(*wurzelptr)->links = NULL;
(*wurzelptr)->rechts = NULL;
}
else if (n < (*wurzelptr)->d)
einfuegen(&((*wurzelptr)->links),n);
else if (n > (*wurzelptr)->d)
einfuegen(&((*wurzelptr)->rechts),n);
else fprintf(stderr, "Element %d ist bereits vorhanden\n",n);
}
| Wurzel | |
| linker Teilbaum | |
| rechter Teilbaum |
| linker Teilbaum | |
| Wurzel | |
| rechter Teilbaum |
| linker Teilbaum | |
| rechter Teilbaum | |
| Wurzel |
| Preorder: | * - A*BC + D/EF |
| Inorder: | A - B*C*D + E/F |
| Postorder: | ABC* - DEF/ + * |
#include <stdio.h>
#include <stdlib.h>
typedef int DATA;
typedef struct Bknoten{
DATA d;
struct Bknoten *links;
struct Bknoten *rechts;
}BKNOTEN;
typedef BKNOTEN *BAUMZEIGER;
void einfuegen(BAUMZEIGER *wurzelptr, int n)
{
if ((*wurzelptr) == NULL){
*wurzelptr = (BAUMZEIGER)malloc(sizeof(BKNOTEN));
(*wurzelptr)->d = n;
(*wurzelptr)->links = NULL;
(*wurzelptr)->rechts = NULL;
}
else if (n < (*wurzelptr)->d)
einfuegen(&((*wurzelptr)->links),n);
else if (n > (*wurzelptr)->d)
einfuegen(&((*wurzelptr)->rechts),n);
else fprintf(stderr, "Element %d ist bereits vorhanden\n",n);
}
void drucken(BAUMZEIGER knoten, int sp)
{
int i;
if (knoten != NULL){
drucken(knoten->rechts, sp+3);
for(i = 1; i <= sp;i++) putchar(' ');
printf("%d\n", knoten->d);
drucken(knoten->links, sp+3);
}
}
void preorder(BAUMZEIGER ptr)
{
if(ptr != NULL){
printf("%d ", ptr->d);
preorder(ptr->links);
preorder(ptr->rechts);
}
}
void inorder(BAUMZEIGER ptr)
{
if (ptr != NULL){
inorder(ptr->links);
printf("%d ",ptr->d);
inorder(ptr->rechts);
}
}
void postorder(BAUMZEIGER ptr)
{
if (ptr != NULL){
postorder(ptr->links);
postorder(ptr->rechts);
printf("%d ",ptr->d);
}
}
int main()
{
BAUMZEIGER wurzel;
int intval;
wurzel = NULL;
scanf("%d", &intval);
while (intval != -1){
einfuegen(&wurzel, intval);
scanf("%d", &intval);
}
drucken(wurzel,0);
printf("Preorder: ");
preorder(wurzel);
putchar('\n');
printf("Inorder: ");
inorder(wurzel);
putchar('\n');
printf("Postorder: ");
postorder(wurzel);
putchar('\n');
return 0;
}
Eingabe: 10 5 15 3 8 12 18 1 4 9 14
Ausgabe:
18
15
14
12
10
9
8
5
4
3
1
Preorder: 10 5 3 1 4 8 9 15 12 14 18
Inorder: 1 3 4 5 8 9 10 12 14 15 18
Postorder: 1 4 3 9 8 5 14 12 18 15 10
Schritt1: Der Scanner. Der Scanner führt lexikalische Analyse durch, d.h. zerlegt Quellcode in Form einer Eingabezeichenkette in sprachliche atomare Elemente. Diese Elemente heißen Tokens. Etwas genauer: Die Aufgabe des Scanners besteht darin, das nächste lexikalische Symbol oder Token aus dem Quelltext zu holen. Der Scanner hat außerden folgende Aufgaben:
-> oder { zu erkennen.
| <Zahlenstelle> | ::= | 0 | 1 | ... | 9 |
| <Integer> | ::= | <Zahlenstelle> | <Zahlenstelle> <Integer> |
| <Konst> | ::= | a | b | ... | w |
| <Var> | ::= | x |
| <PlusOp> | ::= | + |
| <MinusOp> | ::= | - |
| <MalOp> | ::= | * |
| <TeilOp> | ::= | / |
| <LKlammer> | ::= | ( |
| <RKlammer> | ::= | ) |
$) plaziert.
Leerräume werden übersprungen. Das Ergebnis: Eine globale Variable Symbol wird der aktuellen
gescannten Symbolart besetzt, d.h. PlusOp, MinusOp usw. Erlaubt am Ende ist das Terminal-Symbol TermSym,
um den Vorgang zu stoppen. Ist Symbol gleich IntWert, dann enthält die globale Variable
Wert ihren Wert. Es ist wichtig hier zu merken, daß der Scanner nach Bedarf immer das nächste
Token aus der Eingabe liefert. Beziehungen zwischen den Tokens, oder Syntax werden vom Scanner
nicht geprüft. Die Struktur eines Knotens im Ausdruckbaum ist in der Datei tree.h zu finden
und ist hier gegeben:
#define MaxStellen 6
typedef enum{PlusOp, MinusOp, MalOp, TeilOp, LKlammer, RKlammer,
ConstOp, VarOp, TermSym} SymArt;
typedef struct{
enum{operator, value} type;
union{
struct {
char Inhalt[MaxStellen];
int Laenge;
int Wert;
} Blatt;
SymArt Op;
} KnotenTyp;
} DATA;
struct Knoten{
DATA d;
struct Knoten *links, *rechts;
};
typedef struct Knoten *KnotenZeiger;
Der Scanner selbst kann dann mit Hilfe dieser Struktur geschrieben werden. Man achte besonders auf
die Funktion holeSym, die beim Aufruf immer das nächste Token aus der Eingabe liefert:
#include "tree.h"
#include <stdio.h>
#define SIZE 80
extern SymArt Symbol;
char intPuffer[MaxStellen];
char RgabePuffer[MaxStellen];
int Laenge;
int Aktuell = 0;
char Sym;
char puffer[SIZE];
int anzZeich = 0;
int tempVal;
void reInit()
{
Aktuell = 0;
}
void liesCharString()
{
int i;
scanf("%[0-9+ #*/()a-x$-]", puffer);
for (i=0;puffer[i]!='\0';i++) anzZeich++;
}
SymArt holeSym()
/* Liest naechster Symbol aus der Eingabe--ist entweder eine Konstante,
die Variable x oder ein Operator +,-,*,/,(,). Eine Konstante ist
entweder ein Buchstabe 'a' bis 'w' oder eine int Zahl mit max 6 Stellen*/
{
int i, temp;
Sym = puffer[Aktuell];
while (Sym == ' '){Aktuell++; Sym = puffer[Aktuell];}
switch (Sym){
case '0':
case '1':
case '2':
case '3':
case '4':
case '5':
case '6':
case '7':
case '8':
case '9':
if (Aktuell < anzZeich){
i = 0;
do{
intPuffer[i] = Sym;
Aktuell++; i++;
Sym = puffer[Aktuell];
} while ((Aktuell < anzZeich) && (Sym == '0' ||
Sym == '1' ||
Sym == '2' ||
Sym == '3' ||
Sym == '4' ||
Sym == '5' ||
Sym == '6' ||
Sym == '7' ||
Sym == '8' ||
Sym == '9') &&
(i < MaxStellen));
if (Aktuell <= anzZeich) Aktuell--;
}
Laenge = i;
tempVal = 0;
for (i = 0; i < Laenge; i++){
RgabePuffer[i] = intPuffer[i];
tempVal = 10*tempVal + (intPuffer[i] - '0');
}
Aktuell++;
return ConstOp;
break;
case 'a':
case 'b':
case 'c':
case 'd':
case 'e':
case 'f':
case 'g':
case 'h':
case 'i':
case 'j':
case 'k':
case 'l':
case 'm':
case 'n':
case 'o':
case 'p':
case 'q':
case 's':
case 't':
case 'u':
case 'v':
case 'w':
Laenge = 1;
RgabePuffer[0] = Sym;
Aktuell++;
return ConstOp;
break;
case 'x':
RgabePuffer[0] = 'x';
Laenge = 1;
Aktuell++;
return VarOp;
case '+': Aktuell++; return PlusOp;
case '-': Aktuell++; return MinusOp;
case '*': Aktuell++; return MalOp;
case '/': Aktuell++; return TeilOp;
case '(': Aktuell++; return LKlammer;
case ')': Aktuell++; return RKlammer;
case ' ': Aktuell++; break;
case '$': return TermSym;
}
}
void printSym()
{
switch (Symbol){
case ConstOp: printf("ConstOp ");
break;
case VarOp: printf("VarOp ");
break;
case PlusOp: printf("PlusOp ");
break;
case MinusOp:printf("MinusOp ");
break;
case MalOp:printf("MalOp ");
break;
case TeilOp: printf("TeilOp ");
break;
case LKlammer: printf("LinkeKlammer ");
break;
case RKlammer: printf("RechteKlammer ");
break;
case TermSym :printf("Terminal Symbol ");
break;
}
}
Schritt 2: Der Parser Man konstruiere nun den Ausdrucksbaum seiner BNF entsprechend, d.h. entsprechend einer Grammatik, die bestimmt wie zulässige Ausdrücke geformt werden müssen. Es sei bemerkt, daß ein legaler Ausdruck nur eine Ableitung in dieser Grammatk ist (diese Ableitung ist i.A. nicht eindeutig bestimmt). Die Methode der syntaktischen Prüfung (Parsing) heißt rekursiver Abstieg. Nach unserer Ausdrucksgrammatik, nämlich
| <Expression> | ::= | <Term> | <Term>+ <Term> | < Term > - <Term > |
| <Term> | ::= | <Factor> | <Factor>* <Factor>
| < Factor > / <Factor> |
| <Factor> | ::= | <Integer> | ( <Expression>) |
#include "tree.h"
#include <stdio.h>
#include <stdlib.h>
SymArt Symbol;
KnotenZeiger ExpBaum;
extern int Laenge;
extern char RgabePuffer[MaxStellen];
extern int tempVal;
KnotenZeiger term(); //foreward declarations
KnotenZeiger factor();
extern void printSym();
void Fehler(int which)
{
switch(which){
case 1: printf("Malformed expression\n");
break;
case 2: printf("No right paren\n");
}
}
void Baumdrucken(KnotenZeiger B, int margin)
{
int i;
if (B){
Baumdrucken(B->links, margin+8);
if (B->d.type == value){
for(i = 0; i < margin; i++) putchar(' ');
for(i = 0; i < B->d.KnotenTyp.Blatt.Laenge;i++)
putchar(B->d.KnotenTyp.Blatt.Inhalt[i]);
putchar('\n');
}
else switch(B->d.KnotenTyp.Op){
case PlusOp:
for (i = 0; i < margin; i++) putchar(' ');
putchar('+'); putchar('\n');
break;
case MinusOp:
for (i = 0; i < margin; i++) putchar(' ');
putchar('-'); putchar('\n');
break;
case MalOp:
for (i = 0; i < margin; i++) putchar(' ');
putchar('*'); putchar('\n');
break;
case TeilOp:
for (i = 0; i < margin; i++) putchar(' ');
putchar('/'); putchar('\n');
break;
}
Baumdrucken(B->rechts, margin+8);
}
}
void postorder(KnotenZeiger B)
{
int i;
if (B != NULL){
postorder(B->links);
postorder(B->rechts);
if (B->d.type == value){
putchar('(');
for(i = 0; i < B->d.KnotenTyp.Blatt.Laenge;i++)
printf("%c",B->d.KnotenTyp.Blatt.Inhalt[i]);
putchar(')');
}
else
switch(B->d.KnotenTyp.Op){
case PlusOp: printf("+");break;
case MinusOp: printf("-"); break;
case MalOp: printf("*"); break;
case TeilOp:printf("/");
}
}
}
KnotenZeiger expression()
{
KnotenZeiger p,q;
q = term();
while ((Symbol == PlusOp) || (Symbol == MinusOp)){
p = (KnotenZeiger)malloc(sizeof(struct Knoten));
p->links = q;
p->d.KnotenTyp.Op = Symbol;
Symbol = holeSym();
p->d.type = operator;
p->rechts = term();
q = p;
}
return q;
}
KnotenZeiger term()
{
KnotenZeiger p,q;
q = factor();
while((Symbol == MalOp) || (Symbol == TeilOp)){
p = (KnotenZeiger)malloc(sizeof(struct Knoten));
p->links = q;
p->d.KnotenTyp.Op = Symbol;
Symbol = holeSym();
p->rechts = factor();
p->d.type = operator;
q = p;
}
return q;
}
KnotenZeiger factor()
{
KnotenZeiger p;
int i;
if (Symbol == LKlammer){
Symbol = holeSym();
p = expression();
if (Symbol != RKlammer) Fehler(2); //Missing right paren
else Symbol = holeSym();
}
else if ((Symbol == ConstOp) || (Symbol == VarOp)){
p = (KnotenZeiger)malloc(sizeof(struct Knoten));
for (i = 0; i < Laenge; i++)
p->d.KnotenTyp.Blatt.Inhalt[i] = RgabePuffer[i];
p->d.KnotenTyp.Blatt.Laenge = Laenge;
p->d.KnotenTyp.Blatt.Wert = tempVal;
p->links = NULL;
p->rechts = NULL;
p->d.type = value;
Symbol = holeSym();
}
return p;
}
int main()
{
printf("Bitte Infix Ausdruck eingeben und mit $ beenden: ");
liesCharString();
Symbol = holeSym();
ExpBaum = expression();
printf("Now the tree in postorder:\n");
postorder(ExpBaum);
putchar('\n');
printf("Wert des Ausdruckbaeums ist %d\n", evaluateExpression(ExpBaum));
putchar('\n');
printf("Nun wird der Baum gedruckt\n");
Baumdrucken(ExpBaum, 0);
if (Symbol != TermSym) Fehler(1);
return 0;
}
Das Programm verursacht folgende Ausgabe:
Bitte Infix Ausdruck eingeben und mit $ beenden:
(1 + 2) * (3 + 4)$
Now the tree in postorder:
(1)(2)+(3)(4)+*
Nun wird der Baum gedruckt
1
+
2
*
3
+
4
Prof. Raymond Zavodnik, 17.01.2003