New Tree structure to test.

This commit is contained in:
Christoph Holzheuer
2025-10-29 13:43:43 +01:00
parent c4d5155ac3
commit af3a7e06c2

View File

@@ -30,6 +30,188 @@
using namespace Qt::Literals::StringLiterals; using namespace Qt::Literals::StringLiterals;
#include <iostream>
#include <vector>
#include <memory> // Für std::unique_ptr
#include <iterator> // Für std::forward_iterator_tag
#include <algorithm> // Für std::find_if, std::next
#include <cstddef> // Für std::ptrdiff_t
#include <string>
/**
* @brief Eine generische Baumstruktur, die als Knoten selbst fungiert.
*
* Diese Klasse implementiert einen Baum, bei dem jedes 'Tree'-Objekt
* einen Knoten im Baum darstellt. Sie verwendet std::unique_ptr
* für die Verwaltung der Kind-Knoten und ist durch Iteratoren
* mit STL-Algorithmen kompatibel (Pre-Order-Traversierung).
*
* @tparam T Der Typ des zu speichernden Wertes.
*/
template <typename T>
class Tree
{
public:
// --- Öffentliche Typ-Aliase ---
using value_type = T;
using reference = T&;
using const_reference = const T&;
// --- Element-Zugriff ---
value_type value;
// --- Struktur ---
// Wir verwenden unique_ptr, um das Eigentum klar zu definieren.
// Der Elternknoten besitzt seine Kinder.
std::vector<std::unique_ptr<Tree<T>>> children;
// Nicht-besitzender Rohzeiger auf den Elternknoten für die Navigation nach oben.
// Dieser ist für die Iterator-Implementierung entscheidend.
Tree<T>* parent = nullptr;
// --- Konstruktoren ---
explicit Tree(const T& val, Tree<T>* p = nullptr) : value(val), parent(p) {}
explicit Tree(T&& val, Tree<T>* p = nullptr) : value(std::move(val)), parent(p) {}
// Verhindere Kopieren, da unique_ptr nicht kopierbar ist (aber verschiebbar).
Tree(const Tree&) = delete;
Tree& operator=(const Tree&) = delete;
// Standard-Move-Konstruktor und -Zuweisung sind in Ordnung.
Tree(Tree&&) = default;
Tree& operator=(Tree&&) = default;
// --- Methoden ---
/**
* @brief Fügt ein neues Kind mit einem Wert hinzu (Kopie).
* @return Ein nicht-besitzender Zeiger auf den neu erstellten Knoten.
*/
Tree<T>* addChild(const T& val) {
auto newNode = std::make_unique<Tree<T>>(val, this);
children.push_back(std::move(newNode));
return children.back().get();
}
/**
* @brief Fügt ein neues Kind mit einem Wert hinzu (Move).
* @return Ein nicht-besitzender Zeiger auf den neu erstellten Knoten.
*/
Tree<T>* addChild(T&& val) {
auto newNode = std::make_unique<Tree<T>>(std::move(val), this);
children.push_back(std::move(newNode));
return children.back().get();
}
private:
// --- Private Basis-Iterator-Klasse (Template-Magie zur Vermeidung von Code-Duplizierung) ---
// Wir definieren einen Template-Iterator, der sowohl für const als auch für non-const funktioniert.
template <bool IsConst>
class BaseIterator {
public:
// Iterator-Traits (Eigenschaften)
using iterator_category = std::forward_iterator_tag;
using difference_type = std::ptrdiff_t;
// Abhängige Typen: TreeT ist 'const Tree<T>' oder 'Tree<T>'
using TreeT = std::conditional_t<IsConst, const Tree<T>, Tree<T>>;
// value_type ist immer T (der Wert, nicht der Knoten)
using value_type = T;
using pointer = std::conditional_t<IsConst, const T*, T*>;
using reference = std::conditional_t<IsConst, const T&, T&>;
private:
TreeT* current_node;
public:
// Konstruktor
explicit BaseIterator(TreeT* node) : current_node(node) {}
// Dereferenzierung
reference operator*() const { return current_node->value; }
pointer operator->() const { return &(current_node->value); }
// Vergleichsoperatoren
bool operator==(const BaseIterator& other) const {
return current_node == other.current_node;
}
bool operator!=(const BaseIterator& other) const {
return current_node != other.current_node;
}
// --- Inkrement (Kernlogik der Pre-Order-Traversierung) ---
// (Präfix-Version: ++it)
BaseIterator& operator++() {
if (!current_node) {
return *this; // Bereits am Ende
}
// 1. Priorität: Gehe zum ersten Kind (Tiefensuche)
if (!current_node->children.empty()) {
current_node = current_node->children.front().get();
return *this;
}
// 2. Priorität: Gehe zum nächsten Geschwisterknoten oder gehe nach oben
while (current_node) {
// Wenn wir zur Wurzel zurückgekehrt sind und keine Geschwister haben, sind wir fertig.
if (!current_node->parent) {
current_node = nullptr; // Ende erreicht
return *this;
}
TreeT* parent_node = current_node->parent;
// Finde den aktuellen Knoten in der Kind-Liste des Elternteils
auto it = std::find_if(parent_node->children.begin(), parent_node->children.end(),
[this](const auto& child_ptr) {
return child_ptr.get() == current_node;
});
// Finde das nächste Geschwister
auto next_sibling_it = std::next(it);
if (next_sibling_it != parent_node->children.end()) {
// 2a. Nächstes Geschwister gefunden
current_node = next_sibling_it->get();
return *this;
}
// 2b. Kein nächstes Geschwister, gehe eine Ebene nach oben
// Die Schleife wird mit dem parent_node wiederholt
current_node = parent_node;
}
return *this; // Sollte jetzt nullptr sein
}
// (Postfix-Version: it++)
BaseIterator operator++(int) {
BaseIterator temp = *this;
++(*this);
return temp;
}
};
public:
// --- Öffentliche Iterator-Typen ---
using iterator = BaseIterator<false>;
using const_iterator = BaseIterator<true>;
// --- Iterator-Methoden (STL-Kompatibilität) ---
iterator begin() { return iterator(this); }
iterator end() { return iterator(nullptr); }
const_iterator begin() const { return const_iterator(this); }
const_iterator end() const { return const_iterator(nullptr); }
const_iterator cbegin() const { return const_iterator(this); }
const_iterator cend() const { return const_iterator(nullptr); }
};
/*
int main(int argc, char *argv[]) int main(int argc, char *argv[])
{ {
@@ -45,9 +227,91 @@ int main(int argc, char *argv[])
return app.exec(); return app.exec();
} }
*/
int main(int argc, char *argv[])
{
QApplication app(argc, argv);
// hat am wenigsten darstellungfehler (alternative: fusion)
QQuickStyle::setStyle("Imagine");
XQMainWindow window;
// 1. Baum erstellen
// Der Benutzer erstellt den Wurzelknoten direkt.
Tree<std::string> root("Root");
// Kinder hinzufügen. Die addChild-Methode gibt einen Zeiger
// auf den neuen Knoten zurück, sodass wir sie verketten können.
auto* a = root.addChild("A");
auto* b = root.addChild("B");
auto* c = root.addChild("C");
// Enkelkinder hinzufügen
a->addChild("A1");
a->addChild("A2");
auto* b1 = b->addChild("B1 (suche mich)");
c->addChild("C1");
c->addChild("C2");
c->addChild("C3");
b1->addChild("B1.1");
/*
* Der Baum sieht nun so aus (Pre-Order):
* 1. Root
* 2. A
* 3. A1
* 4. A2
* 5. B
* 6. B1 (suche mich)
* 7. B1.1
* 8. C
* 9. C1
* 10. C2
* 11. C3
*/
// 2. Verwendung mit Range-Based 'for' (dank begin/end)
std::cout << "--- Pre-Order Traversierung (Range-based for) ---" << std::endl;
for (const std::string& value : root) {
std::cout << value << " -> ";
}
std::cout << "end" << std::endl;
// 3. Verwendung mit <algorithm> (z.B. std::find_if)
std::cout << "\n--- STL std::find_if ---" << std::endl;
// Finde den ersten Knoten, dessen Wert "B1 (suche mich)" ist.
// Wir verwenden cbegin/cend, da wir den Baum nicht ändern wollen.
auto found_it = std::find_if(root.cbegin(), root.cend(), [](const std::string& val) {
return val == "B1 (suche mich)";
});
if (found_it != root.cend()) {
// *found_it gibt den Wert (std::string) zurück
std::cout << "Gefunden: " << *found_it << std::endl;
} else {
std::cout << "Nicht gefunden." << std::endl;
}
// 4. Verwendung mit <numeric> (z.B. std::count_if)
std::cout << "\n--- STL std::count_if ---" << std::endl;
int count = std::count_if(root.cbegin(), root.cend(), [](const std::string& val) {
return val.length() > 5; // Zähle alle Strings mit mehr als 5 Zeichen
});
std::cout << "Knoten mit > 5 Zeichen: " << count << std::endl;
return 0;
}