0% found this document useful (0 votes)
35 views

lab manual

Uploaded by

dumbasshehe9
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
35 views

lab manual

Uploaded by

dumbasshehe9
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as DOCX, PDF, TXT or read online on Scribd
You are on page 1/ 86

1

DSA LAB

LAB MANUAL

Members:
Huma zainab
Amna razzaq
2

Week 1: Arrays

Question 1
Write a program in your preferred programming language to
search from given element in an array. The program should ask the
user to input the array's elements and the value they want to search
for. If the element is found, print the index where it is located;
otherwise, display a message that the element is not found.

Solution:

#include<iostream>
using namespace std;
int main()
{
int arr[10];
int n,value;
int i;
bool index =false;
cout<<"Enter the array elements:";//ENTER Array elements
for(int i=0;i<10;i++)
{
cin>>arr[i];
}
cout<<"Enter the value for search:";
cin>>value; //value for searching
for(i=0;i<10;i++)
{
if(arr[i]==value){ //loop to check if value is found or not
cout<<"value is found at"<<i<<"index"<<endl;
index=true;
break;
}
}
if(!index)
cout<<"Value not found in the array";
return 0;
}

Question no 2
You are given an array of integers, and your task is to write a
program that searches for a specific element in the array. Your
program should prompt the user to input the size of the array, the
elements of the array, and the target element they want to search for.
3

Requirements:
1. The program should first take an integer input for the size of the
array.
2. It should then take the corresponding number of integer inputs as
the array elements.
3. Finally, it should ask for the target element to be searched in the
array.

Solution:

#include <iostream>
using namespace std;
int main() {
int array[10];
int size;
int target;
bool found = false;
cout << "Enter the size of the array: ";
cin >> size;
cout << "Enter the array elements:" << endl;
for (int i = 0; i < size; ++i) {
cin >> array[i];
}
cout << "Enter the element to search for: ";
cin >> target;
for (int i = 0; i < size; ++i) {
if (array[i] == target) {
cout << "Element is found at " << i << " index ." << endl;
found = true;
break;
}
}
if (!found) {
cout << "Element is not found in the array." << endl;
}
return 0;
}
Question no 3
You are given an array of integers, and your task is to write a
program that inserts a new element into a specific position in the
array. The program should allow the user to input the size of the
array, the elements of the array, the element they want to insert, and
the position where the new element should be inserted.

Solution:

#include <iostream>
4

using namespace std;


const int MAX = 100;
int main() {
int array[MAX];
int size;
int element;
int position;
cout << "Enter the size of the array : ";
cin >> size;
cout << "Enter the elements of array:" << endl;
for (int i = 0; i < size; ++i) {
cin >> array[i];
}

cout << "Enter the element to insert: ";


cin >> element;
cout << "Enter the position to insert the new element: ";
cin >> position;
for (int i = size; i > position; --i) //shift elements to make space for new elements
{
array[i] = array[i-1];//moves element to the right side
}
array[position] = element;
++size;
cout << "Updated array:" << endl;
for (int i = 0; i < size; ++i)
{
cout << array[i] << " ";
}
cout << endl;
return 0;
}
Question no 4
Write a program that deletes an element from an array at a specific
position. The program should prompt the user to:
1. Input the size of the array.
2. Input the elements of the array.
3. Input the position from which the element should be deleted.
After deleting the element, print the updated array.

Solution:

#include <iostream>
using namespace std;
const int MAX = 100;
int main() {
int array[MAX];
int size;
int position;
5

cout << "Enter the size of the array:";


cin >> size;
cout << "Enter array elements:" << endl;
for (int i = 0; i < size; ++i) {
cin >> array[i];
}
cout << "Enter the position of the element to delete: ";
cin >> position;
for (int i = position; i < size - 1; ++i) {
array[i] = array[i + 1];
}
--size;
cout << "Updated array:" << endl;
for (int i = 0; i < size; ++i) {
cout << array[i] << " ";
}
cout << endl;
return 0;
}

Importance of Arrays:

1. Arrays provide a simple and efficient way to store and access a fixed number of
elements sequentially.

2. They allow direct access to elements using indices, making them ideal for
operations like searching, inserting, and deleting.

3. Arrays are useful for implementing algorithms that require contiguous memory for
fast processing.

4. In the context of the provided programs, arrays help handle user input dynamically
while enabling tasks like searching, insertion, and deletion.

5. Arrays reduce the complexity of managing multiple variables and enhance code
organization.

Time Complexity:

1. Searching: The time complexity is O(n) for linear search as every element may
need to be checked.
6

2. Insertion: The time complexity is O(n) due to the need to shift elements to the right
for insertion.

3. Deletion: The time complexity is O(n) as elements need to be shifted to the left to
fill the gap after deletion.

4. Access: Accessing any element by index is O(1) since array indexing provides
direct access.

Week 2:sorting

Question no 1
You are given an array of integers, and your task is to write a C++ program that
sorts the array in ascending order using the Bubble Sort algorithm. Your
program should:
1. Prompt the user to enter the size of the array.
2. Allow the user to input the array elements.
3. Sort the array using the Bubble Sort algorithm.
4. Print the sorted array.
Requirements:
- The program should include a function void bubbleSort(int arr[], int n) to
perform the sorting.
- Demonstrate the algorithm by sorting the array step by step.

Solution:

#include<iostream>
using namespace std;
void bubblesort(int arr[],int n)
{
for(int i=0;i<n-1;i++)
{
for(int j=0;j<n-i-1;j++)
{
if(arr[j]>arr[j+1])
swap(arr[j],arr[j+1]);
}
}
}
int main()
{
int n;
cout<<"Enter the size of array:";
cin>>n;
int arr[n];
cout<<"Enter the elements of array:"<<endl;
7

for(int i=0;i<n;i++)
{
cin>>arr[i];
}
bubblesort(arr,n);

cout<<"sorted array is:";


for(int i=0;i<n;i++)
{
cout<<arr[i]<<" ";
}
cout<<endl;
return 0;
}

Question no 2
Write a C++ program that sorts an array of strings in alphabetical
order using the Insertion Sort algorithm. The program should:
1. Prompt the user to enter the number of strings.
2. Allow the user to input the strings.
3. Sort the array of strings using the Insertion Sort algorithm.
4. Print the sorted array.
Requirements:
- The program should include a function `void insertionSort(string
arr[], int n)` to perform the sorting.
- The program should handle strings of varying lengths.

Solution:
#include <iostream>
#include <string>
using namespace std;

// Function to perform Insertion Sort on an array of strings


void insertionSort(string arr[], int n) {
for (int i = 1; i < n; i++) {
string key = arr[i];
int j = i - 1;

// Move elements of arr[0..i-1] that are greater than key


// to one position ahead of their current position
while (j >= 0 && arr[j] > key) {
arr[j + 1] = arr[j];
j = j - 1;
}
arr[j + 1] = key;
}
}
int main() {
8

int n;

// Prompt user to enter the number of strings


cout << "Enter the number of strings: ";
cin >> n;

// Create an array of strings


string* arr = new string[n];

// Prompt user to enter the strings


cout << "Enter " << n << " strings:\n";
for (int i = 0; i < n; i++) {
cin >> arr[i];
}
// Sort the array of strings using Insertion Sort
insertionSort(arr, n);
// Print the sorted array
cout << "Sorted strings in alphabetical order:\n";
for (int i = 0; i < n; i++) {
cout << arr[i] << endl;
}
// Free the dynamically allocated memory
delete[] arr;
return 0;
}

Question no 3
Write a C++ program that sorts an array of floating-point numbers in
descending order using the Selection Sort algorithm. Your program should:
1. Prompt the user to enter the number of elements in the array.
2. Allow the user to input the array elements.
3. Sort the array using the Selection Sort algorithm.
4. Print the sorted array
5. Requirements:
- The program should include a function void selectionSort(float arr[], int n) to
perform the sorting.
- Provide comments in the code explaining how the selection sort algorithm
works.

Solution:
#include<iostream>
using namespace std;
void selectionsort(int arr[],int n)
{
int maxindex;
int i;
for(int i=0;i<n-1;i++)
int maxindex=i;
for(int j=i+1;j<n;j++)
{
9

if(arr[j]>arr[maxindex])
{
maxindex=j;
}
swap(arr[i],arr[maxindex]);
}
}
int main()
{
int n;
cout<<"Enter the size of array:";
cin>>n;
int arr[n];
cout<<"Enter the elements of array:"<<endl;
for(int i=0;i<n;i++)
{
cin>>arr[i];
}
selectionsort(arr,n);
cout<<"sorted array is:";
for(int i=0;i<n;i++)
{
cout<<arr[i]<<" ";
}
cout<<endl;
return 0;
}

Importance of Sorting:

1. Sorting organizes data in a structured way, enabling efficient searching,


comparison, and analysis.

2. It improves the usability and readability of data for applications like databases,
algorithms, and file management.

3. In the provided examples, sorting simplifies operations on arrays of integers,


strings, and floating-point numbers for both ascending and descending order.

4. Sorting is fundamental to optimizing algorithms, such as binary search, which


requires sorted input.
10

5. It aids in reducing computational complexity for subsequent operations by pre-


arranging the data.

Time Complexity:

1. Bubble Sort:

Best case: O(n) (when the array is already sorted).

Worst and Average case: O(n²) (due to nested loops for pairwise comparison).

2. Insertion Sort:

Best case: O(n) (when the array is already sorted).

Worst and Average case: O(n²) (shifting elements for insertion).

3. Selection Sort:

Always O(n²) for all cases, as every element is compared and swapped to find the
maximum or minimum.

Week 3: Abstraction

Question no 1:
Task 1: Remote Control (Abstraction)
Create a class for a remote control that can change channels and adjust the
volume without
revealing how these actions are performed internally.
Requirements:
- Implement methods for changing channels and adjusting the volume.
- Ensure that the internal processes are hidden from the user.

Solution:
#include <iostream>
using namespace std;
// RemoteControl class that abstracts channel and volume management
class RemoteControl {
private:
int currentChannel;
int currentVolume;

// Internal method to validate channel (hidden from the user)


11

bool isValidChannel(int channel) {


return channel >= 1 && channel <= 999;
}

// Internal method to validate volume (hidden from the user)


bool isValidVolume(int volume) {
return volume >= 0 && volume <= 100;
}
public:
// Constructor to initialize default channel and volume
RemoteControl() {
currentChannel = 1; // Default channel
currentVolume = 50; // Default volume level
}

// Method to change channel


void changeChannel(int newChannel) {
if (isValidChannel(newChannel)) {
currentChannel = newChannel;
cout << "Channel changed to: " << currentChannel << endl;
} else {
cout << "Invalid channel. Please enter a channel between 1 and 999." << endl;
}
}
// Method to increase volume
void increaseVolume() {
if (currentVolume < 100) {
currentVolume++;
cout << "Volume increased to: " << currentVolume << endl;
} else {
cout << "Volume is already at the maximum level (100)." << endl;
}
}
// Method to decrease volume
void decreaseVolume() {
if (currentVolume > 0) {
currentVolume--;
cout << "Volume decreased to: " << currentVolume << endl;
} else {
cout << "Volume is already at the minimum level (0)." << endl;
}
}

// Method to display the current status of the remote


void displayStatus() const {
cout << "Current Channel: " << currentChannel << endl;
cout << "Current Volume: " << currentVolume << endl;
}
};
int main() {
12

// Create an instance of RemoteControl


RemoteControl remote;

// Test changing channels and adjusting volume


remote.displayStatus();

remote.changeChannel(101); // Change channel to 101


remote.increaseVolume(); // Increase volume
remote.decreaseVolume(); // Decrease volume

remote.changeChannel(1000); // Invalid channel


remote.displayStatus(); // Display the current status

return 0;
}

Question no 2:
Write a class for a bank account that keeps the account balance private and
provides methods to deposit, withdraw, and check the balance.
Requirements:
- The balance should be a private attribute.
- Include methods for depositing, withdrawing, and checking the balance,
ensuring proper access control.

Solution:
#include <iostream>
using namespace std;
class BankAccount {
private:
double balance; // Private attribute to store the balance
public:
// Constructor to initialize the balance with an optional starting value
BankAccount(double initialBalance = 0.0) {
if (initialBalance >= 0.0) {
balance = initialBalance;
} else {
balance = 0.0;
cout << "Invalid initial balance. Setting balance to 0." << endl;
}
}
// Method to deposit money into the account
void deposit(double amount) {
if (amount > 0) {
balance += amount;
cout << "Deposited: $" << amount << ". New Balance: $" << balance << endl;
} else {
cout << "Invalid deposit amount. Amount must be positive." << endl;
}
}
// Method to withdraw money from the account
13

void withdraw(double amount) {


if (amount > 0) {
if (amount <= balance) {
balance -= amount;
cout << "Withdrew: $" << amount << ". New Balance: $" << balance <<
endl;
} else {
cout << "Insufficient balance. Withdrawal failed." << endl;
}
} else {
cout << "Invalid withdrawal amount. Amount must be positive." << endl;
}
}
// Method to check the current balance
double checkBalance() const {
return balance;
} };
int main() {
// Create a bank account with an initial balance of $100
BankAccount account(100.0);
// Check the initial balance
cout << "Initial Balance: $" << account.checkBalance() << endl;
// Deposit money into the account
account.deposit(50.0);
// Withdraw money from the account
account.withdraw(30.0);
// Attempt to withdraw more than the balance
account.withdraw(150.0);
// Check the final balance
cout << "Final Balance: $" << account.checkBalance() << endl;
return 0;
}

Question no 3:
Develop a class for a student that stores the student's name and grade privately,
with methods to set and get the grade in a controlled manner.
Requirements:
- Keep the student's name and grade private.
- Provide methods to safely set and retrieve the grade.

Solution:
#include <iostream>
#include <string>
using namespace std;
class Student {
private:
string name; // Private datamember to store the student's name
int grade; // Private datamember to store the student's grade
14

public:
Student(string studentName, int studentGrade) {// constructor
name = studentName;
setGrade(studentGrade);
}
// Method to set the student's grade in a controlled manner
void setGrade(int newGrade) {
if (newGrade >= 0 && newGrade <= 100) {
grade = newGrade;
cout << "Grade for " << name << " set to: " << grade << endl;
} else {
cout << "Invalid grade. Please enter a grade between 0 and 100." << endl;
}
}
// Method to retrieve the student's grade
int getGrade() const {
return grade;
}
// Method to retrieve the student's name
string getName() const {
return name;
}
};
int main() {
// Create a student object with initial name and grade
Student student("Ali", 85);
// Get and display the student's name and grade
cout << student.getName() << "'s current grade is: " << student.getGrade() << endl;
// Attempt to set an invalid grade
student.setGrade(105);
// Set a valid grade
student.setGrade(92);
// Display the updated grade
cout << student.getName() << "'s updated grade is: " << student.getGrade() <<
endl;
return 0;
}

Question no 4:
Implement a class for a smart door lock that keeps its lock status private and
only allows locking and
unlocking through specific methods.
Requirements:
- The lock status should be a private attribute.
- Provide methods to lock and unlock the door, ensuring proper encapsulation.

Solution:
#include <iostream>
using namespace std;
class SmartDoorLock {
15

private:
bool isLocked; // Private datamember
public:
SmartDoorLock()//constructor
{
isLocked = false; // Initially, the door is unlocked
}
void lock()// Method to lock the door
{
if (!isLocked) {
isLocked = true;
cout << "Door is now locked." << endl;
} else {
cout << "Door is already locked." << endl;
}
}
void unlock() {// Method to unlock the door
if (isLocked) {
isLocked = false;
cout << "Door is now unlocked." << endl;
} else {
cout << "Door is already unlocked." << endl;
}
}
void checkStatus() const {// Method to check the current lock status
if (isLocked) {
cout << "The door is locked." << endl;
} else {
cout << "The door is unlocked." << endl;
}
}
};

int main() {
SmartDoorLock doorLock;//member of class

doorLock.checkStatus();
doorLock.lock();
doorLock.lock();
doorLock.unlock();
doorLock.unlock();
doorLock.checkStatus();

return 0;
}

Importance of Encapsulation:
16

1. Encapsulation ensures the protection of sensitive data by restricting direct access to


it.

2. It hides the internal implementation details, providing a clean and secure interface
for users.

3. In the examples, attributes like channel, volume, balance, grade, and lock status are
made private to ensure controlled and safe operations.

4. Encapsulation promotes modularity, enabling easier debugging and updates without


affecting external code.

5. It maintains consistency by enforcing rules for data manipulation through specific


methods.

---

Time Complexity:

1. Remote Control (Abstraction):

Channel and volume operations execute in O(1) since they involve simple condition
checks or assignments.

2. Bank Account (Encapsulation):

Deposit, withdrawal, and balance checks also operate in O(1) due to straightforward
computations.

3. Student (Encapsulation):

Setting and getting the grade operate in O(1), as they only involve validation and
assignment.

4. Smart Door Lock (Encapsulation):


17

Locking, unlocking, and status checks run in O(1) because they involve simple
boolean operations.

Week 4:Singly linked list

Question no 1:
You are tasked with designing a library book management system using a singly
linked list. Each book has an ID, title, author, and availability status (either
"Available" or "Checked Out"). The system should allow the librarian to:
- Add a new book to the collection.
- Delete a book by its ID when it is no longer available in the library.
- Display all books, along with their availability status.
- Search for a book by its ID.
- Update the status of a book when it is checked out or returned.
This system can grow dynamically as the library expands, making a singly linked
list a suitable choice.
Solution:
#include <iostream>
#include <string>
using namespace std;
// Structure for a book in the library
struct Book {
int id;
string title;
string author;
bool isAvailable; // true = Available, false = Checked Out
Book* next; // Pointer to the next book in the linked list
};
// Class to manage the linked list of books
class Library {
private:
Book* head; // Pointer to the head (first book) of the list

public:
// Constructor to initialize an empty library
Library() {
head = nullptr;
}
// Method to add a new book to the collection
void addBook(int id, string title, string author) {
Book* newBook = new Book;
newBook->id = id;
newBook->title = title;
newBook->author = author;
newBook->isAvailable = true; // By default, the book is available
newBook->next = head; // Insert the new book at the beginning of the list
head = newBook;
cout << "Book added successfully: " << title << endl;
}
18

// Method to delete a book by its ID


void deleteBook(int id) {
Book* temp = head;
Book* prev = nullptr;
// If the book to be deleted is the head
if (temp != nullptr && temp->id == id) {
head = temp->next;
delete temp;
cout << "Book with ID " << id << " deleted successfully." << endl;
return;
}
// Search for the book to be deleted, keep track of the previous book
while (temp != nullptr && temp->id != id) {
prev = temp;
temp = temp->next;
}
// If the book was not found
if (temp == nullptr) {
cout << "Book with ID " << id << " not found." << endl;
return;
}
// Unlink the book from the list and delete it
prev->next = temp->next;
delete temp;
cout << "Book with ID " << id << " deleted successfully." << endl;
}
// Method to display all books in the library
void displayBooks() {
Book* temp = head;
if (temp == nullptr) {
cout << "No books available in the library." << endl;
return;
}
cout << "Books in the library:\n";
while (temp != nullptr) {
cout << "ID: " << temp->id << ", Title: " << temp->title << ", Author: " <<
temp->author;
cout << ", Status: " << (temp->isAvailable ? "Available" : "Checked Out") <<
endl;
temp = temp->next;
}
}
// Method to search for a book by its ID
void searchBook(int id) {
Book* temp = head;
while (temp != nullptr) {
if (temp->id == id) {
cout << "Book found!\n";
cout << "ID: " << temp->id << ", Title: " << temp->title << ", Author: " <<
temp>author;
19

cout << ", Status: " << (temp->isAvailable ? "Available" : "Checked Out")
<< endl;
return;
}
temp = temp->next;
}
cout << "Book with ID " << id << " not found." << endl;
}
// Method to update the availability status of a book (Checked Out or Returned)
void updateStatus(int id, bool isAvailable) {
Book* temp = head;
while (temp != nullptr) {
if (temp->id == id) {
temp->isAvailable = isAvailable;
cout << "Book status updated: " << temp->title << " is now "
<< (isAvailable ? "Available" : "Checked Out") << endl;
return;
}
temp = temp->next;
}

cout << "Book with ID " << id << " not found." << endl;
}
};
int main() {
Library library;
// Adding books to the library
library.addBook(101, "The Great Gatsby", "F. Scott Fitzgerald");
library.addBook(102, "1984", "George Orwell");
library.addBook(103, "To Kill a Mockingbird", "Harper Lee");

// Displaying all books


library.displayBooks();
// Searching for a book by ID
library.searchBook(102);
// Updating the status of a book (checking it out)
library.updateStatus(102, false);
// Displaying all books after checking one out
library.displayBooks();
// Returning a book (updating status to available)
library.updateStatus(102, true);
// Deleting a book
library.deleteBook(101);
// Displaying all books after deletion
library.displayBooks();
return 0;
}

Question no 2:
20

You are required to implement an employee record management system using a


singly linked list. Each employee has an ID, name, department, and salary. The
system should allow the HR department to:
- Add new employee records.
- Delete an employee by their ID.
- Search for an employee by their ID.
- Update the salary of an employee.
- Display all employee records.
A singly linked list allows easy insertion, deletion, and search operations for the
growing and shrinking employee list.

Solution:
#include <iostream>
#include <string>

using namespace std;


class EmployeeNode {
public:
int emp_id;
string name;
string department;
double salary;
EmployeeNode* next;
EmployeeNode(int id, const string& emp_name, const string& dept, double sal)
: emp_id(id), name(emp_name), department(dept), salary(sal), next(nullptr) {}
};
class EmployeeLinkedList {
private:
EmployeeNode* head;
public:
EmployeeLinkedList() : head(nullptr) {}
// Add a new employee record
void add_employee(int emp_id, const string& name, const string& department,
double salary) {
EmployeeNode* new_employee = new EmployeeNode(emp_id, name,
department, salary);
if (!head) {
head = new_employee;
} else {
EmployeeNode* temp = head;
while (temp->next) {
temp = temp->next;
}
temp->next = new_employee;
}
cout << "Employee " << name << " added successfully!" << endl;
}
// Delete an employee by ID
void delete_employee(int emp_id) {
EmployeeNode* temp = head;
21

EmployeeNode* prev = nullptr;


// If head needs to be removed
if (temp && temp->emp_id == emp_id) {
head = temp->next;
delete temp;
cout << "Employee with ID " << emp_id << " deleted successfully!" << endl;
return;
}
// Search for the employee to delete
while (temp) {
if (temp->emp_id == emp_id) {
break;
}
prev = temp;
temp = temp->next;
}
if (!temp) {
cout << "Employee with ID " << emp_id << " not found." << endl;
return;
}
// Unlink the node from the linked list
prev->next = temp->next;
delete temp;
cout << "Employee with ID " << emp_id << " deleted successfully!" << endl;
}

// Search for an employee by ID


void search_employee(int emp_id) {
EmployeeNode* temp = head;
while (temp) {
if (temp->emp_id == emp_id) {
cout << "Employee found: ID=" << temp->emp_id
<< ", Name=" << temp->name
<< ", Department=" << temp->department
<< ", Salary=" << temp->salary << endl;
return;
}
temp = temp->next;
}
cout << "Employee with ID " << emp_id << " not found." << endl;
}
// Update salary of an employee
void update_salary(int emp_id, double new_salary) {
EmployeeNode* temp = head;
while (temp) {
if (temp->emp_id == emp_id) {
temp->salary = new_salary;
cout << "Salary of Employee with ID " << emp_id << " updated to " <<
new_salary << "." << endl;
return;
22

}
temp = temp->next;
}
cout << "Employee with ID " << emp_id << " not found." << endl;
}

// Display all employee records


void display_employees() {
if (!head) {
cout << "No employees in the list." << endl;
return;
}
EmployeeNode* temp = head;
while (temp) {
cout << "ID=" << temp->emp_id
<< ", Name=" << temp->name
<< ", Department=" << temp->department
<< ", Salary=" << temp->salary << endl;
temp = temp->next;
}
}
~EmployeeLinkedList() {
while (head) {
EmployeeNode* temp = head;
head = head->next;
delete temp;
}
}
};
int main() {
EmployeeLinkedList employee_list;
// Adding employees
employee_list.add_employee(101, "John Doe", "Engineering", 60000);
employee_list.add_employee(102, "Jane Smith", "HR", 50000);
employee_list.add_employee(103, "Emily Davis", "Marketing", 45000);
// Displaying all employees
cout << "\nAll Employees:" << endl;
employee_list.display_employees();
// Searching for an employee by ID
cout << "\nSearch Employee with ID 102:" << endl;
employee_list.search_employee(102);
// Updating salary
cout << "\nUpdating salary of Employee with ID 103:" << endl;
employee_list.update_salary(103, 48000);
// Deleting an employee
cout << "\nDeleting Employee with ID 101:" << endl;
employee_list.delete_employee(101);
// Displaying all employees after deletion
cout << "\nAll Employees after deletion:" << endl;
employee_list.display_employees();
23

return 0;
}
Question no 3:

In this scenario, you are designing a university course registration system using a
singly linked list. Each course is represented by a unique course code, name, and
the number of enrolled students. The system should allow:
- Adding new courses to the list of available courses.
- Removing a course that is no longer offered by the university.
- Displaying all available courses.
- Searching for a course by its course code.
- Updating the number of students enrolled in a course when new students join
or drop the course. The dynamic nature of course lists is well suited to a linked
list structure.

Solution:
#include <iostream>
#include <string>
using namespace std;
class CourseNode {
public:
string course_code;
string course_name;
int enrolled_students;
CourseNode* next;
CourseNode(const string& code, const string& name, int students)
: course_code(code), course_name(name), enrolled_students(students),
next(nullptr) {}
};
class CourseLinkedList {
private:
CourseNode* head;
public:
CourseLinkedList() : head(nullptr) {}
// Add a new course
void add_course(const string& course_code, const string& course_name, int
enrolled_students) {
CourseNode* new_course = new CourseNode(course_code, course_name,
enrolled_students);
if (!head) {
head = new_course;
} else {
CourseNode* temp = head;
while (temp->next) {
temp = temp->next;
}
temp->next = new_course;
}
cout << "Course " << course_name << " added successfully!" << endl;
}
24

// Remove a course by course code


void remove_course(const string& course_code) {
CourseNode* temp = head;
CourseNode* prev = nullptr;
// If the head needs to be removed
if (temp && temp->course_code == course_code) {
head = temp->next;
delete temp;
cout << "Course with code " << course_code << " removed successfully!" <<
endl;
return;
}
// Search for the course to remove
while (temp) {
if (temp->course_code == course_code) {
break;
}
prev = temp;
temp = temp->next;
}
if (!temp) {
cout << "Course with code " << course_code << " not found." << endl;
return;
}
// Unlink the node from the linked list
prev->next = temp->next;
delete temp;
cout << "Course with code " << course_code << " removed successfully!" <<
endl;
}
// Display all available courses
void display_courses() {
if (!head) {
cout << "No courses available." << endl;
return;
}
CourseNode* temp = head;
while (temp) {
cout << "Course Code: " << temp->course_code
<< ", Course Name: " << temp->course_name
<< ", Enrolled Students: " << temp->enrolled_students << endl;
temp = temp->next;
}
}
// Search for a course by course code
void search_course(const string& course_code) {
CourseNode* temp = head;
while (temp) {
if (temp->course_code == course_code) {
cout << "Course found: Course Code=" << temp->course_code
25

<< ", Course Name=" << temp->course_name


<< ", Enrolled Students=" << temp->enrolled_students << endl;
return;
}
temp = temp->next;
}
cout << "Course with code " << course_code << " not found." << endl;
}
// Update the number of enrolled students
void update_enrollment(const string& course_code, int new_enrollment) {
CourseNode* temp = head;
while (temp) {
if (temp->course_code == course_code) {
temp->enrolled_students = new_enrollment;
cout << "Enrollment for Course " << course_code << " updated to " <<
new_enrollment << "." << endl;
return;
}
temp = temp->next;
}
cout << "Course with code " << course_code << " not found." << endl;
}

~CourseLinkedList() {
while (head) {
CourseNode* temp = head;
head = head->next;
delete temp;
}
}
};
int main() {
CourseLinkedList course_list;
// Adding courses
course_list.add_course("CS101", "Introduction to Computer Science", 30);
course_list.add_course("MATH201", "Calculus I", 25);
course_list.add_course("PHY101", "Physics I", 20);
// Displaying all courses
cout << "\nAll Available Courses:" << endl;
course_list.display_courses();
// Searching for a course by course code
cout << "\nSearching for Course with Code CS101:" << endl;
course_list.search_course("CS101");
// Updating enrollment
cout << "\nUpdating enrollment for Course MATH201:" << endl;
course_list.update_enrollment("MATH201", 28);

// Removing a course
cout << "\nRemoving Course with Code PHY101:" << endl;
course_list.remove_course("PHY101");
26

// Displaying all courses after removal


cout << "\nAll Available Courses after removal:" << endl;
course_list.display_courses();
return 0;
}
Question no 4:

You are tasked with creating a shopping cart system using a singly linked list.
Each item in the shopping cart contains the product ID, name, price, and
quantity. The system should allow the user to:
- Add items to the shopping cart.
- Remove an item from the cart by its product ID.
- Update the quantity of an item in the cart.
- Display all items currently in the shopping cart.
- Calculate the total cost of the cart.
Using a linked list allows the shopping cart to grow or shrink dynamically as
items are added or removed.

Solution:
#include <iostream>
#include <string>
using namespace std;
class CartItemNode {
public:
int product_id;
string product_name;
double price;
int quantity;
CartItemNode* next;

CartItemNode(int id, const string& name, double pr, int qty)


: product_id(id), product_name(name), price(pr), quantity(qty), next(nullptr) {}
};

class ShoppingCart {
private:
CartItemNode* head;
public:
ShoppingCart() : head(nullptr) {}
// Add item to the cart
void add_item(int product_id, const string& product_name, double price, int
quantity) {
CartItemNode* new_item = new CartItemNode(product_id, product_name,
price, quantity);
if (!head) {
head = new_item;
} else {
CartItemNode* temp = head;
while (temp->next) {
temp = temp->next;
27

}
temp->next = new_item;
}
cout << "Item " << product_name << " added to the cart." << endl;
}
// Remove item from the cart
void remove_item(int product_id) {
CartItemNode* temp = head;
CartItemNode* prev = nullptr;
while (temp) {
if (temp->product_id == product_id) {
if (prev) {
prev->next = temp->next;
} else {
head = temp->next;
}
delete temp;
cout << "Item with ID " << product_id << " removed from the cart." <<
endl;
return;
}
prev = temp;
temp = temp->next;
}
cout << "Item with ID " << product_id << " not found." << endl;
}
// Update item quantity
void update_quantity(int product_id, int new_quantity) {
CartItemNode* temp = head;
while (temp) {
if (temp->product_id == product_id) {
temp->quantity = new_quantity;
cout << "Quantity for item " << product_id << " updated to " <<
new_quantity << "." << endl;
return;
}
temp = temp->next;
}
cout << "Item with ID " << product_id << " not found." << endl;
}
// Display items in the cart
void display_items() {
if (!head) {
cout << "Cart is empty." << endl;
return;
}
CartItemNode* temp = head;
while (temp) {
cout << "Product ID: " << temp->product_id
<< ", Name: " << temp->product_name
28

<< ", Price: " << temp->price


<< ", Quantity: " << temp->quantity << endl;
temp = temp->next;
}
}
// Calculate total cost
double calculate_total() {
double total = 0.0;
CartItemNode* temp = head;
while (temp) {
total += temp->price * temp->quantity;
temp = temp->next;
}
return total;
}
~ShoppingCart() {
while (head) {
CartItemNode* temp = head;
head = head->next;
delete temp;
}
}
};

int main() {
ShoppingCart cart;
// Adding items to the cart
cart.add_item(1, "Laptop", 999.99, 1);
cart.add_item(2, "Headphones", 199.99, 2);
cart.add_item(3, "Mouse", 49.99, 1);
// Displaying items in the cart
cout << "\nItems in the Shopping Cart:" << endl;
cart.display_items();
// Updating quantity
cout << "\nUpdating quantity of item with ID 2:" << endl;
cart.update_quantity(2, 3);
// Removing an item
cout << "\nRemoving item with ID 1:" << endl;
cart.remove_item(1);
// Displaying items again
cout << "\nItems in the Shopping Cart after removal:" << endl;
cart.display_items();
// Calculating total cost
double total = cart.calculate_total();
cout << "\nTotal cost of the cart: $" << total << endl;
return 0;
}

Question no 5:
29

Contact Management SystemYou need to develop a contact management system


using a singly linked list. Each contact has a name, phone number, and email
address. The system should allow the user to:
- Add new contacts to the list.
- Delete a contact by name.
- Search for a contact by name.
- Update a contact's details (phone number or email address).
- Display all contacts in the system.
This system requires dynamic allocation of memory as the contact list grows or
shrinks, making the linked list a natural choice.

Solution:
#include <iostream>
#include <string>
using namespace std;
class ContactNode {
public:
string name;
string phone_number;
string email_address;
ContactNode* next;
ContactNode(const string& name, const string& phone, const string& email)
: name(name), phone_number(phone), email_address(email), next(nullptr) {}
};
class ContactManager {
private:
ContactNode* head;
public:
ContactManager() : head(nullptr) {}
// Add a new contact
void add_contact(const string& name, const string& phone, const string& email) {
ContactNode* new_contact = new ContactNode(name, phone, email);
if (!head) {
head = new_contact;
} else {
ContactNode* temp = head;
while (temp->next) {
temp = temp->next;
}
temp->next = new_contact;
}
cout << "Contact " << name << " added successfully!" << endl;
}
// Delete a contact by name
void delete_contact(const string& name) {
ContactNode* temp = head;
ContactNode* prev = nullptr;
while (temp) {
if (temp->name == name) {
if (prev) {
30

prev->next = temp->next;
} else {
head = temp->next;
}
delete temp;
cout << "Contact " << name << " deleted successfully!" << endl;
return;
}
prev = temp;
temp = temp->next;
}
cout << "Contact " << name << " not found." << endl;
}
// Search for a contact by name
void search_contact(const string& name) {
ContactNode* temp = head;
while (temp) {
if (temp->name == name) {
cout << "Contact found: Name=" << temp->name
<< ", Phone Number=" << temp->phone_number
<< ", Email=" << temp->email_address << endl;
return;
}
temp = temp->next;
}
cout << "Contact " << name << " not found." << endl;
}
// Update a contact's details
void update_contact(const string& name, const string& new_phone, const string&
new_email) {
ContactNode* temp = head;
while (temp) {
if (temp->name == name) {
temp->phone_number = new_phone;
temp->email_address = new_email;
cout << "Contact " << name << " updated successfully!" << endl;
return;
}
temp = temp->next;
}
cout << "Contact " << name << " not found." << endl;
}
// Display all contacts
void display_contacts() {
if (!head) {
cout << "No contacts available." << endl;
return;
}

ContactNode* temp = head;


31

while (temp) {
cout << "Name: " << temp->name
<< ", Phone: " << temp->phone_number
<< ", Email: " << temp->email_address << endl;
temp = temp->next;
}
}
~ContactManager() {
while (head) {
ContactNode* temp = head;
head = head->next;
delete temp;
}
}
};
int main() {
ContactManager contact_manager;
// Adding contacts
contact_manager.add_contact("Alice", "123-456-7890", "[email protected]");
contact_manager.add_contact("Bob", "098-765-4321", "[email protected]");
contact_manager.add_contact("Charlie", "555-555-5555",
"[email protected]");
// Displaying all contacts
cout << "\nAll Contacts:" << endl;
contact_manager.display_contacts();
// Searching for a contact
cout << "\nSearching for contact Alice:" << endl;
contact_manager.search_contact("Alice");
// Updating a contact's details
cout << "\nUpdating contact Bob's details:" << endl;
contact_manager.update_contact("Bob", "111-222-3333",
"[email protected]");
// Deleting a contact
cout << "\nDeleting contact Charlie:" << endl;
contact_manager.delete_contact("Charlie");
// Displaying all contacts after deletion
cout << "\nAll Contacts after deletion:" << endl;
contact_manager.display_contacts();
return 0;
}

Importance and Time Complexity of Singly Linked Lists


Singly linked lists are a fundamental data structure used in various programming
applications. Their importance stems from several key advantages:

Dynamic memory allocation: Unlike arrays, singly linked lists don't require a pre-
defined size. Nodes are created and added to the list as needed, making them ideal for
scenarios where the data size is unknown beforehand.
32

Efficient insertion and deletion: Inserting or deleting a node in a singly linked list only
involves modifying the pointers of the surrounding nodes. This operation takes
constant time (O(1)) in most cases, except for specific scenarios (explained in time
complexity).
Simple implementation: Singly linked lists are relatively easy to understand and
implement compared to more complex structures like trees or graphs.
However, singly linked lists also have limitations:

No random access: You cannot directly access any element by its index like in an
array. Traversal requires starting from the head and iterating through each node until
you find the target element. This leads to a linear time complexity (O(n)) for search
operations.
Memory overhead: Each node in a singly linked list requires additional space to store
the next pointer. While not significant for small datasets, it can be a factor for large
amounts of data.
Time Complexity of Singly Linked List Operations
Here's a breakdown of the time complexity for common operations in a singly linked
list:

Insertion:
At the beginning: O(1) - Modifying the head pointer takes constant time.
At the end: O(n) in the worst case - You need to traverse the list to find the last node
before insertion. In some cases, with a tail pointer, insertion at the end can be O(1).
In the middle: O(n) - Requires finding the node before the insertion point.
Deletion:
At the beginning: O(1) - Modifying the head pointer takes constant time.
At the end: O(n) in the worst case - You need to traverse the list to find the last node
before deletion. In some cases, with a tail pointer, deletion at the end can be O(1).
In the middle: O(n) - Requires finding the node before the deletion point.
Search: O(n) - You need to traverse the list from the beginning to find the desired
element.
Traversal: O(n) - You need to iterate through each node in the list.
Note: These complexities are averages. In some cases, with modifications like
keeping track of the tail node or using doubly linked lists, certain operations can be
optimized.

Week 5: doubly linked list

Question no 1:

You are tasked with designing a library book borrowing system using a doubly
linked list as a stack. Each book borrowed by a member should be added to the
stack, and returned books should be removed. The system should allow: -
Pushing a book's details (ID, title, author) onto the stack when borrowed. -
Popping the book's details from the stack when returned. - Viewing the most
33

recently borrowed book using the Top() function. Implement the Push(), Pop(),
and Top() functions using a doubly linked list.

Solution:
#include <iostream>
#include <string>
using namespace std;

// Node class representing a book in the stack


class Node {
public:
int book_id;
string title;
string author;
Node* prev;
Node* next;

// Constructor to initialize a new node


Node(int id, string t, string a) {
book_id = id;
title = t;
author = a;
prev = NULL;
next = NULL;
}
};

// BookStack class representing the doubly linked list stack


class BookStack {
private:
Node* top; // Pointer to the top of the stack (most recently borrowed book)

public:
// Constructor to initialize the stack
BookStack() {
top = NULL;
}

// Function to push a new book onto the stack


void Push(int book_id, string title, string author) {
Node* new_node = new Node(book_id, title, author);

if (top == NULL) {
// If the stack is empty, the new book becomes the top
top = new_node;
} else {
// Add the new book on top of the current top
new_node->prev = top;
top->next = new_node;
top = new_node;
34

cout << "Book '" << title << "' by " << author << " (ID: " << book_id << ") has
been borrowed." << endl;
}

// Function to pop the top book from the stack


void Pop() {
if (top == NULL) {
cout << "No books to return. The stack is empty." << endl;
return;
}

// Get the details of the book at the top


int book_id = top->book_id;
string title = top->title;
string author = top->author;

// Move the top pointer to the previous node


Node* temp = top;
if (top->prev != NULL) {
top = top->prev;
top->next = NULL;
} else {
// The stack becomes empty
top = NULL;
}

// Delete the old top node


delete temp;

cout << "Book '" << title << "' by " << author << " (ID: " << book_id << ") has
been returned." << endl;
}

// Function to view the most recently borrowed book


void Top() {
if (top == NULL) {
cout << "No books have been borrowed yet." << endl;
return;
}

// Display the details of the book at the top of the stack


cout << "Most recently borrowed book: '" << top->title << "' by " << top->author
<< " (ID: " << top->book_id << ")" << endl;
}
};

int main() {
BookStack library_stack;
35

// Borrow some books


library_stack.Push(1, "1984", "George Orwell");
library_stack.Push(2, "Brave New World", "Aldous Huxley");
library_stack.Push(3, "Fahrenheit 451", "Ray Bradbury");

// View the most recently borrowed book


library_stack.Top();

// Return a book
library_stack.Pop();

// View the most recently borrowed book again


library_stack.Top();

// Return all books


library_stack.Pop();
library_stack.Pop();

// Try to return a book from an empty stack


library_stack.Pop();

return 0;
}

Question no 2:
You are required to develop an employee task management system using a stack
implemented with a doubly linked list. Tasks assigned to employees are managed
using this stack. The system should allow:
- Adding a new task to the stack using the Push() function.
- Removing a completed task using the Pop() function.
- Viewing the current task assigned using the Top() function.
Implement the above functions using a doubly linked list.

Solution:
#include <iostream>
#include <string>
using namespace std;

// Node class representing a task in the stack


class Node {
public:
int task_id;
string task_description;
Node* prev;
Node* next;

// Constructor to initialize a new node (task)


Node(int id, string description) {
task_id = id;
36

task_description = description;
prev = NULL;
next = NULL;
}
};

// TaskStack class representing the doubly linked list stack for tasks
class TaskStack {
private:
Node* top; // Pointer to the top of the stack (most recent task)

public:
// Constructor to initialize the stack
TaskStack() {
top = NULL;
}

// Function to push a new task onto the stack


void Push(int task_id, string task_description) {
Node* new_node = new Node(task_id, task_description);

if (top == NULL) {
// If the stack is empty, the new task becomes the top
top = new_node;
} else {
// Add the new task on top of the current top
new_node->prev = top;
top->next = new_node;
top = new_node;
}

cout << "Task '" << task_description << "' (ID: " << task_id << ") has been
assigned." << endl;
}

// Function to pop the top task from the stack


void Pop() {
if (top == NULL) {
cout << "No tasks to complete. The stack is empty." << endl;
return;
}

// Get the details of the task at the top


int task_id = top->task_id;
string task_description = top->task_description;

// Move the top pointer to the previous node


Node* temp = top;
if (top->prev != NULL) {
top = top->prev;
37

top->next = NULL;
} else {
// The stack becomes empty
top = NULL;
}

// Delete the old top node


delete temp;

cout << "Task '" << task_description << "' (ID: " << task_id << ") has been
completed." << endl;
}

// Function to view the current task (top task)


void Top() {
if (top == NULL) {
cout << "No tasks are currently assigned." << endl;
return;
}

// Display the details of the task at the top of the stack


cout << "Current task: '" << top->task_description << "' (ID: " << top->task_id
<< ")" << endl;
}
};

int main() {
TaskStack employee_tasks;

// Assign some tasks


employee_tasks.Push(1, "Design the database schema");
employee_tasks.Push(2, "Implement the user authentication module");
employee_tasks.Push(3, "Fix bugs reported by the testing team");

// View the current task


employee_tasks.Top();

// Mark a task as completed


employee_tasks.Pop();

// View the current task again


employee_tasks.Top();

// Complete all tasks


employee_tasks.Pop();
employee_tasks.Pop();

// Try to complete a task from an empty stack


employee_tasks.Pop();
38

return 0;
}

Question no 3:
In this scenario, you are required to create a course enrollment tracking system
using a stack with a doubly linked list. The system manages the courses a student
enrolls in. The system should allow:
- Adding a course to the stack when the student enrolls using the Push()
function.- Removing a course when the student withdraws using the Pop()
function.
- Viewing the most recently enrolled course using the Top() function. Implement
these operations with a doubly linked list.

Solution:

#include <iostream>
#include <string>
using namespace std;

// Node class representing a course in the stack


class Node {
public:
int course_id;
string course_name;
Node* prev;
Node* next;

// Constructor to initialize a new node (course)


Node(int id, string name) {
course_id = id;
course_name = name;
prev = NULL;
next = NULL;
}
};

// CourseStack class representing the doubly linked list stack for courses
class CourseStack {
private:
Node* top; // Pointer to the top of the stack (most recent course)

public:
// Constructor to initialize the stack
CourseStack() {
top = NULL;
}
39

// Function to push a new course onto the stack


void Push(int course_id, string course_name) {
Node* new_node = new Node(course_id, course_name);

if (top == NULL) {
// If the stack is empty, the new course becomes the top
top = new_node;
} else {
// Add the new course on top of the current top
new_node->prev = top;
top->next = new_node;
top = new_node;
}

cout << "Course '" << course_name << "' (ID: " << course_id << ") has been
enrolled." << endl;
}

// Function to pop the top course from the stack


void Pop() {
if (top == NULL) {
cout << "No courses to withdraw. The stack is empty." << endl;
return;
}

// Get the details of the course at the top


int course_id = top->course_id;
string course_name = top->course_name;

// Move the top pointer to the previous node


Node* temp = top;
if (top->prev != NULL) {
top = top->prev;
top->next = NULL;
} else {
// The stack becomes empty
top = NULL;
}

// Delete the old top node


delete temp;

cout << "Course '" << course_name << "' (ID: " << course_id << ") has been
withdrawn." << endl;
}

// Function to view the most recently enrolled course


void Top() {
if (top == NULL) {
40

cout << "No courses are currently enrolled." << endl;


return;
}

// Display the details of the course at the top of the stack


cout << "Most recently enrolled course: '" << top->course_name << "' (ID: " <<
top->course_id << ")" << endl;
}
};

int main() {
CourseStack student_courses;

// Enroll in some courses


student_courses.Push(101, "Introduction to Computer Science");
student_courses.Push(202, "Data Structures");
student_courses.Push(303, "Operating Systems");

// View the most recently enrolled course


student_courses.Top();

// Withdraw from the most recent course


student_courses.Pop();

// View the most recently enrolled course again


student_courses.Top();

// Withdraw from all courses


student_courses.Pop();
student_courses.Pop();

// Try to withdraw from an empty stack


student_courses.Pop();

return 0;
}

Question no 4:
You are tasked with implementing a shopping cart modification history system
using a stack with a doubly linked list. The stack keeps track of modifications
made to the shopping cart. The system should allow:
- Adding an item to the stack when a product is added to the cart using the
Push() function.
- Removing an item from the stack when it is removed from the cart using the
Pop() function.
- Viewing the last added item using the Top() function. Implement these
operations using a doubly linked list.
41

Solution:
#include <iostream>
#include <string>
using namespace std;

// Node class representing a product in the modification stack


class Node {
public:
int product_id;
string product_name;
Node* prev;
Node* next;

// Constructor to initialize a new node (product)


Node(int id, string name) {
product_id = id;
product_name = name;
prev = NULL;
next = NULL;
}
};

// CartModificationStack class representing the doubly linked list stack


class CartModificationStack {
private:
Node* top; // Pointer to the top of the stack (most recent modification)

public:
// Constructor to initialize the stack
CartModificationStack() {
top = NULL;
}

// Function to push a new product modification onto the stack


void Push(int product_id, string product_name) {
Node* new_node = new Node(product_id, product_name);

if (top == NULL) {
// If the stack is empty, the new product becomes the top
top = new_node;
} else {
// Add the new product on top of the current top
new_node->prev = top;
top->next = new_node;
top = new_node;
}

cout << "Product '" << product_name << "' (ID: " << product_id << ") has been
added to the cart." << endl;
}
42

// Function to pop the most recent product modification from the stack
void Pop() {
if (top == NULL) {
cout << "No products to remove. The cart is empty." << endl;
return;
}

// Get the details of the product at the top


int product_id = top->product_id;
string product_name = top->product_name;

// Move the top pointer to the previous node


Node* temp = top;
if (top->prev != NULL) {
top = top->prev;
top->next = NULL;
} else {
// The stack becomes empty
top = NULL;
}

// Delete the old top node


delete temp;

cout << "Product '" << product_name << "' (ID: " << product_id << ") has been
removed from the cart." << endl;
}

// Function to view the most recently added product in the cart


void Top() {
if (top == NULL) {
cout << "No products in the cart." << endl;
return;
}

// Display the details of the product at the top of the stack


cout << "Most recently added product: '" << top->product_name << "' (ID: " <<
top->product_id << ")" << endl;
}
};

int main() {
CartModificationStack cart;

// Add products to the cart


cart.Push(101, "Laptop");
cart.Push(202, "Smartphone");
cart.Push(303, "Headphones");
43

// View the most recently added product


cart.Top();

// Remove the most recent product


cart.Pop();

// View the most recently added product again


cart.Top();

// Remove all products


cart.Pop();
cart.Pop();

// Try to remove a product from an empty stack


cart.Pop();

return 0;

}Importance of Doubly Linked Lists

Doubly linked lists are a versatile data structure that offers several advantages over
singly linked lists:

 Efficient bidirectional traversal: You can traverse the list in both forward and backward
directions, making operations like finding the previous node or inserting at the beginning
more efficient.
 Flexible insertion and deletion: Inserting or deleting a node can be done in O(1) time,
regardless of the position, by adjusting the prev and next pointers of the surrounding
nodes.
 Implementation of advanced data structures: Doubly linked lists are the foundation for
other data structures like deques, doubly ended queues, and circular linked lists.

Time Complexity of Doubly Linked List Operations

 Insertion:

o At the beginning: O(1)


o At the end: O(1) (if you have a tail pointer) or O(n) (if you don't)
o In the middle: O(1)

 Deletion:
o At the beginning: O(1)
o At the end: O(1) (if you have a tail pointer) or O(n) (if you don't)
o In the middle: O(1)
 Search: O(n) - You need to traverse the list from the beginning or end to find the desired
element.
 Traversal: O(n) - You need to iterate through each node in the list.

When to Use Doubly Linked Lists

Doubly linked lists are particularly useful in scenarios where:


44

 Bidirectional traversal is necessary: For example, implementing undo/redo functionality or


creating a music player with previous/next track options.
 Frequent insertions and deletions at any position: Doubly linked lists offer efficient insertion
and deletion at any point, making them suitable for dynamic data structures.
 Implementing advanced data structures: They are the building block for more complex
structures like deques and circular linked lists.

While doubly linked lists offer flexibility and efficiency, they come with a slight
memory overhead due to the additional prev pointer in each node. However, in many
cases, the benefits outweigh the cost.

Week 6: circular linked list


Assignment (a)

Question no 1:
Write a C++ program that implements a Circular Linked List to simulate this
seating arrangement. Each seat should be represented as a node in the list. Your
list must support the following operations: 1. Insert an attendee at a seat after a
specified seat ID. 2. Remove an attendee from a seat with a given seat ID. 3.
Display the seat arrangement starting from any seat ID. 4. Search for a seat ID
in the arrangement.
Solution:
#include <iostream>
#include <string>
using namespace std;

// Define the structure for a seat node


struct Seat {
int seatID;
string attendeeName;
Seat* next; // Pointer to the next seat node
};

// Class to manage the circular linked list for seating arrangement


class CircularLinkedList {
private:
Seat* last; // Pointer to the last seat in the circular list

public:
CircularLinkedList() : last(nullptr) {}

// Function to check if the list is empty


bool isEmpty() const {
return last == nullptr;
45

// Function to insert an attendee at a seat after a specified seat ID


void insertAfter(int seatID, int newSeatID, const string& attendeeName) {
Seat* newSeat = new Seat();
newSeat->seatID = newSeatID;
newSeat->attendeeName = attendeeName;

if (isEmpty()) {
// If the list is empty, create the first seat
last = newSeat;
newSeat->next = newSeat;
} else {
Seat* temp = last->next;
do {
if (temp->seatID == seatID) {
newSeat->next = temp->next;
temp->next = newSeat;
if (temp == last) {
last = newSeat; // Update last if inserted after the last seat
}
cout << "Attendee " << attendeeName << " added at seat " << newSeatID
<< " after seat " << seatID << ".\n";
return;
}
temp = temp->next;
} while (temp != last->next);

cout << "Seat " << seatID << " not found.\n";
}
}

// Function to remove an attendee from a seat with a given seat ID


void removeSeat(int seatID) {
if (isEmpty()) {
cout << "No seats available to remove.\n";
return;
}

Seat* temp = last->next;


Seat* prev = last;

// Traverse the circular linked list to find the seat


do {
if (temp->seatID == seatID) {
if (temp == last && temp == last->next) {
// Only one node in the list
last = nullptr;
} else {
if (temp == last) {
46

last = prev; // Update last if removing the last seat


}
prev->next = temp->next;
}
delete temp;
cout << "Seat " << seatID << " removed.\n";
return;
}
prev = temp;
temp = temp->next;
} while (temp != last->next);

cout << "Seat " << seatID << " not found.\n";
}

// Function to display the seat arrangement starting from any seat ID


void displaySeats(int startSeatID) const {
if (isEmpty()) {
cout << "No seats available.\n";
return;
}

Seat* temp = last->next;


bool seatFound = false;

// Search for the seat with the given startSeatID


do {
if (temp->seatID == startSeatID) {
seatFound = true;
break;
}
temp = temp->next;
} while (temp != last->next);

if (!seatFound) {
cout << "Seat " << startSeatID << " not found.\n";
return;
}

// Display the seat arrangement starting from the found seat


do {
cout << "Seat ID: " << temp->seatID << ", Attendee: " << temp-
>attendeeName << "\n";
temp = temp->next;
} while (temp != last->next);
}

// Function to search for a seat by its seat ID


void searchSeat(int seatID) const {
if (isEmpty()) {
47

cout << "No seats available.\n";


return;
}

Seat* temp = last->next;


do {
if (temp->seatID == seatID) {
cout << "Seat ID: " << seatID << " found with attendee " << temp-
>attendeeName << ".\n";
return;
}
temp = temp->next;
} while (temp != last->next);

cout << "Seat ID " << seatID << " not found.\n";
}
};

int main() {
CircularLinkedList seatingArrangement;
int choice, seatID, newSeatID;
string attendeeName;

while (true) {
cout << "\nSeating Arrangement Menu\n";
cout << "1. Insert Attendee After Seat\n";
cout << "2. Remove Seat\n";
cout << "3. Display Seats Starting From a Seat\n";
cout << "4. Search for a Seat\n";
cout << "5. Exit\n";
cout << "Enter your choice: ";
cin >> choice;

switch (choice) {
case 1:
cout << "Enter seat ID to insert after: ";
cin >> seatID;
cout << "Enter new seat ID: ";
cin >> newSeatID;
cin.ignore(); // To ignore newline character from previous input
cout << "Enter attendee name: ";
getline(cin, attendeeName);
seatingArrangement.insertAfter(seatID, newSeatID, attendeeName);
break;
case 2:
cout << "Enter seat ID to remove: ";
cin >> seatID;
seatingArrangement.removeSeat(seatID);
break;
case 3:
48

cout << "Enter the seat ID to start displaying from: ";


cin >> seatID;
seatingArrangement.displaySeats(seatID);
break;
case 4:
cout << "Enter seat ID to search for: ";
cin >> seatID;
seatingArrangement.searchSeat(seatID);
break;
case 5:
cout << "Exiting...\n";
return 0;
default:
cout << "Invalid choice. Please try again.\n";
}
}

return 0;
}

Assignment (b)
Question 1:
You are developing a financial calculator that helps investment analysts quickly
evaluate complex arithmetic expressions. Analysts prefer using prefix
expressions to save time, as they do not need to worry about parentheses or
operator precedence. Task: Implement a feature where analysts can input a
prefix expression (e.g., '+ * 5 6 3'), and your program will evaluate it using a
stack-based approach to provide the result instantly. The system should validate
the expression and display an error message if it's invalid.
Solution:

#include <iostream>
#include <stack>
#include <sstream>
#include <string>
#include <stdexcept>
#include <cctype>

using namespace std;

// Function to check if a character is an operator


bool isOperator(char c) {
return (c == '+' || c == '-' || c == '*' || c == '/');
}

// Function to perform the arithmetic operation


int performOperation(char op, int operand1, int operand2) {
switch(op) {
case '+': return operand1 + operand2;
case '-': return operand1 - operand2;
49

case '*': return operand1 * operand2;


case '/':
if (operand2 == 0) throw runtime_error("Division by zero error");
return operand1 / operand2;
default: throw runtime_error("Invalid operator");
}
}

// Function to evaluate the prefix expression


int evaluatePrefix(const string& expression) {
stack<int> operands;
istringstream iss(expression);
string token;

// Split the expression into tokens from right to left


string exprReversed = expression;
reverse(exprReversed.begin(), exprReversed.end());

for (char& ch : exprReversed) {


if (ch == ' ') continue;
token = ch;

// If token is an operator, pop two operands, apply operator, and push result back
if (isOperator(token[0])) {
if (operands.size() < 2) throw runtime_error("Invalid expression");
int operand1 = operands.top(); operands.pop();
int operand2 = operands.top(); operands.pop();
int result = performOperation(token[0], operand1, operand2);
operands.push(result);
}
// If token is a number, push it onto the stack
else if (isdigit(token[0])) {
operands.push(token[0] - '0'); // Convert char to int
}
else {
throw runtime_error("Invalid character in expression");
}
}

// After processing, there should be exactly one operand in the stack


if (operands.size() != 1) throw runtime_error("Invalid expression");
return operands.top();
}

int main() {
string expression;
cout << "Enter a prefix expression: ";
getline(cin, expression);

try {
50

int result = evaluatePrefix(expression);


cout << "Result: " << result << endl;
} catch (const exception& e) {
cout << "Error: " << e.what() << endl;
}

return 0;
}
Question 2:
You are developing a text analysis tool for a language learning application.
Oneof thefeaturesstudents find helpful isidentifying palindromes, which helps
them understand symmetry in words and phrases.
Task: Create a feature that allows students to input a string, and the system
checks whether the string is a palindrome. The program should ignore case
sensitivity
and spaces for accurate detection. Example:
- Input: 'A man a plan a canal Panama'
- Output: 'The string is a palindrome.’
Solution:

#include <iostream>
#include <string>
#include <algorithm>
#include <cctype>

using namespace std;

// Function to check if a given string is a palindrome


bool isPalindrome(string str) {
// Remove spaces and convert to lowercase
string filteredStr;
for (char c : str) {
if (isalnum(c)) {
filteredStr += tolower(c); // Convert to lowercase and keep alphanumeric
characters only
}
}

// Check if the filtered string is a palindrome


int left = 0;
int right = filteredStr.length() - 1;

while (left < right) {


if (filteredStr[left] != filteredStr[right]) {
return false;
}
left++;
right--;
}
51

return true;
}

int main() {
string input;
cout << "Enter a string: ";
getline(cin, input);

if (isPalindrome(input)) {
cout << "The string is a palindrome." << endl;
} else {
cout << "The string is not a palindrome." << endl;
}

return 0;
}
Question 3:
You are building a scientific calculator for engineers who often work with postfix
expressionsduetotheir simplicity in evaluating complex equations. This
calculator should efficiently process postfix expressionswithoutthe need for
parentheses. Task: Implement functionality where users can input a postfix
expression (e.g., '3 45* +'), andyourprogram will evaluate it using a stack-based
approach, providing the final result. It should also handle errorsgracefullyifthe
expression is invalid. Example: - Input: '3 4 5 * +' - Calculation: '3 + (4 * 5) = 23'
- Output: 'Result: 23'*/
Solution:
#include <iostream>
#include <stack>
#include <sstream>
#include <string>
#include <stdexcept>
using namespace std;
// Function to check if a character is an operator
bool isOperator(char c) {
return (c == '+' || c == '-' || c == '*' || c == '/');
}
// Function to perform the arithmetic operation
int performOperation(char op, int operand1, int operand2) {
switch(op) {
case '+': return operand1 + operand2;
case '-': return operand1 - operand2;
case '*': return operand1 * operand2;
case '/':
if (operand2 == 0) throw runtime_error("Division by zero error");
return operand1 / operand2;
default: throw runtime_error("Invalid operator");
}
}

// Function to evaluate the postfix expression


52

int evaluatePostfix(const string& expression) {


stack<int> operands;
istringstream iss(expression);
string token;

// Split the expression into tokens


while (iss >> token) {
// If token is a number, push it onto the stack
if (isdigit(token[0])) {
operands.push(stoi(token));
}
// If token is an operator, pop two operands, apply operator, and push result back
else if (isOperator(token[0]) && token.length() == 1) {
if (operands.size() < 2) throw runtime_error("Invalid expression");
int operand2 = operands.top(); operands.pop();
int operand1 = operands.top(); operands.pop();
int result = performOperation(token[0], operand1, operand2);
operands.push(result);
}
else {
throw runtime_error("Invalid token in expression");
}
}

// After processing, there should be exactly one operand in the stack


if (operands.size() != 1) throw runtime_error("Invalid expression");
return operands.top();
}

int main() {
string expression;
cout << "Enter a postfix expression: ";
getline(cin, expression);

try {
int result = evaluatePostfix(expression);
cout << "Result: " << result << endl;
} catch (const exception& e) {
cout << "Error: " << e.what() << endl;
}

return 0;
}
Question 4:
You are developing a code editor with syntax highlighting for beginner programmers.
Oneofthemost common mistakes new programmers make is mismatched or
unbalanced parentheses, leading to compilationerrors. Task: Implement a feature that
checks whether the parentheses in a given expressionarebalanced.This feature will
help users detect and correct errors in their code quickly. Example: - Input: '( (a + b) *
(c - d) )' - Output: 'The expression has balanced parentheses.'*/
53

Solution:
#include <iostream>
#include <stack>
#include <string>

using namespace std;

// Function to check if the given parentheses are balanced


bool isBalanced(const string& expression) {
stack<char> s;

// Traverse through each character in the expression


for (char ch : expression) {
// If it's an opening parenthesis, push it onto the stack
if (ch == '(') {
s.push(ch);
}
// If it's a closing parenthesis, check the stack
else if (ch == ')') {
// If there's no corresponding opening parenthesis, return false
if (s.empty() || s.top() != '(') {
return false;
}
// Pop the matched opening parenthesis
s.pop();
}
}

// If the stack is empty, all parentheses are balanced


return s.empty();
}

int main() {
string expression;
cout << "Enter an expression: ";
getline(cin, expression);

if (isBalanced(expression)) {
cout << "The expression has balanced parentheses." << endl;
} else {
cout << "The expression does not have balanced parentheses." << endl;
}

return 0;

}Time Complexity:

The time complexity of operations in a circular linked list depends on the specific
operation and whether we have a reference to a specific node (e.g., the head or tail) or
not. Here's a breakdown:
54

 Insertion:
o At the beginning: O(1) - If you have a reference to the last node.
o At the end: O(n) (worst case) - If you don't have a reference to the last node and
need to traverse the list to find it.
o In the middle: O(n) (average case) - You need to traverse to find the insertion point.
 Deletion:

o Similar to insertion, the complexity depends on having a reference to the node to be


deleted. It can be O(1) in the best case and O(n) in the worst case.

 Search: O(n) (worst case) - You need to traverse the list to find the desired element.
 Traversal: O(n) - You need to iterate through each node in the list, following the circular
structure.

Overall, circular linked lists offer efficient memory usage and continuous
traversal capabilities. However, some operations like searching and insertion in
the middle can be slower due to the need for traversal.

Importance:

Circular linked lists offer several advantages over regular singly linked lists:

 Efficient memory usage: Unlike singly linked lists that require a separate pointer for the last
node, circular linked lists don't need it. This can save memory, especially for large datasets.
 Continuous traversal: Since the last node points back to the first, you can efficiently traverse
the list in both directions (forward and backward) without needing to keep track of the
starting or ending node.
 Implementation of advanced data structures: Circular linked lists are the foundation for
data structures like deques (double-ended queues) and can be used to implement advanced
algorithms like Josephus Problem.

Week 7:Queue

Question no 1:
Problem #1: An airport's air traffic control uses a Max Priority Queue to
prioritize landing planes based on urgency:
Priority Levels:
9: Emergency (Highest Priority) .
6: Low Fuel .
4: Regular Flight Operations:
Add Plane: Insert a plane into the queue with its urgency level.
Land Plane: Clear the plane with the highest priority to land.
Check Next: View the next plane to land without removing it.
IsEmpty: Check if any planes are waiting.

Solution:
55

#include <iostream>
#include <queue>
#include <string>
Using namespace std;
class Plane {
public:
string id;
int urgency;
Plane(const string& id, int urgency) : id(id), urgency(urgency) {}
// Comparator to prioritize by urgency
bool operator<(const Plane& other) const {
return urgency < other.urgency;
}
};
class MaxPriorityQueue {
private:
priority_queue<Plane> pq;
public:
void addPlane(const string& plane_id, int urgency) {
pq.emplace(plane_id, urgency);
}

string landPlane() {
if (!isEmpty()) {
Plane topPlane = pq.top();
pq.pop();
return topPlane.id;
}
return "No planes waiting to land.";
}

string checkNext() const {


if (!isEmpty()) {
return pq.top().id;
}
return "No planes waiting to land.";
}
bool isEmpty() const {
return pq.empty();
}
};
int main() {
MaxPriorityQueue atcQueue;
// Adding planes with different urgency levels
atcQueue.addPlane("Plane 1", 9); // Emergency
atcQueue.addPlane("Plane 2", 6); // Low Fuel
atcQueue.addPlane("Plane 3", 4); // Regular Flight
// Landing planes in order of priority
cout << "Next plane to land: " << atcQueue.checkNext() << endl; // Should be
"Plane 1"
56

cout << "Landing plane: " << atcQueue.landPlane() << endl; // Plane 1 lands
cout << "Next plane to land: " << atcQueue.checkNext() << endl; // Should be
"Plane 2"
cout << "Landing plane: " << atcQueue.landPlane() << endl; // Plane 2 lands
cout << "Next plane to land: " << atcQueue.checkNext() << endl; // Should be
"Plane 3"
cout << "Landing plane: " << atcQueue.landPlane() << endl; // Plane 3 lands
cout << "Is queue empty? " << (atcQueue.isEmpty() ? "Yes" : "No") << endl; //
Yes
return 0;
}

QUESTION 2:
An online food delivery service uses a Deque (Double-Ended Queue) to manage
customer orders efficiently.
Operations:
. Add Urgent Order to Front: Urgent requests are added to the front.
. Add Regular Order to Back: Regular orders are added to the back.
. Process Urgent Order from Front: The system processes the front order first.
. Process Regular Order from Back: The back order is processed when no urgent
orders are pending.
. Check Next Order: View the next order from either end without removing it.
. IsEmpty: Check if there are any orders left in the queue.
Example:DSA LAB QUEUE Khadija Ilyas
1. Order A (Urgent) is added to the front.
2. Order B (Regular) is added to the back.
3. Order C (Urgent) is added to the front.
Processing Order:
. The system processes Order C first, followed by Order A, and finally Order B.

Solution:
#include <iostream>
#include <deque>
#include <string>
Using namespace std;
class OrderQueue {
private:
deque<string> orderQueue;
public:
// Add urgent order to the front
void addUrgentOrder(const string& order) {
orderQueue.push_front(order);
cout << "Added urgent order: " << order << " to the front.\n";
}
// Add regular order to the back
void addRegularOrder(const string& order) {
orderQueue.push_back(order);
cout << "Added regular order: " << order << " to the back.\n";
}
57

// Process order from the front (urgent order)


void processUrgentOrder() {
if (!isEmpty()) {
string order = orderQueue.front();
orderQueue.pop_front();
cout << "Processing urgent order from front: " << order << endl;
} else {
cout << "No orders to process.\n";
}
}
// Process order from the back (regular order)
void processRegularOrder() {
if (!isEmpty()) {
string order = orderQueue.back();
orderQueue.pop_back();
cout << "Processing regular order from back: " << order << endl;
} else {
cout << "No orders to process.\n";
}
}
// Check the next order without removing it
void checkNextOrder() const {
if (!isEmpty()) {
cout << "Next order to process: " << orderQueue.front() << endl;
} else {
cout << "No orders in the queue.\n";
}
}
// Check if the queue is empty
bool isEmpty() const {
return orderQueue.empty();
}
};
int main() {
OrderQueue orderQueue;
// Adding orders as per the example
orderQueue.addUrgentOrder("Order A"); // Urgent
orderQueue.addRegularOrder("Order B"); // Regular
orderQueue.addUrgentOrder("Order C"); // Urgent
// Processing orders in the correct order
cout << "Processing orders:\n";
orderQueue.processUrgentOrder(); // Should process "Order C"
orderQueue.processUrgentOrder(); // Should process "Order A"
orderQueue.processRegularOrder(); // Should process "Order B”
// Check if queue is empty
cout << "Is queue empty? " << (orderQueue.isEmpty() ? "Yes" : "No") << endl;

return 0;
}
Output:
58

Added urgent order: Order A to the front.


Added regular order: Order B to the back.
Added urgent order: Order C to the front.
Processing orders:
Processing urgent order from front: Order C
Processing urgent order from front: Order A
Processing regular order from back: Order B
Is queue empty? Yes

Question no 3:
A project management tool uses a Min Priority Queue to manage tasks based on
urgency:
Priority Levels:
. 1: High Urgency (Highest Priority)
. 3: Medium Urgency
. 5: Low Urgency (Lowest Priority)
Operations:
. Add Task: Insert a task with a priority.
. Execute Task: Process the task with the lowest priority first.
. Check Next: View the next task without removing it.
. IsEmpty: Check if there are tasks left.
Example:
. Task A (Priority: 1): Executed first.
. Task B (Priority: 3): Executed next.
. Task C (Priority: 5): Executed last.

Solution:
#include <iostream>
#include <queue>
#include <string>
#include <vector>
Using namespace std;
class Task {
public:
string name;
int priority;
Task(const string& name, int priority) : name(name), priority(priority) {}
// Comparator to prioritize by urgency, with lower values having higher priority
bool operator>(const Task& other) const {
return priority > other.priority;
}
};
class MinPriorityQueue {
private:
// Min-heap priority queue using a greater-than comparator
priority_queue<Task, vector<Task>, greater<Task>> pq;
public:
// Add a task with a specific priority
void addTask(const string& task_name, int priority) {
pq.emplace(task_name, priority);
59

cout << "Added task: " << task_name << " with priority " << priority << ".\n";
}
// Execute and remove the task with the highest priority (lowest priority value)
void executeTask() {
if (!isEmpty()) {
Task topTask = pq.top();
pq.pop();
cout << "Executing task: " << topTask.name << " with priority " <<
topTask.priority << ".\n";
} else {
cout << "No tasks to execute.\n";
}
}
// Check the next task without removing it
void checkNext() const {
if (!isEmpty()) {
Task topTask = pq.top();
cout << "Next task to execute: " << topTask.name << " with priority " <<
topTask.priority << ".\n";
} else {
cout << "No tasks in the queue.\n";
}
}
// Check if the queue is empty
bool isEmpty() const {
return pq.empty();
}
};
int main() {
MinPriorityQueue taskQueue;
// Adding tasks as per the example
taskQueue.addTask("Task A", 1); // High Urgency
taskQueue.addTask("Task B", 3); // Medium Urgency
taskQueue.addTask("Task C", 5); // Low Urgency
// Processing tasks in order of priority
cout << "\nExecuting tasks:\n";
taskQueue.executeTask(); // Should execute "Task A"
taskQueue.executeTask(); // Should execute "Task B"
taskQueue.executeTask(); // Should execute "Task C"

// Check if queue is empty


cout << "Is queue empty? " << (taskQueue.isEmpty() ? "Yes" : "No") << endl;

return 0;
}
Output:
Added task: Task A with priority 1.
Added task: Task B with priority 3.
Added task: Task C with priority 5.
60

Executing tasks:
Executing task: Task A with priority 1.
Executing task: Task B with priority 3.
Executing task: Task C with priority 5.
Is queue empty? Yes

Queues (FIFO - First-In-First-Out)

Importance:

 Efficient task ordering: Queues are ideal for scenarios where elements need to be processed
in the order they were received. This makes them valuable in various applications like task
scheduling, printer spooling, network packet buffering, and data streams.
 Simplicity and ease of implementation: The FIFO concept is intuitive and can be
implemented efficiently using data structures like arrays or linked lists.

Time complexity is a measure of how the runtime of an algorithm increases with the
input size. It's expressed using Big O notation:

 O(1): Constant time (e.g., accessing an array element)


 O(log n): Logarithmic time (e.g., binary search)
 O(n): Linear time (e.g., linear search)
 O(n log n): Linearithmic time (e.g., merge sort)
 O(n^2): Quadratic time (e.g., bubble sort)
 O(2^n): Exponential time (e.g., brute-force algorithms)

Lower time complexity is generally better, as it indicates faster execution times for
larger inputs.

Week 8: infix postfix conversion

Question 1:
You are developing a basic calculator that needs to handle arithmetic
expressions. Your task is to write a C++ program that takes an postfix
expression (e.g., ((2 + 5) * 8) - 6) as input and converts it into a postfix expression
(e.g., 2 5 + 8 * 6 -). Implement the algorithm for infix to postfix conversion using
a stack data structure.
Example: 
Input: ((2 + 5) * 8) – 6
Output: 2 5 + 8 * 6 –

Solution:
#include <iostream>
#include <stack>
#include <string>
#include <cctype>
61

#include <sstream>
#include <map>
using namespace std;
int precedence(char op) {
if (op == '+' || op == '-') return 1;
if (op == '*' || op == '/') return 2;
return 0;
}
bool isOperator(char c) {
return c == '+' || c == '-' || c == '*' || c == '/';
}
string infixToPostfix(const string& infix) {
stack<char> operatorStack;
string postfix;
istringstream iss(infix);
string token;
while (iss >> token) {
if (isdigit(token[0])) {
postfix += token + ' ';
} else if (token[0] == '(') {
operatorStack.push('(');
} else if (token[0] == ')') {
while (!operatorStack.empty() && operatorStack.top() != '(') {
postfix += operatorStack.top();
postfix += ' ';
operatorStack.pop();
}
operatorStack.pop(); // Pop the '(' from the stack
} else if (isOperator(token[0])) {
// If the token is an operator
while (!operatorStack.empty() && precedence(operatorStack.top()) >=
precedence(token[0])) {
postfix += operatorStack.top();
postfix += ' ';
operatorStack.pop();
}
operatorStack.push(token[0]);
}
}
while (!operatorStack.empty()) {
postfix += operatorStack.top();
postfix += ' ';
operatorStack.pop();
}

return postfix;
}
int main() {
string infix;
// Get infix expression input
62

cout << "Enter an infix expression: ";


getline(cin, infix);
// Convert to postfix and output the result
string postfix = infixToPostfix(infix);
cout << "Postfix expression: " << postfix << endl;
return 0;
}

Question 2:
After converting the infix expression to postfix, you now need to
evaluate the postfix expression to get the final result. Extend your C++ program
to take the postfix expression (e.g., 2 5 + 8 * 6 -) and evaluate it using a stack.
Example: 
Input (Postfix): 2 5 + 8 * 6 -
Output (Result): 50

Solution:
#include <iostream>
#include <stack>
#include <sstream>
#include <string>
#include <cctype>
using namespace std;
int evaluatePostfix(const string& expression) {
stack<int> stack;
istringstream iss(expression);
string token;
while (iss >> token) {
if (isdigit(token[0])) {
stack.push(stoi(token));
} else {
int right = stack.top(); stack.pop(); // Pop the first operand
int left = stack.top(); stack.pop(); // Pop the second operand

switch (token[0]) {
case '+':
stack.push(left + right);
break;
case '-':
stack.push(left - right);
break;
case '*':
stack.push(left * right);
break;
case '/':
stack.push(left / right);
break;
default:
throw invalid_argument("Invalid operator");
}
63

}
}
return stack.top();
}
int main() {
string postfixExpression;
cout << "Enter a postfix expression (e.g., 2 5 + 8 * 6 -): ";
getline(std::cin, postfixExpression);
try {
int result = evaluatePostfix(postfixExpression);
cout << "Result: " << result << endl;
} catch (const exception& e) {
cerr << "Error: " << e.what() << endl;
}
return 0;
}

Importance of Infix to Postfix Conversion


Why Convert Infix to Postfix?

 Efficient Evaluation: Postfix expressions can be evaluated directly using a stack, eliminating
the need for complex parsing and operator precedence rules. This simplifies the evaluation
process and improves efficiency.
 Simplified Parsing: Postfix expressions are easier for computers to parse and process, as they
don't require parentheses to indicate operator precedence.
 Compiler Design: Many compilers use postfix notation as an intermediate step in the
compilation process, making it a fundamental concept in compiler design.

Time Complexity of Infix to Postfix


Conversion
The time complexity of the infix to postfix conversion algorithm is O(n), where n is
the length of the infix expression. This is because each character in the infix
expression is processed 1 once, and the operations within the loop (stack operations
and string concatenation) take constant time.

1. github.com
MIT

github.com

Key Points:

 The use of a stack to handle operator precedence and parentheses is crucial for efficient
conversion.
 The algorithm iterates through the infix expression once, making the time complexity linear.
64

 The space complexity is also O(n) due to the stack, which can hold up to n operators in the
worst case.

By understanding the importance and time complexity of infix to postfix conversion,


you can effectively apply this technique in various programming tasks, including
compiler design, calculator implementations, and more.

Week 9:Binary search tree

Question 1:
A school is developing a system to store students’ grades for easy management
and retrieval. Each student has a unique Roll Number and an Average Grade.
Theschool’s system needs to support the following:
1. Add New Student Records: As students’ grades are recorded, each student’s
information, including Roll Number and Grade, needs to be added to the system.
2. Search for a Student by Roll Number: The system should enable teachers to
search for a student’s record by their Roll Number to view grades quickly.
3. Display All Student Grades in Order of Roll Number: The school requires a
sorted list of all students based on Roll Number for easy grade evaluation and
comparison. Implement a Binary Search Tree (BST) where each node represents
a student’s grade record. Use insertion to add new student records based on Roll
Number, and use preorder, inorder, and postorder traversals to analyze the
records in various ways. This structure allows the school to store, retrieve, and
organize student grades efficiently.

Solution:
#include <iostream>
using namespace std;

// Define structure for student record


struct Student {
int rollNumber;
float averageGrade;
};

// Define Node structure for Binary Search Tree


struct Node {
Student student;
Node* left;
Node* right;
};

// Function to create a new Node


Node* createNode(Student student) {
Node* newNode = new Node();
65

newNode->student = student;
newNode->left = newNode->right = NULL;
return newNode;
}

// Function to insert a new student record into the BST


Node* insertNode(Node* root, Student student) {
// If tree is empty, create a new node
if (root == NULL) {
return createNode(student);
}

// Insert data in the left subtree


if (student.rollNumber < root->student.rollNumber) {
root->left = insertNode(root->left, student);
}
// Insert data in the right subtree
else if (student.rollNumber > root->student.rollNumber) {
root->right = insertNode(root->right, student);
}

// If rollNumber already exists, update averageGrade


else {
root->student.averageGrade = student.averageGrade;
}

return root;
}

// Function for preorder traversal


void preorderTraversal(Node* root) {
if (root != NULL) {
cout << "Roll Number: " << root->student.rollNumber << ", Average Grade: "
<< root->student.averageGrade << endl;
preorderTraversal(root->left);
preorderTraversal(root->right);
}
}

// Function for inorder traversal


void inorderTraversal(Node* root) {
if (root != NULL) {
inorderTraversal(root->left);
cout << "Roll Number: " << root->student.rollNumber << ", Average Grade: "
<< root->student.averageGrade << endl;
inorderTraversal(root->right);
}
}

// Function for postorder traversal


66

void postorderTraversal(Node* root) {


if (root != NULL) {
postorderTraversal(root->left);
postorderTraversal(root->right);
cout << "Roll Number: " << root->student.rollNumber << ", Average Grade: "
<< root->student.averageGrade << endl;
}
}

// Function to search for a student by roll number


Node* searchStudent(Node* root, int rollNumber) {
if (root == NULL || root->student.rollNumber == rollNumber) {
return root;
}

if (rollNumber < root->student.rollNumber) {


return searchStudent(root->left, rollNumber);
}
else {
return searchStudent(root->right, rollNumber);
}
}

int main() {
Node* root = NULL;

// Add student records


Student student1 = {1, 85.5};
Student student2 = {5, 90.0};
Student student3 = {3, 78.2};
Student student4 = {2, 92.1};
Student student5 = {4, 88.5};

root = insertNode(root, student1);


root = insertNode(root, student2);
root = insertNode(root, student3);
root = insertNode(root, student4);
root = insertNode(root, student5);

// Display student records using inorder traversal (sorted by roll number)


cout << "Student Records (Inorder Traversal):" << endl;
inorderTraversal(root);

// Search for a student by roll number


int rollNumberToSearch = 3;
Node* searchedStudent = searchStudent(root, rollNumberToSearch);

if (searchedStudent != NULL) {
67

cout << "\nStudent Found: Roll Number = " << searchedStudent-


>student.rollNumber << ", Average Grade = " << searchedStudent-
>student.averageGrade << endl;
}
else {
cout << "\nStudent Not Found!" << endl;
}

// Display student records using preorder traversal


cout << "\nStudent Records (Preorder Traversal):" << endl;
preorderTraversal(root);

// Display student records using postorder traversal


cout << "\nStudent Records (Postorder Traversal):" << endl;
postorderTraversal(root);

return 0;
}

Time Complexity of BST Operations:

Average Worst
Operation
Case Case
Search O(log n) O(n)
Insertion O(log n) O(n)
Deletion O(log n) O(n)
In-order Traversal O(n) O(n)
Pre-order Traversal O(n) O(n)
Post-order
O(n) O(n)
Traversal
Export to Sheets

Importance of BSTs:

 Efficient Searching: BSTs allow for efficient searching of elements based on their keys,
making them suitable for applications like dictionaries and databases.
 Sorting and Ordering: In-order traversal of a BST yields elements in sorted order, making it
useful for sorting and ranking tasks.
 Flexible Operations: BSTs support various operations like insertion, deletion, and searching,
making them versatile for different data management needs.
 Balanced BSTs: Self-balancing BSTs like AVL trees and Red-Black trees maintain a balanced
structure, ensuring O(log n) time complexity for all operations in the average and worst
cases.
 Real-world Applications: BSTs are used in various applications, including:
o Database systems
o File systems
68

o Network routing protocols


o Compiler symbol tables
o Game development (e.g., collision detection)
o Financial applications (e.g., stock market analysis)

Week 10: AVL TREE

Question 1:
Suppose you are managing a database of employee records in an AVL tree,
where each employee's record is stored based on their Employee ID. You must
keep the tree balanced to ensure efficient searching and insertion
1. Insert employee IDs in the following order: 20, 10, 15.
2. After inserting each ID, check if the AVL tree remains balanced by calculating
the balancing factor of each node.
3. When you find an imbalance,apply the necessary rotation to restore balance.

Solution:
#include <iostream>
#include <algorithm>
using namespace std;
// Node structure for AVL Tree
struct Node {
int employeeID;
Node* left;
Node* right;
int height;
Node(int id) : employeeID(id), left(NULL), right(NULL), height(1) {}
};

// Class for AVL Tree


class AVLTree {
private:
Node* root;

// Function to get the height of the node


int getHeight(Node* node) {
return node ? node->height : 0;
}

// Function to get the balance factor of the node


int getBalance(Node* node) {
return node ? getHeight(node->left) - getHeight(node->right) : 0;
}
69

// Right rotate the subtree rooted with y


Node* rightRotate(Node* y) {
Node* x = y->left;
Node* T2 = x->right;

// Perform rotation
x->right = y;
y->left = T2;

// Update heights
y->height = max(getHeight(y->left), getHeight(y->right)) + 1;
x->height = max(getHeight(x->left), getHeight(x->right)) + 1;

// Return new root


return x;
}

// Left rotate the subtree rooted with x


Node* leftRotate(Node* x) {
Node* y = x->right;
Node* T2 = y->left;

// Perform rotation
y->left = x;
x->right = T2;

// Update heights
x->height = max(getHeight(x->left), getHeight(x->right)) + 1;
y->height = max(getHeight(y->left), getHeight(y->right)) + 1;

// Return new root


return y;
}

// Recursive function to insert a new employee ID


Node* insert(Node* node, int employeeID) {
// Perform the normal BST insertion
if (!node) return new Node(employeeID);
if (employeeID < node->employeeID)
node->left = insert(node->left, employeeID);
else if (employeeID > node->employeeID)
node->right = insert(node->right, employeeID);
else // Duplicate keys are not allowed
return node;

// Update the height of this ancestor node


node->height = 1 + max(getHeight(node->left), getHeight(node->right));

// Get the balance factor


int balance = getBalance(node);
70

// If the node becomes unbalanced, then there are 4 cases

// Left Left Case


if (balance > 1 && employeeID < node->left->employeeID)
return rightRotate(node);

// Right Right Case


if (balance < -1 && employeeID > node->right->employeeID)
return leftRotate(node);

// Left Right Case


if (balance > 1 && employeeID > node->left->employeeID) {
node->left = leftRotate(node->left);
return rightRotate(node);
}

// Right Left Case


if (balance < -1 && employeeID < node->right->employeeID) {
node->right = rightRotate(node->right);
return leftRotate(node);
}

// Return the (unchanged) node pointer


return node;
}

// Function to print the tree (in-order)


void inOrder(Node* node) {
if (node) {
inOrder(node->left);
cout << node->employeeID << " ";
inOrder(node->right);
}
}

public:
AVLTree() : root(NULL) {}

// Public method to insert a new employee ID


void insert(int employeeID) {
root = insert(root, employeeID);
cout << "Inserted " << employeeID << ", In-order traversal: ";
inOrder(root);
cout << endl;
}
};

int main() {
AVLTree tree;
71

// Insert employee IDs in the specified order


tree.insert(20);
tree.insert(10);
tree.insert(15);

return 0;
}

Question 2:
Consider a scheduling system where you are adding event times (in minutes past
midnight) into an AVL tree. Each time entry is unique, and the AVL tree helps
in
quickly finding available time slots
. 1. Insert the event times in this order: 30, 40, 35.
2. After inserting each time, calculate the balancing factor for each node to
check for imbalance.
3. Once an imbalance is identified, use the appropriate rotation to balance the
tree. Hint: Double Right-Left Rotation.

Solution:
#include <iostream>
#include <algorithm>

using namespace std;

// Node structure for AVL Tree


struct Node {
int key; // Event time in minutes past midnight
Node* left;
Node* right;
int height;

Node(int k) : key(k), left(NULL), right(NULL), height(1) {}


};

// Function to get the height of the node


int height(Node* node) {
return node ? node->height : 0;
}

// Function to get the balance factor of the node


int getBalance(Node* node) {
return node ? height(node->left) - height(node->right) : 0;
}

// Right rotate the subtree rooted with y


72

Node* rightRotate(Node* y) {
Node* x = y->left;
Node* T2 = x->right;

// Perform rotation
x->right = y;
y->left = T2;

// Update heights
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;

// Return new root


return x;
}

// Left rotate the subtree rooted with x


Node* leftRotate(Node* x) {
Node* y = x->right;
Node* T2 = y->left;

// Perform rotation
y->left = x;
x->right = T2;

// Update heights
x->height = max(height(x->left), height(x->right)) + 1;
y->height = max(height(y->left), height(y->right)) + 1;

// Return new root


return y;
}

// Function to insert a key in the AVL tree


Node* insert(Node* node, int key) {
// Perform the normal BST insert
if (!node) return new Node(key);
if (key < node->key)
node->left = insert(node->left, key);
else if (key > node->key)
node->right = insert(node->right, key);
else // Duplicate keys are not allowed
return node;

// Update height of this ancestor node


node->height = 1 + max(height(node->left), height(node->right));

// Get the balance factor


int balance = getBalance(node);
73

// If the node becomes unbalanced, then there are 4 cases

// Left Left Case


if (balance > 1 && key < node->left->key)
return rightRotate(node);

// Right Right Case


if (balance < -1 && key > node->right->key)
return leftRotate(node);

// Left Right Case


if (balance > 1 && key > node->left->key) {
node->left = leftRotate(node->left);
return rightRotate(node);
}

// Right Left Case


if (balance < -1 && key < node->right->key) {
node->right = rightRotate(node->right);
return leftRotate(node);
}

// Return the (unchanged) node pointer


return node;
}

// Function to print the balance factor of each node


void printBalanceFactors(Node* node) {
if (node) {
printBalanceFactors(node->left);
cout << "Node: " << node->key << ", Balance Factor: " << getBalance(node) <<
endl;
printBalanceFactors(node->right);
}
}

// Main function to demonstrate the AVL tree insertion and balance factor calculation
int main() {
Node* root = NULL;
// Insert 30
root = insert(root, 30);
cout << "After inserting 30:\n";
printBalanceFactors(root);
cout << endl;
// Insert 40
root = insert(root, 40);
cout << "After inserting 40:\n";
printBalanceFactors(root);
cout << endl;
// Insert 35
74

root = insert(root, 35);


cout << "After inserting 35:\n";
printBalanceFactors(root);
cout << endl;
return 0;
}

Time Complexity of AVL Tree Operations:

The time complexity of operations in a balanced AVL tree is O(log n), where n is the
number of nodes in the tree. This is due to the self-balancing property of AVL trees,
which ensures that the tree remains balanced after insertions and deletions.

Importance of AVL Trees:

 Efficient Search: AVL trees provide efficient searching, insertion, and deletion operations
with a logarithmic time complexity.
 Self-Balancing: AVL trees maintain their balance through rotations, ensuring that the tree
remains balanced and efficient.
 Real-world Applications: AVL trees are used in various applications, including:
o Database systems for indexing and searching

o Network routing protocols


o Symbol tables in compilers
o Priority queues
o Set and map implementations

Week 11: Balancing and rotations in AVL tree

Scenario No 1: Arrays
Scenario: Managing a calendar of fixed appointments for a week.
Code:
#include <iostream>
using namespace std;

int main() {
// Array to store appointments for a week
string appointments[7] = {"Dentist", "Meeting", "Gym", "Rest", "Shopping",
"Party", "Study"};

// Accessing and modifying appointments


cout << "Appointment on Day 3: " << appointments[2] << endl;
appointments[4] = "Relax"; // Update Friday's appointment
cout << "Updated Appointment on Day 5: " << appointments[4] << endl;
75

return 0;
}
Why Use Array:
Fixed Size: The calendar has exactly 7 days, so the number of elements is known and
fixed.
Random Access: Direct indexing allows quick retrieval and modification of any day's
appointment.
Memory Efficiency: Arrays store data in contiguous memory without additional
overhead.
Why Not Use Other Structures:
Linked List: Overhead of pointers for each element adds unnecessary complexity for
fixed data.
Stack: LIFO operations are not relevant for random access to days.
Queue: FIFO behavior doesn't align with the need to access any day directly.
Time Complexity:

 Access: O(1)
 Search: O(n)
 Insertion/Deletion: O(n) (average)

· Importance:

 Simple and efficient for storing and accessing elements by index.


 Widely used in various algorithms and data structures.

Scenario 2: Linked List


Scenario: Managing a playlist where songs can be added or removed dynamically.
Code:
#include <iostream>
using namespace std;

struct Song {
string name;
Song* next;
};

// Function to add a song


void addSong(Song*& head, string songName) {
Song* newSong = new Song{songName, nullptr};
if (!head) {
head = newSong;
} else {
Song* temp = head;
while (temp->next) temp = temp->next;
temp->next = newSong;
76

}
}

// Function to display the playlist


void displayPlaylist(Song* head) {
while (head) {
cout << head->name << " -> ";
head = head->next;
}
cout << "END" << endl;
}

int main() {
Song* playlist = nullptr;

addSong(playlist, "Imagine");
addSong(playlist, "Hey Jude");
addSong(playlist, "Bohemian Rhapsody");

cout << "Playlist: ";


displayPlaylist(playlist);

return 0;
}
Why Use Linked List:
Dynamic Size: New songs can be added easily without worrying about memory
constraints.
Efficient Insertion: Adding songs at the end is quick without the need to shift elements.
Flexibility: Allows sequential traversal through the playlist.
Why Not Use Other Structures:
Array: Fixed size is limiting, and resizing arrays can be computationally expensive.
Stack: LIFO order is irrelevant for playlists that need forward traversal.
Queue: FIFO doesn't support bidirectional or random access traversal.

· Time Complexity:

 Access: O(n)
 Insertion/Deletion: O(1) (at the beginning/end), O(n) (in the middle)
 Search: O(n)

· Importance:

 Dynamically sized, allowing for flexible insertion and deletion.


 Used in implementing stacks, queues, and other data structures.

Scenario 3: Queue
77

Scenario: Managing tickets in an amusement park line.


Code:
#include <iostream>
#include <queue>
using namespace std;

int main() {
queue<string> ticketLine;

// Adding people to the queue


ticketLine.push("Alice");
ticketLine.push("Bob");
ticketLine.push("Charlie");

// Serving customers
cout << "Serving customers:" << endl;
while (!ticketLine.empty()) {
cout << ticketLine.front() << " is being served." << endl;
ticketLine.pop();
}

return 0;
}
Why Use Queue:
FIFO Behavior: People are served in the same order they joined the line.
Dynamic Size: The queue grows and shrinks as people join and leave.
Built-in Operations: Efficient support for adding at the end and removing from the front.
Why Not Use Other Structures:
Array: Inefficient for removing the first element due to shifting .
Linked List: While it can implement a queue, the built-in queue simplifies usage.
Stack: LIFO behavior doesn’t match the real-world scenario of a line.

· Time Complexity:

 Enqueue: O(1)
 Dequeue: O(1)
 Search: O(n)

· Importance:

 First-In-First-Out (FIFO) data structure.


 Used in task scheduling, breadth-first search, and simulation.

Scenario 4: Stack
Scenario: Managing undo operations in a text editor.
Code:
#include <iostream>
78

#include <stack>
using namespace std;

int main() {
stack<string> undoStack;

// Actions performed
undoStack.push("Typed 'Hello'");
undoStack.push("Deleted 'World'");
undoStack.push("Bolded text");

// Undoing actions
cout << "Undoing actions:" << endl;
while (!undoStack.empty()) {
cout << "Undo: " << undoStack.top() << endl;
undoStack.pop();
}

return 0;
}
Why Use Stack:
LIFO Behavior: The last performed action is undone first.
Push/Pop Operations: Actions are pushed onto the stack when performed and popped off when
undone.
Relevance: Perfectly models the undo mechanism.
Why Not Use Other Structures:
Array: Inefficient for dynamic undo operations as resizing or rearranging might be
required.
Linked List: While technically feasible, managing LIFO order manually is less
straightforward.
Queue: FIFO behavior contradicts the need to undo the most recent action first.

· Time Complexity:

 Push: O(1)
 Pop: O(1)
 Search: O(n)

· Importance:

 Last-In-First-Out (LIFO) data structure.


 Used in function call stacks, undo/redo operations, and backtracking algorithms.

Week 13: Trees


79

Binary Tree Problem


#include <iostream>
#include <stack>
#include <cctype>
#include <cstdlib>
using namespace std;

// Tree Node structure


struct Node {
char value;
Node *left, *right;

Node(char val) : value(val), left(nullptr), right(nullptr) {}


};

// Expression Tree Class


class ExpressionTree {
public:
// Construct tree from postfix expression
Node* buildTree(const string &postfix) {
stack<Node*> st;

for (char ch : postfix) {


if (isspace(ch)) continue;

if (isdigit(ch)) { // Operand
st.push(new Node(ch) }
else { // Operator
Node *node = new Node(ch);
node->right = st.top(); st.pop();
node->left = st.top(); st.pop();
st.push(node);
}
}
return st.top();
}

// Inorder traversal (for infix expression)


void inorder(Node* root) {
if (!root) return;
if (root->left && root->right) cout << "(";
inorder(root->left);
cout << root->value;
inorder(root->right);
if (root->left && root->right) cout << ")";
}
80

// Evaluate expression tree


int evaluate(Node* root) {
if (!root) return 0;

if (!root->left && !root->right) { // Leaf node


return root->value - '0';
} int leftVal = evaluate(root->left);
int rightVal = evaluate(root->right);

switch (root->value) {
case '+': return leftVal + rightVal;
case '-': return leftVal - rightVal;
case '*': return leftVal * rightVal;
case '/': return leftVal / rightVal;
}
return 0;
}
};

int main() {
string postfix = "34+5*"; // Example: (3 + 4) * 5
ExpressionTree et;

Node *root = et.buildTree(postfix);

cout << "Infix Expression: ";


et.inorder(root);
cout << endl;

cout << "Evaluated Result: " << et.evaluate(root) << endl;

return 0;
}

Expression Tree:

Time Complexity:

 Building the tree (from postfix expression):


o In a postfix expression, each character (either an operand or operator) is processed
once.
o If the length of the expression is nnn, the time complexity to build the tree is
O(n)O(n)O(n).
 Inorder traversal (to print infix expression):

o Inorder traversal visits each node exactly once. So, if the tree has nnn nodes, the
time complexity of inorder traversal is O(n)O(n)O(n).

 Evaluating the expression:


81

o Like inorder traversal, evaluating the expression also visits each node once, so the
time complexity is O(n)O(n)O(n).

Importance:

 Expression Trees are fundamental in evaluating arithmetic expressions, especially in


compilers and interpreters.
 They allow us to efficiently evaluate mathematical expressions while considering operator
precedence.
 Infix, Prefix, and Postfix expressions are widely used in data structures and algorithms (like
in calculators, expression evaluation

QUESTION NO 2:
Huffman Coding Problem
#include <iostream>
#include <queue>
#include <unordered_map>
#include <vector>
using namespace std;

// Node structure for Huffman Tree


struct Node {
char ch;
int freq;
Node *left, *right;

Node(char c, int f) : ch(c), freq(f), left(nullptr), right(nullptr) {}


};

// Comparison object for priority queue


struct Compare {
bool operator()(Node* a, Node* b) {
return a->freq > b->freq;
}
};

// Huffman Coding Class


class HuffmanCoding {
public:
// Build Huffman Tree
Node* buildTree(const unordered_map<char, int> &freqMap) {
priority_queue<Node*, vector<Node*>, Compare> pq;

for (auto &[ch, freq] : freqMap) {


pq.push(new Node(ch, freq));
82

while (pq.size() > 1) {


Node *left = pq.top(); pq.pop();
Node *right = pq.top(); pq.pop();
Node *merged = new Node('\0', left->freq + right->freq);
merged->left = left;
merged->right = right;
pq.push(merged);
}

return pq.top();
}

// Generate codes
void generateCodes(Node* root, const string &path, unordered_map<char, string>
&codes) {
if (!root) return;
if (root->ch != '\0') {
codes[root->ch] = path;
}
generateCodes(root->left, path + "0", codes);
generateCodes(root->right, path + "1", codes);
}

// Encode string
string encode(const string &text, const unordered_map<char, string> &codes) {
string encoded = "";
for (char ch : text) {
encoded += codes.at(ch);
}
return encoded;
}

// Decode string
string decode(const string &binary, Node* root) {
string decoded = "";
Node *current = root;
for (char bit : binary) {
current = (bit == '0') ? current->left : current->right;
if (!current->left && !current->right) {
decoded += current->ch;
current = root;
}
}
return decoded;
}
83

};

int main() {
string text = "ABRACADABRA";
unordered_map<char, int> freqMap;

for (char ch : text) freqMap[ch]++;

HuffmanCoding hc;
Node* root = hc.buildTree(freqMap);

unordered_map<char, string> codes;


hc.generateCodes(root, "", codes);

string encoded = hc.encode(text, codes);


string decoded = hc.decode(encoded, root);

cout << "Encoded: " << encoded << endl;


cout << "Decoded: " << decoded << endl;

return 0;
}

Huffman Coding:

Time Complexity:

 Building the Huffman tree:


o First, we need to calculate the frequency of each character, which takes
O(n)O(n)O(n), where nnn is the length of the input string.
o Then, we use a priority queue (min-heap) to build the tree. Each insertion and
extraction operation takes O(log⁡k)O(\log k)O(logk), where kkk is the number of
distinct characters in the string.
o If there are kkk distinct characters, the time complexity of building the Huffman tree
is O(klog⁡k)O(k \log k)O(klogk).
o So, the total time complexity for building the tree is O(n+klog⁡k)O(n + k \log
k)O(n+klogk), where nnn is the length of the string, and kkk is the number of unique
characters.
 Generating codes:

o The process of traversing the Huffman tree to generate the codes takes
O(k)O(k)O(k), where kkk is the number of distinct characters.

 Encoding the string:

o To encode the string, we simply replace each character with its Huffman code. This
takes O(n)O(n)O(n), where nnn is the length of the input string.

 Decoding the string:


84

o To decode a string of binary digits, we traverse the Huffman tree for each bit in the
binary string. In the worst case, each bit will lead to one traversal of the tree. The
time complexity of decoding is O(m)O(m)O(m), where mmm is the length of the
encoded binary string (which is generally longer than nnn depending on the code
size).

Importance:

 Huffman Coding is an optimal compression technique used in algorithms for lossless data
compression (like ZIP files, JPEG images, and even MP3 files).
 It helps reduce the size of data without losing any information.
 Efficiency in space and time is crucial in many areas of computing, including networking, file
systems, and databases.

QUESTION NO 3:
Heap Tree Problem
#include <iostream>
#include <vector>
using namespace std;

// Heap Class
class Heap {
vector<int> heap;
bool isMinHeap;

int parent(int i) { return (i - 1) / 2; }


int left(int i) { return 2 * i + 1; }
int right(int i) { return 2 * i + 2; }

void heapifyDown(int i) {
int smallestOrLargest = i;
int l = left(i), r = right(i);

if (l < heap.size() && compare(heap[l], heap[smallestOrLargest])) {


smallestOrLargest = l;
}
if (r < heap.size() && compare(heap[r], heap[smallestOrLargest])) {
smallestOrLargest = r;
}

if (smallestOrLargest != i) {
swap(heap[i], heap[smallestOrLargest]);
heapifyDown(smallestOrLargest);
}
}
85

void heapifyUp(int i) {
while (i > 0 && compare(heap[i], heap[parent(i)])) {
swap(heap[i], heap[parent(i)]);
i = parent(i);
}
}

bool compare(int a, int b) {


return isMinHeap ? (a < b) : (a > b);
}

public:
Heap(bool minHeap = true) : isMinHeap(minHeap) {}

void insert(int val) {


heap.push_back(val);
heapifyUp(heap.size() - 1);
}

int extract() {
if (heap.empty()) return -1;
int root = heap[0];
heap[0] = heap.back();
heap.pop_back();
heapifyDown(0);
return root;
}

void print() {
for (int val : heap) cout << val << " ";
cout << endl;
}
};

int main() {
Heap minHeap(true), maxHeap(false);

minHeap.insert(10);
minHeap.insert(20);
minHeap.insert(5);

cout << "Min-Heap: ";


minHeap.print();
cout << "Extracted Min: " << minHeap.extract() << endl;
minHeap.print();

maxHeap.insert(10);
86

maxHeap.insert(20);
maxHeap.insert(5);

cout << "Max-Heap: ";


maxHeap.print();
cout << "Extracted Max: " << maxHeap.extract() << endl;
maxHeap.print();

return 0;
}

Time Complexity:

 Insertion:
o Inserting an element into a heap involves adding it at the end of the array (which
takes O(1)O(1)O(1)) and then "bubbling up" to restore the heap property, which
takes O(log⁡n)O(\log n)O(logn), where nnn is the size of the heap.
 Extraction:

o Extracting the root element involves replacing it with the last element, then
"bubbling down" to restore the heap property. The "bubbling down" operation
takes O(log⁡n)O(\log n)O(logn), where nnn is the number of elements in the heap.

 Building the heap (from an unordered array):

o If we need to build a heap from an unordered array of nnn elements, it takes


O(n)O(n)O(n) time using the heapify algorithm.

Importance:

 Heaps are essential for priority queues where the elements are processed based on their
priority (e.g., job scheduling systems).
 They are used in many algorithms, such as Dijkstra’s shortest path algorithm, heap sort, and
Kruskal’s minimum spanning tree algorithm.
 Heap data structures are highly efficient in situations where we need to frequently extract
the maximum or minimum element.

You might also like