Algorithmen und Datenstrukturen in C++: Von der Theorie zu realen Projekten
Die Beherrschung von Algorithmen und Datenstrukturen ist die Grundlage, die einen Anfänger von einem professionellen Ingenieur unterscheidet. In C++, einer der leistungsstärksten und effizientesten Sprachen, ermöglicht das Verständnis dieser Konzepte, nicht nur funktionierenden, sondern schnellen, skalierbaren und zuverlässigen Code zu schreiben. In diesem Artikel analysieren wir die wichtigsten Datenstrukturen und Algorithmen in der Praxis, mit fertigen Beispielen, die Sie sofort in Ihren Projekten verwenden können.
Wir werden uns nicht mit abstrakter Theorie aufhalten – jeder Abschnitt enthält konkreten C++-Code und Erklärungen, wie und warum eine bestimmte Konstruktion funktioniert. Das Material richtet sich an Entwickler, die bereits mit der C++-Syntax vertraut sind, aber ihr Wissen über Algorithmen systematisieren möchten.
1. Grundlegende Datenstrukturen: Arrays, Listen und dynamische Strukturen
Beginnen wir mit dem Nötigsten. In C++ bietet die Standardbibliothek (STL) leistungsstarke Container, aber das Verständnis ihrer internen Funktionsweise ist für die Optimierung entscheidend.
Dynamisches Array (std::vector)
std::vector ist der am häufigsten verwendete Container. Er stellt ein dynamisches Array dar, das sich beim Hinzufügen von Elementen automatisch erweitert. Es ist jedoch wichtig, die amortisierte Komplexität zu beachten: Das Einfügen am Ende ist im Durchschnitt O(1), aber das Einfügen in der Mitte ist O(n).
#include <iostream>#include <vector>
int main() { std::vector<int> numbers = {10, 20, 30, 40}; // Hinzufügen am Ende numbers.push_back(50); // Einfügen am Anfang (teure Operation) numbers.insert(numbers.begin(), 5); // Löschen des letzten Elements numbers.pop_back(); for (int num : numbers) { std::cout << num << " "; } // Ausgabe: 5 10 20 30 40 return 0;}Verkettete Liste (std::list)
Im Gegensatz zum Vektor ist std::list als doppelt verkettete Liste implementiert. Sie ist ideal, wenn Sie häufig Elemente in der Mitte des Containers einfügen oder löschen – diese Operationen werden in O(1) ausgeführt. Der Preis dafür ist das Fehlen eines direkten Zugriffs über einen Index.
#include <list>
std::list<std::string> tasks = {"Code schreiben", "Tests durchführen"};tasks.push_front("Architektur planen");tasks.push_back("Dokumentieren");
// Einfügen in der Mitteauto it = tasks.begin();std::advance(it, 2); // Iterator auf das 2. Element verschiebentasks.insert(it, "Refactoring");Stack und Queue (std::stack, std::queue)
Dies sind Container-Adapter, die die Funktionalität des Basiscontainers einschränken. Ein Stack arbeitet nach dem LIFO-Prinzip (Last In, First Out), eine Queue nach dem FIFO-Prinzip (First In, First Out).
#include <stack>#include <queue>
// Stackstd::stack<int> st;st.push(1);st.push(2);st.pop(); // Entfernt 2
// Queuestd::queue<int> q;q.push(10);q.push(20);q.pop(); // Entfernt 10 (das erste)2. Sortier- und Suchalgorithmen: Von Bubble Sort bis zur binären Suche
Sortieren ist eine klassische Aufgabe, aber in der Praxis ist es wichtig zu wissen, wann man integrierte Algorithmen verwendet und wann man eigene schreibt.
QuickSort – Implementierung in C++
Die STL verwendet eine introspektive Sortierung (eine Mischung aus QuickSort, HeapSort und InsertionSort). Aber zum Verständnis schreiben wir einen klassischen QuickSort:
#include <iostream>#include <vector>
int partition(std::vector<int>& arr, int low, int high) { int pivot = arr[high]; int i = low - 1; for (int j = low; j < high; j++) { if (arr[j] < pivot) { i++; std::swap(arr[i], arr[j]); } } std::swap(arr[i + 1], arr[high]); return i + 1;}
void quickSort(std::vector<int>& arr, int low, int high) { if (low < hi