lab manual
lab manual
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
Solution:
#include <iostream>
using namespace std;
const int MAX = 100;
int main() {
int array[MAX];
int size;
int position;
5
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);
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;
int n;
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:
2. It improves the usability and readability of data for applications like databases,
algorithms, and file management.
Time Complexity:
1. Bubble Sort:
Worst and Average case: O(n²) (due to nested loops for pairwise comparison).
2. Insertion Sort:
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;
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
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
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.
---
Time Complexity:
Channel and volume operations execute in O(1) since they involve simple condition
checks or assignments.
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.
Locking, unlocking, and status checks run in O(1) because they involve simple
boolean operations.
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
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");
Question no 2:
20
Solution:
#include <iostream>
#include <string>
}
temp = temp->next;
}
cout << "Employee with ID " << emp_id << " not found." << endl;
}
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
~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
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;
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
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
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;
}
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;
}
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.
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;
public:
// Constructor to initialize the stack
BookStack() {
top = NULL;
}
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;
}
cout << "Book '" << title << "' by " << author << " (ID: " << book_id << ") has
been returned." << endl;
}
int main() {
BookStack library_stack;
35
// Return a book
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;
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;
}
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;
}
top->next = NULL;
} else {
// The stack becomes empty
top = NULL;
}
cout << "Task '" << task_description << "' (ID: " << task_id << ") has been
completed." << endl;
}
int main() {
TaskStack employee_tasks;
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;
// 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
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;
}
cout << "Course '" << course_name << "' (ID: " << course_id << ") has been
withdrawn." << endl;
}
int main() {
CourseStack student_courses;
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;
public:
// Constructor to initialize the stack
CartModificationStack() {
top = NULL;
}
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;
}
cout << "Product '" << product_name << "' (ID: " << product_id << ") has been
removed from the cart." << endl;
}
int main() {
CartModificationStack cart;
return 0;
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.
Insertion:
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.
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.
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;
public:
CircularLinkedList() : last(nullptr) {}
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";
}
}
cout << "Seat " << seatID << " not found.\n";
}
if (!seatFound) {
cout << "Seat " << startSeatID << " not found.\n";
return;
}
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
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>
// 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");
}
}
int main() {
string expression;
cout << "Enter a prefix expression: ";
getline(cin, expression);
try {
50
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>
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");
}
}
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>
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:
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.";
}
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
return 0;
}
Output:
58
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"
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
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:
Lower time complexity is generally better, as it indicates faster execution times for
larger inputs.
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
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;
}
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.
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.
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;
newNode->student = student;
newNode->left = newNode->right = NULL;
return newNode;
}
return root;
}
int main() {
Node* root = NULL;
if (searchedStudent != NULL) {
67
return 0;
}
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
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) {}
};
// 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;
// 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;
public:
AVLTree() : root(NULL) {}
int main() {
AVLTree tree;
71
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>
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;
// 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;
// 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
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.
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
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"};
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:
struct Song {
string name;
Song* next;
};
}
}
int main() {
Song* playlist = nullptr;
addSong(playlist, "Imagine");
addSong(playlist, "Hey Jude");
addSong(playlist, "Bohemian Rhapsody");
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:
Scenario 3: Queue
77
int main() {
queue<string> ticketLine;
// 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:
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:
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();
}
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;
return 0;
}
Expression Tree:
Time Complexity:
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).
o Like inorder traversal, evaluating the expression also visits each node once, so the
time complexity is O(n)O(n)O(n).
Importance:
QUESTION NO 2:
Huffman Coding Problem
#include <iostream>
#include <queue>
#include <unordered_map>
#include <vector>
using namespace std;
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;
HuffmanCoding hc;
Node* root = hc.buildTree(freqMap);
return 0;
}
Huffman Coding:
Time Complexity:
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.
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.
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;
void heapifyDown(int i) {
int smallestOrLargest = i;
int l = left(i), r = right(i);
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);
}
}
public:
Heap(bool minHeap = true) : isMinHeap(minHeap) {}
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);
maxHeap.insert(10);
86
maxHeap.insert(20);
maxHeap.insert(5);
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(logn)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(logn)O(\log n)O(logn), where nnn is the number of elements in the heap.
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.