updated filestructure and gitignore. uploading exam progress
This commit is contained in:
454
Exam/IKT203Exam/LibExample/TCircularDoublyLinkedListTemplate.hpp
Normal file
454
Exam/IKT203Exam/LibExample/TCircularDoublyLinkedListTemplate.hpp
Normal file
@@ -0,0 +1,454 @@
|
||||
// Implantation of the Circular Doubly Linked List
|
||||
|
||||
#ifndef TCIRCULARDOUBLYLINKEDLISTTEMPLATE_HPP
|
||||
#define TCIRCULARDOUBLYLINKEDLISTTEMPLATE_HPP
|
||||
#pragma once
|
||||
|
||||
#include "TDoublyLinkedListTemplate.hpp"
|
||||
|
||||
template <typename T>
|
||||
class TCircularDoublyLinkedList : public TDoublyLinkedList<T> {
|
||||
private:
|
||||
|
||||
// Helper to remove a node, updating both next and prev pointers
|
||||
void InternalRemoveNode(TNode<T>* aNodeToDelete);
|
||||
|
||||
protected:
|
||||
// --- New Member Variable ---
|
||||
TNode<T>* cursor; // Points to the "current" node in the list.
|
||||
|
||||
public:
|
||||
using TSingleLinkedList<T>::Append;
|
||||
using TSingleLinkedList<T>::Prepend;
|
||||
|
||||
// --- Constructor & Destructor ---
|
||||
TCircularDoublyLinkedList(bool aIsDataOwner);
|
||||
virtual ~TCircularDoublyLinkedList() override;
|
||||
|
||||
// --- New Cursor Management Methods ---
|
||||
void ResetCursor();
|
||||
T GetCursorData() const;
|
||||
void AdvanceCursor(int aSteps = 1);
|
||||
void RewindCursor(int aSteps = 1);
|
||||
int GetCursorIndex() const;
|
||||
|
||||
|
||||
// --- Overridden Virtual Methods ---
|
||||
// For maintaining the circular link
|
||||
void Append(const T&) override;
|
||||
void Prepend(const T&) override;
|
||||
void Remove(const T&) override;
|
||||
void RemoveAll(const T&) override;
|
||||
T RemoveLast() override;
|
||||
void Reverse() override;
|
||||
void ReverseSublist(int, int) override;
|
||||
void Merge(TSingleLinkedList<T>& otherList) override;
|
||||
|
||||
// For avoiding infinite loops
|
||||
bool Contains(const T&) const override;
|
||||
T Search(const T&, FCheckNode<T>) const override;
|
||||
void ForEach(FVisitNode<T>) const override;
|
||||
TNode<T>* GetMiddle() const override;
|
||||
};
|
||||
|
||||
// Constructor: Establishes the initial circular link where the head points to itself.
|
||||
template <typename T>
|
||||
TCircularDoublyLinkedList<T>::TCircularDoublyLinkedList(bool aIsDataOwner)
|
||||
: TDoublyLinkedList<T>(aIsDataOwner) {
|
||||
// An empty circular list's dummy head points to itself.
|
||||
this->head->SetNext(this->head);
|
||||
this->head->SetPrev(this->head);
|
||||
this->cursor = this->head; // Cursor also starts at the head.
|
||||
}
|
||||
|
||||
// Destructor: The parent destructors are virtual and will handle cleanup.
|
||||
template <typename T>
|
||||
TCircularDoublyLinkedList<T>::~TCircularDoublyLinkedList() {
|
||||
// Before the parent destructors run, we must break the circular link.
|
||||
// The parent's `nullptr`-terminated cleanup loop will now work correctly.
|
||||
if (!this->IsEmpty()) {
|
||||
// The tail is the node before the dummy head.
|
||||
// Set its 'next' pointer to null instead of back to the head.
|
||||
this->head->GetPrev()->SetNext(nullptr);
|
||||
}
|
||||
// Now, the chain is broken: head -> node1 -> ... -> tail -> nullptr
|
||||
// The TDoublyLinkedList and TSingleLinkedList destructors will be called
|
||||
// automatically after this, and they will now function correctly.
|
||||
}
|
||||
|
||||
// Moves the cursor to the first element in the list.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::ResetCursor() {
|
||||
if (this->IsEmpty()) {
|
||||
this->cursor = this->head;
|
||||
}
|
||||
else {
|
||||
this->cursor = this->head->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the data at the cursor's current position.
|
||||
template <typename T>
|
||||
T TCircularDoublyLinkedList<T>::GetCursorData() const {
|
||||
// The cursor is only valid if it's not pointing to the dummy head.
|
||||
if (!this->IsEmpty() && this->cursor != this->head) {
|
||||
return this->cursor->GetData();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Moves the cursor forward, wrapping around if needed.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::AdvanceCursor(int aSteps) {
|
||||
if (this->IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < aSteps; ++i) {
|
||||
this->cursor = this->cursor->GetNext();
|
||||
// If we move to the dummy head, we've wrapped around. Skip over it to the first element.
|
||||
if (this->cursor == this->head) {
|
||||
this->cursor = this->head->GetNext();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Moves the cursor backward, wrapping around if needed.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::RewindCursor(int aSteps) {
|
||||
if (this->IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
for (int i = 0; i < aSteps; ++i) {
|
||||
this->cursor = this->cursor->GetPrev();
|
||||
// If we move to the dummy head, we've wrapped around. Skip over it to the last element.
|
||||
if (this->cursor == this->head) {
|
||||
this->cursor = this->head->GetPrev();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Gets the numerical index of the cursor's current position.
|
||||
template <typename T>
|
||||
int TCircularDoublyLinkedList<T>::GetCursorIndex() const {
|
||||
if (this->IsEmpty() || this->cursor == this->head) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
int index = 0;
|
||||
TNode<T>* current = this->head->GetNext();
|
||||
|
||||
// Traverse the list safely, knowing it will eventually loop back to the head.
|
||||
while (current != this->head) {
|
||||
if (current == this->cursor) {
|
||||
return index;
|
||||
}
|
||||
current = current->GetNext();
|
||||
index++;
|
||||
}
|
||||
|
||||
return -1; // Should not be reached if cursor is valid.
|
||||
}
|
||||
|
||||
// Appends a node to the end of the list (before the dummy head).
|
||||
// Use the InternAppend from TDoublyLinkedList for basic logic, then adjust prev pointers.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::Append(const T& aData) {
|
||||
// 1. Let the base class do ALL the standard work.
|
||||
TDoublyLinkedList<T>::Append(aData);
|
||||
// 2. Now fix the circular links.
|
||||
if(!this->IsEmpty()) {
|
||||
this->head->SetPrev(this->tail); // Dummy head's prev points to new tail
|
||||
this->tail->SetNext(this->head); // New tail's next points to dummy head
|
||||
}
|
||||
}
|
||||
|
||||
// Prepends a node to the beginning of the list (after the dummy head).
|
||||
// Use the Prepend from TDoublyLinkedList, this will handle all the logic.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::Prepend(const T& aData) {
|
||||
TDoublyLinkedList<T>::Prepend(aData);
|
||||
// No need to adjust circular links, as TDoublyLinkedList::Prepend already does it.
|
||||
}
|
||||
|
||||
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::InternalRemoveNode(TNode<T>* aNodeToDelete) {
|
||||
if (aNodeToDelete == nullptr || aNodeToDelete == this->head) return; // Invalid input
|
||||
|
||||
// 1. Update cursor if it points to the node being deleted
|
||||
if (this->cursor == aNodeToDelete) {
|
||||
this->cursor = aNodeToDelete->GetNext();
|
||||
}
|
||||
|
||||
//2. Update tail if the last node is being removed
|
||||
if (aNodeToDelete == this->tail) {
|
||||
this->tail = aNodeToDelete->GetPrev();
|
||||
}
|
||||
|
||||
// 3. Relink neighbors (this maintains the circle automatically)
|
||||
TNode<T>* prevNode = aNodeToDelete->GetPrev();
|
||||
TNode<T>* nextNode = aNodeToDelete->GetNext();
|
||||
prevNode->SetNext(nextNode);
|
||||
nextNode->SetPrev(prevNode);
|
||||
|
||||
// 4. Delete data if owned, then delete the node
|
||||
if (this->isDataOwner) {
|
||||
delete aNodeToDelete->GetData();
|
||||
}
|
||||
delete aNodeToDelete;
|
||||
this->size--;
|
||||
|
||||
// 5. If the list is now empty, ensure tail and cursor point to the head.
|
||||
if (this->IsEmpty()) {
|
||||
this->tail = this->head;
|
||||
this->cursor = this->head;
|
||||
}
|
||||
}
|
||||
|
||||
// Removes the first node with the given value.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::Remove(const T& aData) {
|
||||
if (this->IsEmpty()) return;
|
||||
|
||||
// Start searching from the first actual node
|
||||
TNode<T>* current = this->head->GetNext();
|
||||
// Traverse the entire circle once
|
||||
for (int i = 0; i < this->size; ++i) {
|
||||
if (current->GetData() == aData) {
|
||||
TNode<T>* prevNode = current->GetPrev();
|
||||
TNode<T>* nextNode = current->GetNext();
|
||||
|
||||
// Update tail if the last node is being removed
|
||||
if (current == this->tail) {
|
||||
this->tail = prevNode;
|
||||
}
|
||||
|
||||
// Relink neighbors (this maintains the circle automatically)
|
||||
prevNode->SetNext(nextNode);
|
||||
nextNode->SetPrev(prevNode);
|
||||
|
||||
if (this->isDataOwner) {
|
||||
delete current->GetData();
|
||||
}
|
||||
delete current;
|
||||
this->size--;
|
||||
return;
|
||||
}
|
||||
current = current->GetNext();
|
||||
}
|
||||
}
|
||||
|
||||
// Removes the last element from the list.
|
||||
template <typename T>
|
||||
T TCircularDoublyLinkedList<T>::RemoveLast() {
|
||||
if (this->IsEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TNode<T>* nodeToRemove = this->tail;
|
||||
T dataToReturn = nodeToRemove->GetData();
|
||||
TNode<T>* newTail = nodeToRemove->GetPrev();
|
||||
|
||||
// Relink the new tail to the head to maintain the circle
|
||||
newTail->SetNext(this->head);
|
||||
this->head->SetPrev(newTail);
|
||||
|
||||
this->tail = newTail; // Update tail pointer
|
||||
|
||||
delete nodeToRemove;
|
||||
this->size--;
|
||||
|
||||
if (this->IsEmpty()) {
|
||||
this->tail = this->head; // Reset tail if list is now empty
|
||||
}
|
||||
|
||||
return dataToReturn;
|
||||
}
|
||||
|
||||
// Removes all occurrences of a given value using a safe, bounded loop.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::RemoveAll(const T& aData) {
|
||||
if (this->IsEmpty()) return;
|
||||
|
||||
TNode<T>* current = this->head->GetNext();
|
||||
int initialSize = this->size; // Loop based on the original size
|
||||
|
||||
for (int i = 0; i < initialSize; ++i) {
|
||||
TNode<T>* nextNode = current->GetNext(); // Get next node before potential deletion
|
||||
|
||||
if (current->GetData() == aData) {
|
||||
TNode<T>* prevNode = current->GetPrev();
|
||||
|
||||
if (current == this->tail) {
|
||||
this->tail = prevNode;
|
||||
}
|
||||
|
||||
prevNode->SetNext(nextNode);
|
||||
nextNode->SetPrev(prevNode);
|
||||
|
||||
if (this->isDataOwner) {
|
||||
delete current->GetData();
|
||||
}
|
||||
delete current;
|
||||
this->size--;
|
||||
}
|
||||
current = nextNode;
|
||||
}
|
||||
if (this->IsEmpty()) {
|
||||
this->tail = this->head;
|
||||
}
|
||||
}
|
||||
|
||||
// Reverses the entire list by swapping the next/prev pointers of every node.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::Reverse() {
|
||||
if (this->size <= 1) return; // Nothing to reverse
|
||||
|
||||
// 1. Keep track of original head and tail nodes.
|
||||
TNode<T>* newFirstNode = this->tail;
|
||||
|
||||
// 2. Iterate through all nodes, swapping next and prev pointers.
|
||||
TNode<T>* current = this->head;
|
||||
// We must loop size + 1 times to include the dummy head in the pointer swap.
|
||||
for (int i = 0; i < this->size + 1; ++i) {
|
||||
current->SwapNextPrev();
|
||||
// The *new* prev pointer is the *original* next pointer, so this moves us forward.
|
||||
current = current->GetPrev();
|
||||
}
|
||||
|
||||
// 3. Correct the head and tail pointers.
|
||||
this->tail = this->head->GetNext(); // The old head is the new tail.
|
||||
this->head->SetNext(newFirstNode); // The dummy head now points to the old tail.
|
||||
newFirstNode->SetPrev(this->head); // New first node's prev points to dummy head.
|
||||
}
|
||||
|
||||
// Reverses a portion of the list, ensuring circular links are maintained.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::ReverseSublist(int start, int end) {
|
||||
if (start < 0 || end >= this->size || start >= end) {
|
||||
return;
|
||||
}
|
||||
|
||||
// --- 1. Find boundary nodes ---
|
||||
TNode<T>* startNode = this->head->GetNext();
|
||||
for (int i = 0; i < start; ++i) {
|
||||
startNode = startNode->GetNext();
|
||||
}
|
||||
|
||||
TNode<T>* endNode = startNode;
|
||||
for (int i = start; i < end; ++i) {
|
||||
endNode = endNode->GetNext();
|
||||
}
|
||||
|
||||
TNode<T>* startPrev = startNode->GetPrev();
|
||||
TNode<T>* endNext = endNode->GetNext();
|
||||
|
||||
// --- 2. Reverse pointers for all nodes within the sublist ---
|
||||
TNode<T>* current = startNode;
|
||||
for (int i = 0; i <= (end - start); ++i) {
|
||||
TNode<T>* temp = current->GetNext();
|
||||
current->SetNext(current->GetPrev());
|
||||
current->SetPrev(temp);
|
||||
current = temp;
|
||||
}
|
||||
|
||||
// --- 3. Re-stitch the sublist (no nullptr checks needed) ---
|
||||
startPrev->SetNext(endNode);
|
||||
endNode->SetPrev(startPrev);
|
||||
startNode->SetNext(endNext);
|
||||
endNext->SetPrev(startNode);
|
||||
|
||||
// Update tail pointer if it was part of the reversed segment
|
||||
if (this->head->GetPrev() == endNode) { // Original startNode is now at the end
|
||||
this->tail = startNode;
|
||||
}
|
||||
}
|
||||
|
||||
// Merges another sorted list, creating a single, sorted circular list.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::Merge(TSingleLinkedList<T>& otherList) {
|
||||
// We can call the parent implementation to do the heavy lifting of merging.
|
||||
TDoublyLinkedList<T>::Merge(otherList);
|
||||
|
||||
// The parent merge results in a null-terminated list. We just need to fix the ends.
|
||||
if (!this->IsEmpty()) {
|
||||
TNode<T>* firstNode = this->head->GetNext();
|
||||
this->tail->SetNext(firstNode);
|
||||
firstNode->SetPrev(this->tail);
|
||||
}
|
||||
}
|
||||
|
||||
// Applies a function to each node using a safe loop for a circular list.
|
||||
template <typename T>
|
||||
void TCircularDoublyLinkedList<T>::ForEach(FVisitNode<T> aVisitNode) const {
|
||||
if (aVisitNode == nullptr || this->IsEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
TNode<T>* current = this->head->GetNext();
|
||||
int index = 0;
|
||||
while (current != this->head) {
|
||||
aVisitNode(current->GetData(), index);
|
||||
current = current->GetNext();
|
||||
index++;
|
||||
}
|
||||
}
|
||||
|
||||
// Checks if a value is in the list using a safe, non-infinite loop.
|
||||
template <typename T>
|
||||
bool TCircularDoublyLinkedList<T>::Contains(const T& aData) const {
|
||||
if (this->IsEmpty()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
TNode<T>* current = this->head->GetNext();
|
||||
while (current != this->head) {
|
||||
if (current->GetData() == aData) {
|
||||
return true;
|
||||
}
|
||||
current = current->GetNext();
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Searches for a value using a safe, non-infinite loop.
|
||||
template <typename T>
|
||||
T TCircularDoublyLinkedList<T>::Search(const T& aData, FCheckNode<T> aCheckNode) const {
|
||||
if (this->IsEmpty()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
TNode<T>* current = this->head->GetNext();
|
||||
while (current != this->head) {
|
||||
if (aCheckNode != nullptr) {
|
||||
if (aCheckNode(current->GetData(), aData)) {
|
||||
return current->GetData();
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (current->GetData() == aData) {
|
||||
return current->GetData();
|
||||
}
|
||||
}
|
||||
current = current->GetNext();
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
// Finds the middle node by traversing to the size/2 index.
|
||||
template <typename T>
|
||||
TNode<T>* TCircularDoublyLinkedList<T>::GetMiddle() const {
|
||||
if (this->IsEmpty()) return nullptr;
|
||||
|
||||
// The safest way for a list where we know the size.
|
||||
int middleIndex = this->size / 2;
|
||||
TNode<T>* current = this->head->GetNext();
|
||||
for (int i = 0; i < middleIndex; ++i) {
|
||||
current = current->GetNext();
|
||||
}
|
||||
return current;
|
||||
}
|
||||
|
||||
#endif // TCIRCULARDOUBLYLINKEDLISTTEMPLATE_HPP
|
||||
Reference in New Issue
Block a user