ADT Stapel

Werbung
Vorlesung Datenstrukturen
ADT Stapel
Verwendung von Stapeln
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 304
Wiederholung - Stapel
Stapel
Ein Stapel (auch als Keller oder Stack bezeichnet) ist eine Datenstruktur, die eine
Menge von Daten aufnehmen und in umgekehrter Reihenfolge wieder abgeben kann.
Funktionen
Eine Stapel-Datenstruktur kennt im Wesentlichen nur zwei Operationen:
push
Ablegen eines Elements auf dem Stapel
pop
Entnehmen des zuletzt abgelegten Elements vom Stapel
Prinzip
Das zuletzt eingefügte Element wird als erstes wieder entnommen. Diese Art des
Vorgehens bezeichnet man auch als LIFO-Prinzip (Last In, First Out).
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 305
Schnittstellendefinition
Wunsch
Es soll ein Stapel zur Verarbeitung von int-Werten definiert werden.
Schnittstelle
class Stack {
Stack()
// Konstruktor
~Stack()
// Destruktor
bool isEmpty()
// Test auf leeren Stapel
void push(int data)
// Ablage eines int-Werts
int
// Entnehmen eines int-Werts
pop()
}
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 306
Implementierung
Auswahl der internen Datenstruktur
Wir könnten uns z.B. zwischen einer statischen Variante mittels Feld oder einer dynamischen
Implementierung mittels Liste entscheiden.
Eine dynamische Datenstruktur erscheint adäquater, da eine typische Eigenschaft eines
Stapels die Speicherung einer beliebigen Elementanzahl ist und wir auf diesem Weg zudem
nur den tatsächlich benötigten Speicher belegen.
Algorithmuskriterien
Wir müssen jederzeit Zugriff auf das oberste Element haben und nach dessen Entnahme auf
das zweitoberste zugreifen können usw.
Bei Ablage eines neuen Elementes auf dem Stapel muss deshalb sichergestellt werden, dass
eine Verbindung zwischen dem neuen Element und dessen Vorgänger existiert.
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 307
ADT Stack - privater Teil
class Stack {
// Stapel für int-Werte
private:
struct node {
int
// interne Realisierung des Stapels mit Liste
data;
node* next;
};
node* head;
Dr. Frank Seifert
// ADT „kennt“ das Kopfelement der Liste
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 308
Stack - Konstruktor & Destruktor
public:
Stack() {
// Konstruktor erzeugt einen
head = 0;
// leeren Stapel
}
~Stack() {
// Destruktor löscht alle Elemente,
node* temp = head; // die sich evtl. noch im Stapel
while (temp) {
// befinden und gibt den Speicher frei
head = head->next;
delete temp;
temp = head;
}
}
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 309
ADT Stack - Methoden
bool isEmpty() {
// Test auf leeren Stapel
return (head == 0);
}
void push(int data) {
// Ablegen eines int-Elements
node* temp = new node(); // Prinzip: Einfügen am Listenanfang
temp->data = data;
temp->next = head;
head = temp;
}
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 310
ADT Stack - Methoden
int pop() {
// Entnahme eines int-Elements
if (isEmpty()) {
printf("Fehler! Entnahme von leerem Stapel!");
return 0;
// besser wäre ein Programmabbruch!
}
else {
// Entnahme am Listenkopf
node* temp = head;
int data = head->data;
head = head->next;
delete temp;
return data;
}
}
};
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 311
Bewertung: Stapel als ADT
Vorteile
Mit Hilfe des vorgestellten abstrakten Datentyps „Stapel“ kann eine Menge von
Elementen mit lediglich drei Operationen (push, pop und isEmpty)
verarbeitet werden.
Der ADT abstrahiert so auf einfachem Weg die zugrundeliegende komplexe
und fehleranfällige Listenverarbeitung (bei Listenimplementierung des Stapels)
sowie die nötige Freispeicherverwaltung.
Falls erforderlich, könnte zu einem späteren Zeitpunkt eine andere
Implementierung erfolgen, ohne dass der Nutzer des ADTs „Stapel“ dies
bemerkt.
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 312
Templates
Potentielles Problem
Mit den bisher kennengelernten ADT-Konzept müssten wir für Stapel verschiedener Elementtypen
(z.B. double oder char) jeweils einen vollständigen individuellen ADT entwickeln (was zwar nur die
jeweilige Ersetzung des Elementtyps verlangen würde, aber aufwändig und redundant erscheint).
Abhilfe
Mit dem Template-Konzept von C++ können gleichartige Operationen auf verschiedenen
Datentypen nach den Vorgaben einer „Schablone“ automatisch implementiert werden.
Syntax
template <class Name>
Anwendung und Funktionsweise
Jedes Vorkommen eines generischen Datentyps wird durch Name ersetzt. Beim Aufruf einer
Template-Funktion bestimmt der Compiler anhand des Typs der Parameter, welche Funktion
generiert und ausgeführt werden muss ➔ Weitere Details siehe Sprachbeschreibung von C++.
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 313
Verwendung von Stapeln
Test auf korrekte Klammerung
Addition großer Zahlen
Wertbestimmung arithmetischer Ausdrücke
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 314
Test auf korrekte Klammerung (1)
Situation
Es existieren verschiedene Klammerformen in C, z.B. ( ) [ ] { }. Diese können
kombiniert und (nach gewissen Regeln) auch verschachtelt auftreten.
Es soll vereinfachend für runde, eckige und geschweifte Klammerungen getestet werden, ob
eine C-Anweisung korrekt geklammert ist, d.h. ob einer öffnenden immer eine schließende
Klammer gleichen Typs folgt.
Beispiele
korrekte geklammerte Anweisungen:
fehlerhafte geklammerte Anweisungen:
a = b + (c - d) * (e - f);
a = b + (c - d) * (e - f));
g[1] = h[i[0]] + (j + k) * l;
g[1] = h[i[0]] + j + k) * l;
while (m < (n[2] + o)) { p = 3; r = 4; }
while (m < (n[2) + o]) { p = 3; r = 4; }
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 315
Test auf korrekte Klammerung (2)
Gegeben
• Zeichenkette, die die zu untersuchende C-Anweisung enthält
• ADT Stapel vom Typ charStack (analog intStack, nur mit char als Elementtyp)
Gesucht
Funktion, die bei korrekter Klammerung true und sonst false als Funktionswert zurückliefert.
Algorithmusbedingung
Für einen korrekt geklammerten Ausdruck KA muss gelten: Jeder öffnenden Klammer folgt die
korrespondierende schließende Klammer oder wiederum ein korrekter Klammerausdruck KA:
KA = ( KA ) | [ KA ] | { KA } | KA KA | NKA
NKA = NKA Nichtklammersymbol | ε
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 316
Algorithmusidee
Für jedes Zeichen c der Eingabezeichenkette untersuchen wir:
• ist c kein Klammersymbol, dann ignorieren wir es
• ist c eine öffnende Klammer (also (, [ oder {), dann legen wir c auf dem Stapel ab
• ist c eine schließende Klammer (also ), ] oder })
- dann muss c zur zuletzt auf dem Stapel abgelegten öffnenden Klammer korrespondieren
- deshalb entnehmen wir das zuletzt auf dem Stapel abgelegte Zeichen und vergleichen es mit c
- stimmen die beiden Zeichen nicht überein, dann liegt eine fehlerhafte Klammerung vor und wir
geben eine entsprechende Fehlermeldung aus
Sind alle Zeichen der Eingabezeichenkette abgearbeitet, darf auf dem Stapel keine Klammer mehr
abgelegt sein, d.h. wenn der Stapel nach der Abarbeitung keine Elemente mehr enthält, dann ist die
Eingabezeichenkette korrekt geklammert.
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 317
Quelltext
bool delimiterMatching(char* str) {
Stack s;
// Stack mit Elementtyp char
char c;
while (c = *(str++)) {
// Iteration über Zeichenkette
if (c=='(' || c=='[' || c=='{')
// Öffnende Klammern?
s.push(c);
else if (c==')' || c==']' || c=='}') {// Schließende Klammern?
if (c==')') c = '(';
else if (c==']') c = '[';
else if (c=='}') c = '{';
if (c != s.pop())
// Keine korrespondierenden Klammern
return false;
}
}
return s.isEmpty();
Dr. Frank Seifert
// Stapel muss am Ende leer sein
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 318
Verwendung von Stapeln
Test auf korrekte Klammerung
Addition großer Zahlen
Wertbestimmung arithmetischer Ausdrücke
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 319
Addition großer Zahlen (1)
Aufgabe
Es sollen zwei beliebig große natürliche Zahlen miteinander addiert werden und das exakte
Ergebnis der Addition ausgegeben werden.
Für diese Aufgabe stehen keine vordefinierten arithmetischen Datentypen zur Verfügung, da
deren Wertebereich zu gering für eine exakte Rechenoperation wäre.
Lösungsidee
Wir algorithmieren die „schriftliche“ Addition zweier Zahlen. Das heißt, wir addieren beginnend
mit den letzten Stellen die jeweils bezüglich Zehnerpotenzen korrespondierenden Ziffern der
beiden Zahlen.
Beispiel
65 + 943
65
+943
Merke 1
Dr. Frank Seifert
65
+943
8
Merke 1
65
+943
08
Merke 1
100
65
+943
008
Merke 1
Vorlesung Datenstrukturen - Sommersemester 2016
1000
65
+943
1008
Merke 1
1008
Folie 320
Addition großer Zahlen (2)
Algorithmusidee
• Einlesen der Ziffern der ersten Zahl und Ablegen der Ziffern auf dem ersten Stapel
• Einlesen der Ziffern der zweiten Zahl und Ablegen der Ziffern auf dem zweiten Stapel
• Solange sich auf mindestens einem Stapel eine Ziffer befindet
- Entnimm aus jedem Stapel eine Ziffer (bei leerem Stapel verwende Ziffer 0)
- addiere die Ziffern und einen evtl. gemerkten Übertrag
- lege die letzte Stelle des Ergebnisses auf einem dritten Stapel ab
- Merke einen möglichen Übertrag
• Hole die Ziffern vom dritten Stapel und gib sie aus
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 321
Addition großer Zahlen (2)
void bigAddition(char str[]){ // Eingabezeichenkette in Form Zahl1+Zahl2
int i = 0;
Stack s1, s2, s3;
// Stack mit Elementtyp int
while (str[i] && str[i] != '+') s1.push(str[i++] - '0');
if (str[i] == '+') i++;
while (str[i]) s2.push(str[i++] - '0');
int value1, value2, sum, memory = 0;
while ((!s1.isEmpty()) || (!s2.isEmpty())) {
if (s1.isEmpty()) value1 = 0; else value1 = s1.pop();
if (s2.isEmpty()) value2 = 0; else value2 = s2.pop();
sum = value1 + value2 + memory;
memory = sum / 10;
s3.push(sum % 10);
}
if (memory) s3.push(memory);
while (!s3.isEmpty()) printf("%d", s3.pop());
}
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 322
Verwendung von Stapeln
Test auf korrekte Klammerung
Addition großer Zahlen
Wertbestimmung arithmetischer Ausdrücke
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 323
Darstellung von Ausdrücken
Infixnotation
Bezeichnet die Notation eines Ausdrucks mittels Operand Operator Operand, wobei ein
Operand rekursiv wieder analog definiert sein kann.
Um eine eindeutige Ausführungsreihenfolge der Operatoren festzulegen, muss diese
durch Klammerung erzwungen werden.
Deshalb ist ein vollständiger Infixausdruck als ( Operand Operator Operand ) definiert,
wobei ein möglicher Infixausdruck als Operand selbst wieder vollständig sein muss.
Postfixnotation
Hierbei folgt der Operator den Operanden, also Operand Operand Operator
Der Vorteil der Postfixdarstellung besteht darin, dass bei geschachtelten Ausdrücken die
Position des Operators die Berechnungsreihenfolge bestimmt, wodurch generell keine
Klammern mehr nötig sind.
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 324
Berechnung von Ausdrücken
Wunsch
Wir wollen den Wert arithmetischer Ausdrücke berechnen.
Vereinfachung
Berechnung von Postfixausdrücken, da hierbei keine Klammerungen zu berücksichtigen sind.
Wie erhält man Postfixausdrücke
Nach der Vorstellung des Algorithmus zur Berechnung von Postfixausdrücken stellen wir einen
Algorithmus vor, der vollständig geklammerte Infixausdrücke in Postfixausdrücke umwandelt.
Realisierung
Für beide Aufgaben sind Stapel prädestiniert.
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 325
Berechnung eines Postfixausdrucks
Allgemeiner Algorithmus
Solange die Eingabe nicht leer ist
Lese Eingabe e
Wenn e ein Operand o ist, dann lege o auf dem Stapel ab
Wenn e ein Operator p ist, dann
hole die letzten beiden Operanden o1 und o2 vom Stapel
berechne o1 p o2
lege das Berechnungsergebnis auf dem Stapel ab
Das letzte Element auf dem Stapel ist das Ergebnis des Postfixausdrucks
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 326
Berechnung ganzzahliger Postfixausdrücke
int postfix(char p[]) { // Postfixausdruck, Leerzeichen trennt Operanden
Stack s;
int i = 0, v1, v2;
bool flag;
while (p[i]) {
// Stack mit Elementtyp int
flag = true;
if (p[i] == '+') s.push(s.pop() + s.pop()); // kommutativer Operator
if (p[i] == '*') s.push(s.pop() * s.pop()); // kommutativer Operator
if (p[i] == '-') { v2 = s.pop(); v1 = s.pop(); s.push(v1 - v2); }
if (p[i] == '/') { v2 = s.pop(); v1 = s.pop(); s.push(v1 / v2); }
if ((p[i] >= '0') && (p[i] <= '9')) { s.push(0); flag = false; }
while ((p[i] >= '0') && (p[i] <= '9'))
s.push(10 * s.pop() + (p[i++] - '0'));
if (flag) i++;
}
return s.pop();
}
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 327
Konvertieren von Infix nach Postfix
Wir haben gesehen, wie wir mit Hilfe eines Stapels einen Postfixausdruck auswerten können.
Wir wollen nun einen Infix- in einen Postfixausdruck umwandeln. Dazu sei ein vollständiger, d.h.
vollständig geklammerter Infixausdruck wie folgt gegeben:
Infixausdruck = ( Operand Operator Operand )
Operand = Infixausdruck | Zahl
Algorithmusidee
Für jedes gelesene Zeichen c des Infixausdrucks:
• Wenn c ein Operator ist, dann lege c auf dem Stapel ab
• Wenn c ein Operand ist, dann gib c aus
• Wenn c eine öffnende Klammer ist, dann ignoriere c
• Wenn c eine schließende Klammer ist, dann entnimm Operator vom Stapel und gib ihn aus
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 328
Infix-Postfix-Konvertierung - Quelltext
void infix2postfix(char e[]) {// Infix- wird mit Postfixausdruck überschrieben
Stack s;
// Stack mit Elementtyp char
int pi = 0;
// Postfixindex
int ii = 0;
// Infixindex
while (e[ii]) {
if (e[ii] == ')')
e[pi++] = s.pop();
if ((e[ii]=='+') || (e[ii]=='*') || (e[ii]=='-') || (e[ii]=='/'))
s.push(e[ii]);
if ((e[ii] >= '0') && (e[ii] <= '9'))
e[pi++] = e[ii];
ii++;
}
e[pi] = '\0';
// Endmarkierung der Zeichenkette
}
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 329
Ende der Vorlesung
Dr. Frank Seifert
Vorlesung Datenstrukturen - Sommersemester 2016
Folie 330
Zugehörige Unterlagen
Herunterladen