Was ist der Unterschied der Definition und der Deklaration einer
Funktion? Kann es eine Definition einer Funktion ohne eine
Deklaration geben?
Bei der Definition wird der Speicherplatz erzeugt und reserviert. Bei der
Deklaration wird nur der Typ und der Name dem Compiler bekannt
gemacht, aber nichts angelegt.
Definition: mit Schlüsselwort (struct, union, enum), Name für den Datentyp,
Rumpf in geschwungenen Klammern (Beschreibung der Elemente)
Was ist eine einfach verkettete Liste und wie ist diese
aufgebaut?
In der Prog. versteht man unter Listen eine sequenzielle
Aneinanderreihung von Elementen, wobei die Reihenfolge explizit
festgehalten wird. Sie speichern zwei Arten von Informationen: Die
Elemente und eine Reihenfolge. Liste gestatten in einfachster Weise das
Verändern der Reihenfolge von Elementen sowie das Einfügen und
Entfernen von Elementen.Das Suche von Elementen ist jedoch schwierig
Die grundlegendste Art der Listen ist die einfach verkettete Liste (single
linked list; SLL). Eine einfach Verkettete Liste besitzt 2 Elemente 1. Den
Eintrag 2.Der Verweis auf das nächste Element,
Ein Knoten kann mit einer Struktur beschrieben werden. Die Verkettung
wird durch einen Index realisiert (entspricht Feldindex des verwendeten
Feldes). Der Kopf der Liste ist ebenfalls ein Index, und zwar der erste
Knoten der Liste. Das Ende ist mit einem ungültigen Index (-1) vereinbart.
Zweite Methode für das Markieren des Endes wäre ein Knoten, der auf sich
selbst zeigt. Erstere Methode hat den Vorteil, dass kein eigener Knoten für
das Listenende benötigt wird, aber den Nachteil, dass -1 ja ungültig ist.
Der Nachteil der Implementierung mit Feldern ist, dass ein eigenes
Speichermanagement für die Verwaltung der Knoten im Feld verwendet
werden muss, wegen eventueller Löcher.
Um ein neues Element, also einen neuen Knoten einzufügen, ist die
Verkettung an der
Einfügestelle aufzubrechen und auf den neuen Knoten zu verbiegen.
Weiters muss die
Verknüpfung des eingefügten Objekts auf den Rest der Liste gesetzt
werden. Vorteil
gegenüber Feldern ist die Einfachheit und Geschw. des Vorgangs. Es ist
egal wo der Knoten im Speicher liegt, die Reihenfolge ergibt sich aus der
Verkettung und nicht aus der Pos. im Speicher. Beim Einfügen des neuen
Elements muss der Vorgänger auf den neuen Knoten zeigen und dieser
muss auf den richtigen Nachfolger zeigen. Da der Vorgänger aber nicht
bekannt ist, wird nach einem bestehenden Knoten eingefügt. Dazu muss
die Position des einzufügenden Elements definiert werden. Dies kann
durch einen Index erfolgen, oder mit der Angabe des Elements im
Vorgänger bzw. Nachfolger geschehen. Es ist darauf zu achten, dass ein
gültiger Index übergeben wird, der auch wirklich frei ist. Komfortabler wäre
es den einzufügenden Datensatz als Parameter zu übergeben und die Fkt
muss sich selbst um die Verwaltung des Feldes kümmern(freier
Speicherplatz, Überprüfen der Feldlänge, verwalten von gelöschten
Einträgen);
Wie kann ein Element einer Liste entfernt werden?
Was ist ein Heap und für welche Operationen ist er gut
geeignet?
Eine Heap-Struktur (Heap=Haufen) genannt, ist eine sogenannte
Prioritätswarteschlange. Dies ist eine Datenstruktur, die es ermöglicht, den
größten Eintrag zu finden, zu löschen und neue Elemente einzufügen.
Aufgrund der besonderen Struktur von Heaps können eine Reihe von sehr
effizienten Algorithmen geschaffen werden, wie zB das Sortieren des
Heaps. Heaps sind binäre Bäume die in einem Feld realisiert werden,
wobei jeder Knoten die Heapbedingung erfüllt. Heps werden voar allem
zum Sortiern von Daten eingesetzt. Der Schlüssel jedes Knotens eines
Heaps ist größer als der seiner Nachfolger. Können gleiche Schlüssel
auftreten, so muss der Schlüsssel jedes Knotens größer oder gleich dem
seiner Nachfolger sein. Ein Heap ist ein vollständiger binärer Baum.
Was ist ein Hash und für welche Operationen ist er gut
geeignet? Wie groß sollte die Hash-Länge gewählt werden? Was
wird aus dem Hash, wenn man bei getrennter Verknüpfung
Hashlänge 1 wählt?
Hashes(hashing⇒zerhacken) eignen sich besonders für das Suchen von
Datensätzen und stellen einen guten Kompromiss zw. Zeit- u. Platzbedarf
dar. Das Verfahren beruht im Prinzip auf einem Feld, in dem die
Datensätze geeignet gespeichert werden und einen Index generieren, mit
dem Datensätze gesucht werden können. Dieses Zerhack-Methode läuft so
ab: N...Anzahl derDatensätze; Jedem Schlüssel wird ein geeigneter Index
zugeordnet, dieser Index liegt zw. 0 und N-1; damit reduziert sich die
Suche nach einen Datensatz auf den Zugriff auf das Element mit dem
Index i in einem Feld der Länge N. Diesen Index ermittelt die Hash-Fkt. Da
die Hashfkt aber nicht perfekt ist, (des Klumpat), kann es passieren, dass
die Indizes verschiedener Schlüssel gleich sind, wodurch eine
Kollisionsbeseitigung berücksichtigt werden muss. Datensätze mit
gleichem Index können in einfach verketteten Listen zusammengefasst
werden.
• Liste:
speichern Elemente in vorgegebener Reihenfolge; diese ist durch
Verkettung der
Elemente gegeben; Listen eigene sich zum Speichern und Bearbeiten von
Elementen
in bestimmter Reihenfolge; Einfügen u. löschen gestalte sich einfach; für
wahlfreien
Zugriff und Suchen vollkommen ungeeignet;
• Stapel:
speichert Elemente in der Reihenfolge ihres Eintreffens, diese kann nicht
verändert
werden; zwei Operatoren: Push, Pop; Reihenfolge durch FIFO oder LIFO
vorgegeben;
eignen sich zum temporären Speichern von Daten und zum Auflösen von
Rekursionen
in Algorithmen; sie sind für die Datenhaltung völlig ungeeignet, da ein
wahlfreier
Zugang auf ein beliebiges Element nicht möglich ist.
• Bäume:
speichern Hierarchien und Abhängigkeit von Daten; ermöglichen gezielte
Einordnen
und Suchen von Daten; werden zur Datenhaltung herangezogen; Sind
Alleskönner,für
manches aber nur mittelmäßig; Suchen erfolgt schneller als in Listen,
langsamer als in
Hahes; sind die Hierarchien von Daten unwichtig, sollen andere
Datenstrukturen
verwendet werden.
• Heaps:
sind spezielle binäre Bäume, die in einem Feld realisiert werden, wobei für
jeden
Knoten die Heapbedingung erfüllt sein muss. Bietet wenig Operationen,
wie Einfügen
und das Holen eines Elements mit dem größten Schlüssel; Hauptzweck
Prioritätswarteschlange und Suchen von Daten;
• Hashes:
speichern Daten in Feld ab, wobei der Index des Feldelements mit Hashfkt
ermittelt
wird. Qualität hängt von Hashfkt, Feldlänge und den Schlüssel der zu
speichernden
Daten ab. Eignen sich hervorragend zum Suchen von Daten u. zur
Datenhaltung.
Speichern keine Reihenfolge und Hierarchien sowie Abhängigkeit von
Daten.
Wird von mallco, calloc, realloc retournieren Null, wenn kein Speicher
alloziert werden kann. Dieser Fehler sollte immer abgefragt werden und
korrekt behandelt werden. Falls kein Speicher mehr angefordert werde
kann, muss die jeweilige Operation abgebrochen werden
und eine Fehlermeldung an den Benutzer abgegeben werden(nicht mehr
benötigten Speicher
freizugeben, Benutzer über gewünschtes Vorgehen fragen(speichern,
abbrechen,..).
Was ist wenn die Freigabe mit einer falschen Adresse erfolgt?
Char * puffer = malloc(10); free(puffer+1); Schwerer Fehler; eventuell
wird auf Speicher zugegriffen, auf den man nicht zugreifen kann.
Wird ein reguläres Feld, dass nicht dynamisch alloziert wurde, freigegeben,
ist das Ganze zwar syntaktisch korrekt, semantisch jedoch falsch, was der
Compiler allerdings nicht erkennen kann.
Nullzeiger dienen nur dazu, zu zeigen, ob ein Zeiger auf einen gültigen
Speicherbereich zeigt, oder nicht. Er darf nicht freigegeben werden, da der
Speicher der Adresse 0 nicht angefordert wurde und auch nicht
angefordert werden kann.
Der Speicher wird mit der Fkt malloc nicht initialisiert. Bevor dieser
Speicher gelesen werden kann, muss er initialisert werden, sonst steht
irgend a kas drin. Sicherheitslücke: Der Speicher wird nämlich weder beim
Speichern noch beim Freigeben gelöscht, die Daten stehen noch immer im
Speicher, nur halt jetzt frei verfügbar.
Auch hier kommt es zu memory leak. Beim Rücksprung aus einer Fkt wird
der lokale Zeiger zwar gelöscht, der allozierende Speicher kann aber
weder gelöscht noch verwendet werden.
Wird durch eine Fkt die Adresse eines Speichers korrekt zurückgegeben,
dort aber dann
verloren entsteht wieder ein leak.
Was wissen Sie über die Eigenschaften und die Terminologie von
Bäumen?
Unter einem Schlüssel wird ein Kriterium verstanden, mit dem ein Element
in einem Baum eingefügt od. gesucht werden kann. Er kann direkt ein
Datensatz oder dessen Attribut sein oder über eine Fkt ermittelt werden.
(Namen, Adresse die durch eine Struktur beschrieben werden).
Unter einem binären Baum versteht man Bäume, deren Knoten zwei
Nachfolger besitzen. Die Knoten enthalten das Element und die Verkettung
zum linken und rechten Nachfolger. Der Kopf muss separat gespeichert
werden. Unter einem vollständigen binären Baum versteht man ein
Bäumchen, dass von links nach rechts aufgefüllt wird. Ist eine Reihe
komplett, wird eine neue begonnen. Ein binärer Baum Mit Ni inneren
Knoten hat max. Ni +1 äußere Knoten. Die Höhe eine vollständigen
binären Baumes kann mittels log2(N)+1 ermittelt werden.
Ein binärer Suchbaum ist ein binärer Baum, bei dem alle Knoten, die sich
im linken
Unterbaum eines Knotens befinden(erreichbar über linke Verkettung),
einen kleineren
Schlüssel besitzen. Alle Knoten, die sich im rechten Unterbaum befinden,
haben größere
Schlüssel. Knoten mit gleichem Schlüssel werden per Konvektion auch
links abgelegt. Es ist aber nicht möglich, diesen Baum in einen
vollständigen binären Baum umzuwandeln, wenn
Was versteht man unter dem Traversieren von Bäumen?
Erklären Sie die
verschiedenen Traversierungsarten!
Unter traversieren versteht man, wie ein Baum durchlaufen wird. Diese
Aufgaben sind
besonders schön rekursiv zu lösen.
Es gibt die Level-Order-, die Preorder-, die Inorder-, und die
Postordertraversierung.
Leve-Order-Methode:
Kommt bei Heap Strukturen zum Einsatz. Die Elemente werden von links
nach rechts
durchlaufen.
PreorderTraversierung:
Wird jeder Knote Ebene für Ebene der Reihe nach bearbeitet. Bei heaps-
Strukturen
Zuerst wird der mittlere Knoten bearbeitet, anschließend wird der linke
Knoten und all
seine Subknoten durchlaufen. Im Anschluss daran wird der rechte Knoten
und seine
Subknoten traversiert. Diese Art der Traversierung wird gerne für
Syntaxbäume in der
PrefixNotation verwendet.
Inorder Traversierung:
Hier wird zuerst der linke Subbaum bearbeitet, dann der mittlere Knoten
und zuletzt
der rechte Subbaum. Wird zum Sortieren, Erzeugen und Darstellen von
Syntaxbäumen
in der Infix-Notation verwendet.
Postorder-Traversierung:
Hier wird zuerst der linke Subbaum bearbeitet, dann der rechte um zum
Schluss der
mittlere Knoten. Wird zur Auswertung von Syntaxbäumen verwendet.
Wird beim Einfügen von Elementen benötigt. Sie stellt sicher, dass ein
Element das die
Heap Bedingung verletzt(Schlüssel des Vorgängers ist kleiner), richtig
platziert wird und der Heap dadurch repariert wird. Diese Methode arbeitet
sich von unten durch den Heap(bottom up). Ein neues Element wird
anfangs nach dem letzten Eintrag eingefügt(Heap ist ja ein ausreichend
dimensioniertes Feld). Damit wurde die Heapbedingung allerdings verletzt.
Die Methode UpHeap repariert den Heap von unten hinauf, indem es
überprüft ob der Schlüssel des Vorgängers kleiner ist. Ist dies der Fall, so
werden die Elemente vertauscht. Das neue Element steigt den Heap
hinauf. Dies wird solange wiederholt, bis der Vorgänger größer oder gleich
groß ist, dann befindet sich das Element an der richtigen Position. Diese
Methode setzt voraus, dass der Heap vor dem Einfügen korrekt geordnet
war.
Die Methode DonwHeap arbeitet sich von oben nach unten durch den
Heap(bottom down). Se wird dann verwendet, wenn ein Knoten die Heap-
Bedingung in einem sonst korrekten Heap nicht erfüllt, weil mindestens
einer seiner Nachfolger größer ist. Um zu reparieren, wird der Schlüssel
des Elements mit seinem Nachfolger verglichen. Ist der Schlüssel von
einem der beiden Nachfolgers größer, tauschen die Elemente ihre
Plätze(mit dem größten Nachfolger wird getauscht). Sind sie gleich groß,
wird je nach Konvention der linke oder rechte Nachfolger zum Tauschen
gewählt. Die wird solange fortgesetzt, bis der Heap korrekt ist. Auch hier
wird ein korrekter Heap vorausgesetzt, mit nur einem inkorrekten Element.
Beim Entfernen würde eine Lücke in der Struktur des Heaps aufgerissen.
Um das letzte
Element des Heaps zu löschen, wird lediglich die Feldgröße des Heaps um
1 vermindert (also die Anzahl der Elemente des Heaps; nicht die
Feldlänge, die ja starr ist). Im ersten Schritt wird also das zu löschende
Element mit dem an der letzten Stelle getauscht. Danach wird die Länge
des Heaps um 1 reduziert, wodurch das Element entfernt wird. Die
Heapstruktur ist jetzt allerdings nicht mehr korrekt, es muss die Methode
DownHeap für das Tauschobjekt aufgerufen werden, die die verletzte
Struktur wieder repariert.
Wie kann ein Element in einen Heap eingefügt werden?
Das Einfügen eines Elements erfolgt mit der Methode UpHeap. Durch
mehrmaliges
Ausführen des Einfügevorgangs kann ein Heap gefüllt werden. Die
tatsächliche Position des Elements im Heap spielt keine Rolle(ein Element
könnte an mehren Stellen des Heaps sitzen, ohne die Bedingungen zu
verletzen). Der Nachteil dieser intuitiven Methode ist, dass sie langsam ist,
da für jedes eingefügte der Heap repariert werden muss.
Der Algorithmus sucht in einer Folge von Zahlen das kleinste Element und
tauscht es gegen das Element an der Position 0 aus. Danach sucht er das
zweitkleinste Element und tauscht es mit dem an Stelle 1 aus. (jeweils
kleinste Zahl muss gemerkt werden. Danach mittels Dreieckstausch
tauschen). Diese Verfahren ist langsam und einfach, für kleine
Datenmengen ausreichend, meist zum Sortieren von Daten in Dateien
verwendet. 2 Schritte 1. Suche das kleinste Element; 2. Tausche das
Element gegen die erste Position im verbleibenden Feld
Aufzählungsdatentyp(enum)
Aufzählungen werden in C verwendet, um mehrere Konstanten zu
definieren und zu einem Typ zu kombinieren. Alle Konstanten müssen
ganzzahlig sein. Diese Zahlen werden nacheinander aufsteigend mit 0
beginnend vergeben. Wenn man einen eigenen Wert angeben will muß
man KONSTANTE=Wert schreiben.
Enum e_Farbe{
FARBE_ROT=1,
FARBE_GRUEN=2,
FARBE_BLAU=4};
Erkläre "atexit"
Mittels atexit können mehrere Fkt registriert werden, die beim regulären
Programmende aufgerufen werden. Das reguläre Programmende wird
durch den Rücksprung aus der Fkt. main oder durch den Aufruf exit
ausgelöst. Werden mehrer Fkt registriert, erfolgt der Aufruf der Fkt in der
umgekehrten Reihenfolge ihrer Registrierung. Ist in stdlib.h deklariert und
erwarten Zeiger auf eine Fkt, die keinen Parameter als Rückgabewerte hat.
Fehlerarten
Modellfehler:
Bei jeder Modellbildung wird eine Abstraktion der Realität durchgeführt.
Dabei werden
bekannte und unbekannte Größen vernachlässigt und Vereinfachungen der
tatsächlichen Verhältnisse vorgenommen. Der dadurch entstehende Fehler
wird Modellfehler genannt.
Datenfehler:
Entstehen durch Ungenauigkeiten bei der Erfassung von Daten – z.B.
messen physikalischer Größen. (Messfehler – können abhängig von der
Messmethode meist abgeschätzt werden) Beispiele: Messungenauigkeiten,
syntaktisch falsche oder unvollständige Eingabedaten, fehlerhafte
Dateiformate, etc.
Verletzte Vorbedinung:
Zählt eigentlich zu den Datenfehlern. Können nur auf Grund einer
Vorbedingung auf
Korrektheit überprüft werden.
Anwenderfehler:
Entstehen durch fehlerhafte Bedienung, sowie fehlende oder inkorrekte
Eingaben – zählen ebenfalls zu den Datenfehlern. (Hilfestellung für den
Benutzer)
Fehlerhafter Programmaufruf:
Eigentlich ein Anwenderfehler – In vielen Betriebssystemen können
Programme mit einem Set an Parametern oder Optionen gestartet werden.
Numerische Fehler:
Numerische Methoden liefern nur mit einer Fehlerabschätzung eine
sinnvolle Aussage
Speicherproblem:
Speichermangel ist ein sehr leicht zu erkennendes, aber schwer zu
korrigierendes Problem:
1.) Programm beenden
2.) Das Programmwartet bis wieder Speicher frei wird
3.) Wenn möglich, Speicher freigeben.
Fehlerhafter Speicherzugriff:
Besonders kritisch, entweder sofortiger Programmabbruch (SIGSEGV),
oder es werden falsche Daten gelesen oder überschrieben.
Fehlerhafte Module:
Module oder Bibliotheken – Fremd-Software muss nicht fehlerfrei sein...
Dead lock:
Bei der Kommunikation paralleler Prozesse kann es vorkommen, dass zwei
Prozesse auf
Daten des jeweils anderen warten, wodurch der Programmablauf stoppt –
kann während der Laufzeit kaum behoben werden. (Außer es ist erlaubt.
Dann kann z.B. nur eine gewisse Zeit gewartet werden),
Wann verwendet man Zeiger auf Zeiger?
Schlechter Programmierstil sehr unübersichtlich, kann bei Swapfunktionen
angewendet werden.
Der Algorithmus sucht in einer Folge von Zahlen das kleinste Element und
tauscht es gegen das Element an der Position 0 aus. Danach sucht er das
zweitkleinste Element und tauscht es mit dem an Stelle 1 aus. (jeweils
kleinste Zahl muss gemerkt werden. Danach mittels Dreieckstausch
tauschen)
Bubblesort:
Der Algorithmus vergleicht der Reihe nach zwei benachbarte Elemente
und vertauscht sie, falls sie in der falschen Reihenfolge vorliegen. Dieser
Vorgang wird solange wiederholt, bis keine Vertauschungen mehr nötig
sind. Hierzu sind in der Regel mehrere Durchläufe erforderlich. Je
nachdem, ob auf- oder absteigend sortiert wird, steigen die kleineren oder
größeren Elemente wie Blasen im Wasser (daher der Name) immer weiter
nach oben, d.h. an das Ende der Reihe.
Heap-Sort:
Der eigentliche Sortieralgorithmus nutzt die Tatsache aus, dass die Wurzel
eines Heaps
stets der größte Knoten ist. Da im fertig sortierten Array der größte Wert
ganz rechts
stehen soll, vertauscht man das erste mit dem letzten Arrayelement. Das
Element am Ende des Arrays ist nun an der gewünschten Position und
bleibt dort. Jetzt
wird die Heaplänge um 1 verringert und die Methode DownHeap
angewandt (Der um 1
verringerte Heap wird repariert). Das größte Element steht jetzt hinten im
Feld. Dieser
Vorgang wird so oft wiederholt bis alle Elemente entfernt wurden.
Erkläre „Quicksort“
Vorteile: Sehr schnell, besonders effizient bei vollkommen unsortierten
Datenmengen,
geringer Speicherverbrauch, relativ einfach zu implementieren. Quicksort
erwartet die zu sortierenden Daten in einem Feld. Das grundlegende
Verfahren arbeitet rekursiv – in jedem Schritt wird die korrekte Position
eines gegebenen Elements gesucht und dieses
eingeordnet. (Die Wahl des Elementes ist beliebig) Nach einem Durchgang
ist aber nicht nur ein Element richtig plaziert, vielmehr wurden auch
alle Elemente rechts und links davon „grobgeordnet“ (links alles kleiner,
rechts alles größer) QuickSort wählt ein Element aus der zu sortierenden
Liste aus (Pivotelement) und zerlegt die Liste in zwei Teillisten, eine untere,
die alle Elemente kleiner und eine obere, die alle Elemente gleich oder
größer dem Pivotelement enthält. Dazu wird zunächst ein Element von
unten gesucht, das größer (oder gleichgroß) als das Pivotelement und
damit für die untere Liste zu groß ist. Entsprechend wird von oben ein
kleineres Element als das Pivotelement gesucht. Die beiden Elemente
werden dann vertauscht und landen damit in der jeweils richtigen Liste.
Der Vorgang wird fortgesetzt, bis sich die untere und obere Suche treffen.
Damit sind die oben erwähnten Teillisten in einem einzigen Durchlauf
entstanden. Suche und Vertauschung können in-place durchgeführt
werden. Die noch unsortierten Teillisten werden über denselben
Algorithmus in noch kleinere Teillisten zerlegt (z.B. mittels Rekursion) und,
sobald nur noch Listen mit je einem Element vorhanden sind, wieder
zusammengesetzt. Die Sortierung ist damit abgeschlossen.
typedef enum
{
ERR_NO_ERROR = 0,
....
....
}ERR_Type_t;
typedef struct
{
ERR_Type_t errNum;
const char *errStr;
}ERR_t;
Schlüssel
Unter einem Schlüssel wird ein Kriterium verstanden, mit dem ein Element
in einem Baum eingefügt oder darin gesucht werden kann. Der Schlüssel
kann entweder direkt der Datensatz oder ein Attribut des Datensatzes
sein. Er kann auch mittels einer Funktion aus einem Datensatz errechnet
werden.
Sortierverfahren "Maximumsuche"