Wir bauen (konzeptionell) ein kleines Tool: z.B. digitaler Notizzettel
Es kommen „Ereignisse“/Aufgaben rein, werden verarbeitet und teilweise rückgängig gemacht
Anforderungen (Operationen):
Klassisches Beispiel für lineare Datenstruktur: (Keller-)Stapel
Funktionsweise zur Genüge bekannt (→ Kapitel Zeiger)
→ Hier nicht noch mal Thema
push: legt Element oben auf den Stapelpop: entfernt oberstes Element vom Stapeltop: gibt das oberste Element vom Stapel zurückempty: prüft, ob Stapel leer istsize: gibt Anzahl der Elemente zurück
std::stack#include <stack>
#include <iostream>
using std::cout, std::endl;
int main() {
std::stack<int> int_stack;
int_stack.push(42);
int_stack.push(4711);
cout << "Top: " << int_stack.top() << endl; // Element wird *nicht* entfernt
int_stack.pop(); // Wegwerfen. Rückgabe 'void' -> Zugriff nur mit top()
cout << "New Top: " << int_stack.top() << endl;
}
enqueue: fügt Element hinten andequeue: entfernt vorderstes Elementfront: gibt vorderstes Element zurückback: gibt letztes Element zurückempty: prüft, ob Warteschlange leer istsize: gibt Anzahl der Elemente zurück
std::queue#include <queue>
#include <iostream>
using std::cout, std::endl;
int main() {
std::queue<int> int_queue;
int_queue.push(42);
int_queue.push(4711);
int_queue.push(5);
int_queue.push(3);
cout << "Front: " << int_queue.front() << endl; // Analog zu Stack: Kein Entfernen
cout << "Back: " << int_queue.back() << endl; // Analog zu Stack: Kein Entfernen
int_queue.pop(); // Entfernen ohne Verwenden
cout << "New Front: " << int_queue.front() << endl;
}
push_front/push_back und analog pop_front/pop_back#include <deque>
#include <iostream>
using std::cout, std::endl;
int main() {
std::deque<int> int_deque;
int_deque.push_front(42);
int_deque.push_back(4711);
int_deque.push_front(5);
int_deque.push_back(3);
cout << "Front: " << int_deque.front() << endl;
cout << "Back: " << int_deque.back() << endl;
int_deque.pop_back();
cout << "New Back: " << int_deque.back() << endl;
}
Jedes Element kennt Inhalt
Jedes Element kennt Nachfolger
→ Kettenglieder bzw. Knoten(Nodes)
std::listinsert: fügt Element an Position X einerase: entfernt vorderstes Element#include <list>
#include <iostream>
using std::cout, std::endl;
int main() {
std::list<int> l;
l.push_front(5);
l.push_front(10);
l.push_back(20);
auto pos = l.end();
l.insert(pos, 42);
cout << "Back: " << l.back() << endl;
l.pop_back();
cout << "Back: " << l.back() << endl;
}
Suchen von Elementen
→ B-Trees (Datenbanken), Rot-Schwarz-Bäume
Aufteilen von Raum
→ Binary Space Partitioning (z.B. in Videospielen)
Min-/Max-Heaps
→ Grundlage von Prioritätswarteschlangen
Wurzel hat keine Vorfahren
Blätter hingegen haben keine Kinder
→ Linkes und rechtes Kind
Darf aber auch nur einen/keinen Kindknoten besitzen
Pfad: Folge von zusammenhängenden Eltern-Kind-Knoten
Definition erfolgt rekursiv
0, da keine Knoten vorhanden
Mathematische Abbildung, erzeugt aus Daten einen sogenannten Hashwert
→ eindeutiger Indentifizierer
Indexierung: unter anderem Datenbanken, Caches, Dictionaries
Kryptographie: z.B. Hashwert aus Passwort berechnen, Abspeichern des Hashes (bcrypt)
[Dateivergleich: Prüfen der Integrität von großen Dateien durch separaten Hashwert (SHA1, MD5)
Bloom-Filter: unter anderem Spam-Filter, schnelles Nachschauen von Symbolen
→ Linker des GCC-Compilers bei dynamischen Bibliotheken
Blockchain: Bei Bitcoins wird fortwährend SHA-256 (kryptographische Hashfunktion) berechnet
Speichern für jeden Hash assoziierte Daten
Hashkollisionen: Erstellen einer Linked-List für diesen Hashwert
Gute Hashfunktion → wenig/keine Kollisionen
Unwahrscheinliche Kollision
→ i.d.R. wenige Schritte für Nachschlagen
→ konstante Zeit
Dictionaries sind gut für schnelles und zuverlässiges Nachschauen geeignet
HashMap<K,V>Dictionary<K,V>dict() bzw. {} (sehr starke Verwendung)std::unordered_mapstd::map
std::map ist allerdings mit Red-Black-Bäumen umgesetzt (→ keine Hashtabelle)std::unordered_map#include <iostream>
#include <string>
#include <unordered_map>
using std::cout, std::endl;
int main() {
std::unordered_map<int, std::string> int_str_dict = {
{1, "Eins"}, // Key und Value
{2, "Zwei"},
};
// Insert
int_str_dict.insert({42, "Zweiundvierzig"});
int_str_dict.insert({4711, "Siebenvierzigelf"});
cout << int_str_dict[42] << endl; // Lookup: Version Nr.1
// Fallstrick: operator[] erzeugt in C++ automatisch neues Element,
// falls es nicht vorhanden ist. Besser: Methode at()
try { // at() kann Exception werfen!
int_str_dict.erase(1); // Delete
cout << int_str_dict.at(1) << endl; // Lookup: Version Nr.2
} catch(std::out_of_range &err) {
cout << "Error: " << err.what() << endl;
}
}
Dictionaries werden Euch noch oft begegnen. Es lohnt sich, sie zu kennen!
Übrigens: Ein Binärbaum ist auch nur ein spezieller Graph 😊
0 in Matrix1, sonst 0
<algorithm>, numeric und memory zu finden (Link)
std::accumulatestd::accumulate: Addiert die Zahlenwerte in einem STL-Container automatisch#include <iostream>
#include <vector>
#include <numeric>
using std::cout, std::endl;
int main() {
std::vector<int> vec = {1, 3, 5, 10};
// Parameter 3: 0 ist der Startwert der Summe
int result = std::accumulate(vec.begin(), vec.end(), 0);
cout << "Sum of vector: " << result << endl;
}
std::count#include <iostream>
#include <vector>
#include <algorithm>
using std::cout, std::endl;
int main() {
std::vector<int> vec = {1,2,3, 3, 5, 10, 14, 17, 3, 18, 22, 42};
int count_3 = std::count(vec.begin(), vec.end(), 3);
cout << "Number of 3: " << count_3 << endl;
}
std::sortstd::greater() und ggf. Operator operator< implementieren#include <iostream>
#include <vector>
#include <algorithm>
using std::cout, std::endl;
int main() {
std::vector<int> vec = {5, 2, 1, 10, 4, 42};
cout << "Original: \t";
for (const int& i: vec) {
cout << i << ", ";
}
std::sort(vec.begin(), vec.end());
cout << "\nSorted: \t";
for (const int& i: vec) {
cout << i << ", ";
}
// std::greater() (und std::less()) rufen Vergleichsoperator operator< auf
std::sort(vec.begin(), vec.end(), std::greater<int>()); // Default für int generieren
cout << "\nReverse: \t";
for (const int& i: vec) {
cout << i << ", ";
}
}
std::transform#include <iostream>
#include <vector>
#include <algorithm>
using std::cout, std::endl;
template <typename T>
T double_val(T value) {
return value * 2;
}
int main() {
std::vector<int> vec = {5, 2, 1, 10, 4, 42};
cout << "Original: \t";
for (const int& i: vec) {
cout << i << ", ";
}
std::transform(vec.begin(), vec.end(), vec.begin(), double_val<int>);
cout << "\nTransformed: \t";
for (const int& i: vec) {
cout << i << ", ";
}
}
Ein sehr kleiner Einblick in die Welt der Algorithmen und Datenstrukturen
Für ein gutes Programm ist auch ein Verständnis dieses Bereichs notwendig
Anders ausgedrückt: Gute Entwickler:innen müssen neben dem Programmieren auch verstehen, was entwickelt werden muss und wie dies effizient möglich ist
→ Mischung aus gutem Verständnis der Programmiersprache und der Algorithmen
Wir waren hiermit gestartet …
Ihr schreibt bestimmt irgendwann einmal eine Abschlussarbeit 😀
Wenn Euch Hardware-nahe und/oder Betriebssystem-nahe Themen interessieren, denkt gerne an uns! 😀
Wir interessieren uns auch für Themen aus der E-Technik.
Meldet Euch dann gerne bei uns: https://sys.cs.tu-dortmund.de/
🤞 Viel Erfolg 🤞
und vielen Dank für die Aufmerksamkeit! 🙏
Am Donnerstag gibt es eine Fragestunde!
Bitte bereitet Fragen zum Stoff vor.