Einleitung
Nicht-modifizierende Algorithmen
Modifizierende Algorithmen
Löschende Algorithmen
Die Standard-Bibliothek stellt eine große Anzahl von Algorithmen zur Verfügung, um Elemente in einem Container zu bearbeiten. Einige der Algorithmen haben Sie in den vorherigen Lektionen ja schon kennen gelernt, wie z.B. for_each(...).
Die Algorithmen lassen sich in folgende Kategorien einteilen:
In dieser und der nachfolgenden Lektion finden Sie eine Übersicht über die Algorithmen. Die Algorithmen werden nur kurz beschrieben. Eine ausführliche Referenz finden Sie in der Online-Dokumentation zur Standard-Bibliothek. Jedoch sollten Sie nach dem Durchlesen dieser Lektion und den dort aufgeführten Beispielen in der Lage sein, den für Ihr Problem richtigen Algorithmus zu finden und unter zu Hilfenahme der Online-Dokumentation diesen auch einzusetzen.
Bis auf die nummerischen Algorithmen sind alle Algorithmen in der Header-Datei algorithm definiert. Die Definitionen der nummerischen Algorithmen sind in der Header-Datei numeric enthalten.
Beginnen werden wir aber zunächst mit den Vorbereitungen zur Behandlung der Algorithmen.
Bevor wir mit der Behandlung der Algorithmen beginnen, müssen wir noch etwas Vorarbeit leisten. Die Demonstration der Algorithmen soll soweit wie möglich praxisnah erfolgen, d.h. wir werden die Algorithmen nicht immer nur auf einfache Datentypen anwenden, sondern auch auf Objekte. Dazu erstellen wir uns die Headerdatei object.h, die die Klasse Object enthält:
#ifndef OBJECT_H__ #define OBJECT_H__ #include <iostream> #include <string> // Makro für Debug-Ausgabe #ifdef TRACE_METH #define TRACE(_T) std::cout << _T << " Obj-Nr:" << num << std::endl #else #define TRACE(_T) #endif // Object Klasse class Object { std::string text; // Objektdatum static int count; // Objektzähler int num; // aktuelle Objektnummer public: Object(); // Standard-ctor Object(const char* pText); // ctor Object(const Object& obj); // copy-ctor virtual ~Object(); // dtor // Zuweisungsoperator Object& operator = (const Object& rhs); // Vergleichsoperatoren bool operator < (const Object& rhs) const; bool operator == (const Object& rhs) const; // Restl. Vergleichsops auf Basis von < und == definieren bool operator != (const Object& rhs) const { return !(*this == rhs); } bool operator > (const Object& rhs) const { return rhs < *this; } bool operator <= (const Object& rhs) const { return !(rhs < *this); } bool operator >= (const Object& rhs) const { return !(*this < rhs); } // Ausgabeoperator friend std::ostream& operator << (std::ostream& os, const Object& obj); }; // Statisches Datum definieren int Object::count=0; // ctor-Definitionen inline Object::Object() { num = ++count; TRACE("std-ctor"); } inline Object::Object(const char* pText) { num = ++count; text = pText; TRACE("ctor"); } inline Object::Object(const Object& obj) { num = ++count; text = obj.text; TRACE("copy-ctor"); } // dtor Definition inline Object::~Object() { TRACE("dtor"); } // Überladene Operatoren definieren inline Object& Object::operator = (const Object& rhs) { if (&rhs==this) return *this; text = rhs.text; TRACE("operator="); return *this; } inline bool Object::operator < (const Object& rhs) const { return text < rhs.text; } inline bool Object::operator == (const Object& rhs) const { return text == rhs.text; } inline std::ostream& operator << (std::ostream& os, const Object& obj) { std::cout << obj.text; return os; } #endif |
Die Klasse Object enthält alle für die Ablage von Objekten in Containern notwendigen Memberfunktionen wie z.B. den Kopier-Konstruktor oder den überladenen Zuweisungsoperator. Ferner besitzt die Klasse die entsprechenden Vergleichsoperatoren, damit Objekte der Klasse in Containern sortiert und gesucht werden können.
Die gesamte Klasse wurde in der Header-Datei object.h definiert. Dies entspricht zwar nicht den allgemein üblichen Programmier-Regeln, sondern dient lediglich zur Vereinfachung des Compileraufrufs. In der Regel werden Sie in der Header-Datei ohject.h lediglich die Klasse definieren und in einer weiteren Datei object.cpp die Memberfunktionen der Klasse.
Zusätzlich wurde noch die Möglichkeit eingebaut, die Erstelleung und das Löschen der Objekte protokollieren zu können. Um die Protokollierung zu aktivieren, muss das Symbol TRACE_METH vor dem Einbinden der Header-Datei object.h definiert sein.
So, sehen wir uns jetzt die erste Algorithmen-Kategorie an, die nicht-modifizierenden Algorithmen
Folgende nicht-modifizierende Algorithmen stehen zur Verfügung:
| Algorithmus | Beschreibung |
| adjacent_find() | Sucht nach benachbarten Elementen, deren 'Werte' gleich sind |
| count() | Zählt die Anzahl der Elemente, die einen bestimmten 'Wert' besitzen |
| count_if() | Zählt die Anzahl der Elemente, die einer bestimmten Bedingung genügen |
| equal() | Vergleicht zwei Containerbereiche auf Gleichheit |
| find() | Sucht nach Element mit einem bestimmten 'Wert' |
| find_if() | Sucht nach lement das einer bestimmten Bedingung genügt |
| find_end() | Sucht nach dem letzten Auftreten einer Sequenz in einem Bereich |
| find_first_of() | Sucht nach dem ersten Auftreten eines von mehreren Elementen |
| for_each() | Führt für jedes Element eine Operation durch |
| lexicographical_compare() | Vergleicht zwei Bereiche auf lexikographisch kleiner |
| max_element() | Liefert das größte Element |
| min_element() | Liefert das kleinste Element |
| mismatch() | Liefert die erste Position zurück an der zwei Container verschieden sind |
| search() | Sucht nach einem überstimmenden Bereich |
| search_n() | Sucht nach dem ersten n-fachen Auftreten eines Elements |
|
|
// Beispiel zu Algorithmen // Suchen und Finden #include <iostream> // Algorithmen einbinden #include <algorithm> // ostream_iterator einbinden #include <iterator> // vordefinierte Functors einbinden #include <functional> // Listen-Container einbinden #include <list> using std::cout; using std::endl; // #define TRACE_METH #include "object.h" int main() { // Liste definieren typedef std::list<Object> cType; cType cont; // Liste füllen cont.push_back(Object("Eins")); cont.push_back(Object("Zwei")); cont.push_back(Object("Zwei")); cont.push_back(Object("Drei")); cont.push_back(Object("Zwanzig")); // Liste ausgeben cout << "Container-Inhalt: "; std::copy(cont.begin(), cont.end(), std::ostream_iterator<Object>(cout, ", ")); cout << '\n'; // Suche nach einem bestimmten Eintrag mit find() cType::iterator iter; iter = std::find(cont.begin(), cont.end(), Object("Zwei")); if (iter != cont.end()) cout << "'Zwei' an Position " << unsigned long(std::distance(cont.begin(),iter)) << " gefunden\n"; else cout << "'Zwei' nicht gefunden!\n"; // Suche nach ersten Objekt dessen Wert größer 'F' ist mit find_if() // Da find_if() an das Predicate nur das aktuelle Objekt übergibt, // ist hier bind2nd() notwendig. bind2nd() fungiert als Predicate // und ruft dann seinerseits den Functor greater<> auf, der dann das // aktuelle Objekt und den zusätzlichen Parameter (hier das Objekt // mit dem Wert 'F') erhält. iter = std::find_if(cont.begin(), cont.end(), std::bind2nd(std::greater<Object>(),Object("F"))); if (iter != cont.end()) cout << "1. Eintrag > 'F' an Position " << unsigned long(std::distance(cont.begin(),iter)) << " gefunden\n"; else cout << "Kein Objekt mit einem Wert groesser 'F' gefunden!\n"; // Suche nach erstem doppelten Eintrag mit adjacent_find() iter = std::adjacent_find(cont.begin(),cont.end()); if (iter != cont.end()) cout << "Benachbarte gleiche Werte auf Position " << unsigned long(std::distance(cont.begin(),iter)) << " gefunden\n"; else cout << "Keine benachbarten gleiche Werte gefunden!\n"; } |
|
Container-Inhalt: Eins, Zwei, Zwei, Drei, Zwanzig, |
// Beispiel zu Algorithmen // Zählen und Minimum/Maximum #include <iostream> // Algorithmen einbinden #include <algorithm> // ostream_iterator einbinden #include <iterator> // vordefinierte Functors einbinden #include <functional> // Vektor-Container einbinden #include <vector> using std::cout; using std::endl; // #define TRACE_METH #include "object.h" int main() { // Vektor definieren typedef std::vector<Object> cType; cType cont; // Für Optimierung, verhindert die erneute Speicherreservierung // bei jedem neu hinzugefügten Objekt cont.reserve(10); // Vektor vorbelegen cont.push_back(Object("Eins")); cont.push_back(Object("Zwei")); cont.push_back(Object("Drei")); cont.push_back(Object("Zwanzig")); cont.push_back(Object("Zwei")); cout << "Container-Inhalt: "; std::copy(cont.begin(), cont.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; // Anzahl der Objekte mit dem Wert "Zwei" zählen cType::iterator::difference_type result; result = std::count(cont.begin(), cont.end(),Object("Zwei")); cout << "Objekte mit dem Wert 'Zwei': " << unsigned long(result) << endl; // Anzahl der Objekte zählen die größer/gleich "Z" sind result = std::count_if(cont.begin(), cont.end(), std::bind2nd(std::greater_equal<Object>(),Object("Z"))); cout << "Objekte mit dem Wert >= 'Z': " << unsigned long(result) << endl; // Kleinstes und größtes Objekt finden cType::iterator iterMin, iterMax; iterMin = std::min_element(cont.begin(), cont.end()); iterMax = std::max_element(cont.begin(), cont.end()); cout << "Kleinstes Element: '" << *iterMin << "', groesstes Element: '" << *iterMax << "'\n"; } |
|
Container-Inhalt: Eins, Zwei, Drei, Zwanzig, Zwei, |
// Beispiel zu Algorithmen // Container-Vergleich #include <iostream> // Algorithmen einbinden #include <algorithm> // ostream_iterator einbinden #include <iterator> // vordefinierte Functors einbinden #include <functional> // Vektor-Container einbinden #include <vector> using std::cout; using std::endl; // #define TRACE_METH #include "object.h" int main() { // Container definieren und initialisieren typedef std::vector<Object> cType; cType cont; cont.push_back(Object("Eins")); cont.push_back(Object("Zwei")); cont.push_back(Object("Zwei")); cont.push_back(Object("Drei")); cont.push_back(Object("Zwanzig")); cout << "Container-Inhalt: "; std::copy(cont.begin(), cont.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; // Zweiten Container definieren und mit dem Inhalt des // ersten Containers initialisieren cType toCmp(cont); // Viertes Element überschreiben // Die String-Zuweisung erstellt über den ctor Object(const char*) // ein neues Objekt und übernimmt dieses in den Container toCmp.at(3) = "Null"; cout << "Vergleichs-Container: "; std::copy(toCmp.begin(), toCmp.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; // Vergleiche nun die ersten 3 Elemente der Container miteinander bool bRet; bRet = std::equal(cont.begin(),cont.begin()+3,toCmp.begin()); if (bRet) cout << "Bereich [0..3) der Container identisch\n"; else cout << "Bereich [0..3) der Container nicht identisch\n"; // Suche nun die erste Position, an der die beiden Container // unterschiedliche Wert haben. Da der Vergleich auf unterschiedlichen // Positionen in den Containern beginnen kann, liefert mismatch() // zwei Iteratoren zurück, einen für die Position im ersten Container // und einen fuer die im zweiten Container // pair fuer die die beiden Positionen definieren std::pair<cType::iterator, cType::iterator> iterMissed; iterMissed = std::mismatch(cont.begin(),cont.end(),toCmp.begin()); if (iterMissed.first != cont.end()) cout << "Container weichen ab Position " << unsigned long(std::distance(cont.begin(),iterMissed.first)) << " im ersten Container voneinander ab\n"; else cout << "Container haben identischen Inhalt"; } |
|
Container-Inhalt: Eins, Zwei, Zwei, Drei, Zwanzig, |
Folgende modifizierende Algorithmen stehen zur Verfügung:
| Algorithmus | Beschreibung |
| copy() | Kopiert einen Bereich |
| copy_backward() | Kopiert einen Bereich, wobei das letzte Element des Bereichs als erstes kopiert wird |
| for_each() | Führt für jedes Element eine Operation durch |
| generate() | Erzeugt für einen Bereich neue Elemente |
| generate_n() | Erzeugt für n Elemente neue Elemente |
| merge() | Vereinigt zwei Bereiche |
| replace() | Ersetzt in einen Bereich bestimmte Werte der Elemente |
| replace_if() | Ersetzt in einem Bereich die Werte der Elemente, die einer bestimmten Bedingung genügen |
| replace_copy() | Ersetzt in einem Bereich bestimmte Werte der Elemente und kopiert dann den Bereich |
| replace_copy_if() | Ersetzt in einem Bereich die Werte der Elemente die einer bestimmten Bedingung genügen und kopiert dann den Bereich |
| swap_ranges() | Vertauscht zwei Bereiche |
| fill() | Ersetzt in einem Bereich alle Elemente durch ein anderes Element |
| fill_n() | Ersetzt n Elemente durch ein anderes Element |
| transform() | Modifiziert die Werte einen Bereich und kopiert diesen optional |
Der Unterschied zwischen generate(), replace() und transform() ist, dass generate() über eine definierbare Operation neue Elemente erzeugt, replace() bestehende Elemente ersetzt und transform() das Element selbst zwar nicht verändert, wohl aber seinen Wert.
// Beispiel zu Algorithmen // Modifizierende Algorithmen #include <iostream> // Algorithmen einbinden #include <algorithm> // ostream_iterator einbinden #include <iterator> // vordefinierte Functors einbinden #include <functional> // Container einbinden #include <deque> #include <vector> using std::cout; using std::endl; // #define TRACE_METH #include "object.h" // Functor zur Erzeugung von Zufallszahlen // mit einer Obergrenze class Randomize { int upperLimit; public: Randomize(int limit): upperLimit(limit) {} int operator ()() { return rand() % upperLimit; } }; int main() { // Container definieren/initialisieren typedef std::vector<Object> cType; cType cont; cont.push_back(Object("Eins")); cont.push_back(Object("Zwei")); cont.push_back(Object("Drei")); cont.push_back(Object("Vier")); cout << "Container-Inhalt: "; std::copy(cont.begin(), cont.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; // Container umgekehrt in einen anderen Containertyp umkopieren // Der Ziel-Container enthält am Anfang keine Elemente, deshalb // muss für den Kopiervorgang ein entsprechender Inserter // verwendet werden std::deque<Object> revCont; std::copy(cont.begin(), cont.end(), std::front_inserter(revCont)); cout << "Kopie in umgekehrter Reihenfolge: "; std::copy(revCont.begin(), revCont.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; // Tausche die ersten beiden Elemente des Ausgangs-Containers und // seiner umgekehrten Kopie gegeneinander aus std::swap_ranges(cont.begin(),cont.begin()+2,revCont.begin()); cout << "Vertausche die beiden ersten Elemente der Container:\n" << "Ausgangs-Container: "; std::copy(cont.begin(), cont.end(), std::ostream_iterator<Object>(cout,", ")); cout << "\nContainer-Kopie: "; std::copy(revCont.begin(), revCont.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; // Leeren Vektor definieren std::vector<int> intVect; // Vektor mit 5 Zufallszahlen füllen. generate_n() erzeugt die Zahlen // über Functor Randomize(). Da der Vektor noch leer ist, müssen die // die erzeugten Zahlen über einen Inserter eingefügt werden std::generate_n(std::back_inserter(intVect),5,Randomize(100)); cout << "Vektor mit 5 Zufallszahlen: "; std::copy(intVect.begin(),intVect.end(),std::ostream_iterator<int>(cout,", ")); cout << '\n'; // Vektor fuer Differenzen definieren std::vector<int> diffVect; // Differenzen zwischen benachbarten Werten bilden und in neuem // Vektor ablegen. Der Trick dabei besteht darin, dass die Start- // positionen der beiden Input-Iteratoren um eins verschoben sind. // Damit wird die auszuführende Operation minus<> mit den Elementen // vector(n) und vector(n+1) aufgerufen. minus<> bildet dann aus diesen // beiden Werten die Differenz, die dann mit dem back_inserter in // den Differenz-Vektor übernommen wird. std::transform(intVect.begin(),intVect.end()-1, intVect.begin()+1, std::back_inserter(diffVect),std::minus<int>()); cout << "Differenzen der benachbarten Elemente: "; std::copy(diffVect.begin(),diffVect.end(),std::ostream_iterator<int>(cout,", ")); cout << '\n'; // Differenzen kleiner < 0 durch 0 ersetzen std::replace_if(diffVect.begin(),diffVect.end(),std::bind2nd(std::less<int>(),0),0); cout << "Differenzen kleiner 0 durch 0 ersetzt: "; std::copy(diffVect.begin(),diffVect.end(),std::ostream_iterator<int>(cout,", ")); cout << '\n'; // int-Vektor leeren intVect.clear(); // Differenzen umkopieren, 0 Differenzen dabei aber überspringen std::remove_copy(diffVect.begin(),diffVect.end(),std::back_inserter(intVect),0); cout << "Umkopierte Differenzen, ohne 0 Differenzen: "; std::copy(intVect.begin(),intVect.end(),std::ostream_iterator<int>(cout,", ")); cout << '\n'; } |
|
Container-Inhalt: Eins, Zwei, Drei, Vier, |
Folgende löschenden Algorithmen stehen zur Verfügung:
| Algorithmus | Beschreibung |
| remove() | Entfernt Element mit einem bestimmten Wert |
| remove_if() | Entfernt Element die einer bestimmten Bedingung genügen |
| remove_copy() | Kopiert alle Elemente eines Bereichs die nicht einen bestimmten Wert besitzen |
| remove_copy_if() | Kopiert alle Elemente eines Bereichs die nicht einer bestimmten Bedingung genügen |
| unique() | Entfernt unmittelbar aufeinander folgende gleiche Werte |
| unique_copy() | Kopiert alle Elemente, wobei aber unmittelbar aufeinander folgende Elemente mit dem gleichen Wert nur einmal kopiert werden |
Die zu löschenden Elemente werden nur logisch entfernt, d.h. die Anzahl der Container-Elemente bleibt dabei erhalten. Es werden lediglich die zu löschenden Elemente mit den nachfolgenden Elemente überschrieben. Die löschenden Algorithmen liefern aber einen Iterator auf das neue logische Ende des Containers zurück. Sollen die 'überzähligen' Elemente entfernt werden. so ist die entsprechende erase(...) Memberfunktion des Containers aufzurufen (siehe dazu auch nachfolgendes Beispiel).
// Beispiel zu Algorithmen // Löschende Algorithmen #include <iostream> // Algorithmen einbinden #include <algorithm> // ostream_iterator einbinden #include <iterator> // vordefinierte Functors einbinden #include <functional> // Container einbinden #include <vector> #include <set> using std::cout; using std::endl; // #define TRACE_METH #include "object.h" int main() { // Container definieren/initialisieren typedef std::vector<Object> cType; cType cont; cont.push_back(Object("Eins")); cont.push_back(Object("Zwei")); cont.push_back(Object("Drei")); cont.push_back(Object("Vier")); cont.push_back(Object("Fuenf")); // Container spiegeln // Hier dürfen keine Iteratoren verwendet werden, da beim Spiegeln // des Containers eventuell neuer Speicher angefordert wird und // dies die Iteratoren ungültig macht! for (size_t index=cont.size(); index>0; index--) cont.push_back(cont[index-1]); cout << "Container-Inhalt:\n"; std::copy(cont.begin(), cont.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; // Entferne alle Elemente mit dem Wert 'Drei' // remove() liefert neues logisches Ende des Containers zurück // Die Anzahl der Elemente bleibt nach dem Entfernen der Elemente // zunächst gleich // Iterator für neues logisches definieren cType::iterator newEnd; newEnd = std::remove(cont.begin(), cont.end(), Object("Drei")); cout << "Elemente mit dem Wert 'Drei' entfernt:\n"; std::copy(cont.begin(), cont.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; // Jetzt alle Elemente hinter dem neuen logischen Ende entfernen // (phyikalisches entfernen) cont.erase(newEnd,cont.end()); cout << "Container auf neues logisches Ende verkuerzt:\n"; std::copy(cont.begin(), cont.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; // Entferne alle unmittelbar aufeinander folgenden Elemente // die den gleichen Wert besitzen newEnd = std::unique(cont.begin(), cont.end()); // Elemente physikalisch entfernen cont.erase(newEnd, cont.end()); cout << "Alle unmittelbar aufeinander folgenden Werte entfernt:\n"; std::copy(cont.begin(), cont.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; // Neuen Container definieren cType newCont; // Kopiere alle Elemente in einen neuen Container deren Wert // kleiner 'M' ist std::remove_copy_if(cont.begin(), cont.end(), std::back_inserter(newCont), std::bind2nd(std::greater<Object>(),Object("M"))); cout << "Element mit einem Wert kleiner 'M' umkopiert:\n"; std::copy(newCont.begin(), newCont.end(), std::ostream_iterator<Object>(cout,", ")); cout << '\n'; } |
|
Container-Inhalt: |