# Data Structures & Algorithms Fall 2023
#### Instructor:
Dr. Umar Suleman (umar.suleman@itu.edu.pk)
#### TAs:
- Saad Ahmed (bscs21099@itu.edu.pk)
- Muhammad Zain Faraz (bscs21083@itu.edu.pk)
## Assignment 6
### Submission Format:
A single .zip file labelled `BSCSXXXXX-Assignment-4.zip` which should contain ONLY the `.cpp`, `.h` and `.doc` files.
<span style="color: red">Note: Late submissions would not be entertained and any kind of plagiarism would not be tolerated!</span>
### Task 1:
### Task 2:
Implement the `CircularList` class. A normal linked list and a circular linked list differ primarily in how they handle the termination of the list. In a normal linked list, the last node points to `nullptr` indicating the end of the list and traversal stops upon encountering this null pointer. In contrast, a circular linked list connects the last node's next pointer back to the first node, forming a loop. This means that in a circular linked list, traversal continues until we revisit the `head`, as there is no explicit end marked by `nullptr`
```cpp
template <class T>
class CircularList
{
Node<T>* head;
Node<T>* tail;
public:
CircularList(); //initialize both pointers with nullptr
void insert(T _data); //add node with data at the end of list
Node<T>* find(T _data); //return node with matching data
void erase(Node<T>* it); //remove the matching node from list
T& operator[](int i); //return the data at ith node
void print() // print the circular list
~CircularList(); //deallocate all nodes
};
```
### Task 3:
Implement the `DoublyNode` and `DoublyList` class. Each node in a doubly linked list contains two pointers: one pointing to the next node in the sequence and another pointing to the previous node. This bidirectional linkage allows for more flexible traversal and manipulation of the list compared to a singly linked list.
```cpp
template <class T>
struct DoublyNode
{
T data;
DoublyNode<T>* prev; // This points to nullptr for head
DoublyNode<T>* next; // This points to nullptr for tail
DoublyNode(); //initialize next with nullptr
DoublyNode(T _data);
//initialize data with _data and next with nullptr
DoublyNode(T _data, DoublyNode<T>* _prev, DoublyNode<T>* _next);
//You know what to do-
};
```
```cpp
template <class T>
class DoublyList
{
DoublyNode<T>* head;
DoublyNode<T>* tail;
public:
DoublyList(); //initialize both pointers with nullptr
void insert(T _data); //add node with data at the end of list
void insert(T _data, int pos); //add node with data at pos-th index
DoublyNode<T>* find(T_data); //return node with matching data
void erase(DoublyNode<T>* it); //remove the matching node from list
T& operator[](int i); //return the data at ith node
void print_next() // print the list from head to tail
void print_prev() // print the list from tail to head
// Both print functions are of O(n) time complexity!
~DoublyList(); //deallocate all nodes
};
```
### Task 4:
Implement the `Stack` and `Queue` class using linked list:
```cpp
template <class T>
class Stack
{
LinkedList<T> list;
int _size;
public:
Stack(); //initialize your attributes
int size(); //return current size of your stack
bool empty(); //tell whether the stack is empty or not
T top() //return the top element of stack
void push(T _data) //push the data into stack
void pop() //remove the top element of stack
void print() //print your stack from top to bottom
};
```
```cpp
template <class T>
class Queue
{
LinkedList<T> list;
int _size;
public:
Queue(); //initialize your attributes
int size(); //return current size of your queue
bool empty(); //tell whether the queue is empty or not
T front() //return element at front of queue
T back() //return element at back of queue
void push_back(T _data) //pushe the data at the back
void pop_front() //remove the data at front
void print() //print your queue from front to back
};
```
**Note:** Write a brief report discussing the suitability of implementing stacks and queues using either linked lists or arrays. Submit the report along with the `.cpp` and `.h` files.
### Task 5:
Implement a linked list of arrays:-
```cpp
template <class T>
struct ArrNode
{
T* arr;
int size;
int capacity;
ArrNode<T>* next;
//You can add whatever attribute/method you like here.
};
```
```cpp
template <class T>
class ArrList
{
private:
ArrNode<T>* head;
ArrNode<T>* tail;
//You are free to add any additional method/attribute.
public:
ArrList(); //initialize both attributes
void insert(T _data); //add data at the end of list/array
void insert(T _data, int i); //add data at the overall i-th index
pair<ArrNode<T>*,int> find(T _data); //return a pointer to the node-
//and the index at which the data exist
void erase(ArrNode<T>* it, int i); //remove the data from the node-
//if the array becomes empty, delete the node
void erase(ArrNode<T>* it) //remove the node
T& operator[](int i); //return the data at overall i-th index
void print() //print the list of arrays
~ArrList(); //deallocate all nodes
};
```
**Note:** You must implement two versions of this list:-
- For the first one, the size of the array must double for each created node from head to tail. So the first node would be an array of size n, the second would be of size 2n, third one would be of size 4n and so on.
- For the second one, the user would tell the size of the array each time a new node is created.