Professional Documents
Culture Documents
Samuel Tolossa
Samuel Tolossa
Submitted to:
Instructor: Belayneh Mengistu
1. Some advantages of linked lists over arrays have already been mentioned. However, there are
occasions when arrays have advantages over linked lists. When are arrays preferable?
Advantage Description
Random access Arrays can be randomly accessed, which means that you can access any
element in the array directly with an index. This makes arrays more
efficient than linked lists for operations such as searching, sorting, and
random access.
Cache locality Arrays have better cache locality than linked lists, which means that they
are better suited for situations where the data needs to be accessed
sequentially. This is because arrays store their elements in contiguous
memory locations, allowing the CPU cache to load more data
simultaneously.
Memory efficiency Arrays are more memory-efficient than linked lists in terms of memory
overhead. This is because arrays only need to store the elements
themselves, whereas linked lists need to store pointers to the next
element. This can be important in situations where memory is limited
d.Write a function that deletes all elements that have data= item
void deleteNodesWithData(Node *&head, int item){
while (head != NULL && head->data == item){
Node *temp = head;
head = head->next;
delete temp;
}
if (head == NULL){
return;
}
Node *current = head;
while (current->next != NULL){
if (current->next->data == item){
Node *temp = current->next;
current->next = temp->next;
delete temp;
} else {
current = current->next;
}
}
}
Stack Queue
The last element added (also known The first element added (also known as the
as the "top" of the stack) is the first "front" of the queue) is the first one to be
one to be removed. removed.
Elements can be added to the top of Elements can be added to the back of the
the stack (called "pushing") or queue (called "enqueueing") or removed
removed from the top of the stack from the front of the queue (called
(called "popping"). "dequeueing").
#include <iostream>
struct Node {
int data;
Node* next;
};
Node* head = nullptr;
int size = 0;
newNode->data = data;
newNode->next = head;
head = newNode;
size++;
int pop() {
if (head == nullptr) {
return INT_MIN;
head = head->next;
delete temp;
size--;
return data;
int peek() {
if (head == nullptr) {
return INT_MIN;
return head->data;
}
bool is_empty() {
int get_size() {
return size;
int main() {
push(1);
push(2);
push(3);
return 0;
#include <iostream>
#include <cstring>
struct Stack {
int top;
unsigned capacity;
char* array;
};
stack->capacity = capacity;
stack->top = -1;
return stack;
if (isFull(stack))
return;
stack->array[++stack->top] = item;
if (isEmpty(stack))
return '\0';
return stack->array[stack->top--];
int n = strlen(str);
Stack* stack = createStack(n);
push(stack, str[i]);
// Pop each character from the stack and overwrite it to reverse the string
str[i] = pop(stack);
delete[] stack->array;
delete stack;
int main() {
char str[100];
cin.getline(str, 100);
reverseString(str);
return 0;
6. Write a code to implement circular queue (all operations i.e. enqueue(), display, and deque()
operations)
#include <iostream>
#define MAX_SIZE 5
// Define a circular queue class
class CircularQueue {
private:
int items[MAX_SIZE];
public:
CircularQueue() {
front = -1;
rear = -1;
bool isFull() {
return true;
return false;
bool isEmpty() {
if (front == -1) {
return true;
return false;
if (isFull()) {
return;
if (front == -1) {
front = 0;
items[rear] = element;
int dequeue() {
int element;
if (isEmpty()) {
return -1;
element = items[front];
if (front == rear) {
front = -1;
rear = -1;
} else {
return element;
}
void display() {
if (isEmpty()) {
return;
int i;
};
int main() {
CircularQueue q;
q.enqueue(1);
q.enqueue(2);
q.enqueue(3);
q.enqueue(4);
q.enqueue(5);
q.display();
// Remove elements from the queue
q.dequeue();
q.dequeue();
q.display();
q.enqueue(6);
q.enqueue(7);
q.display();
return 0;
7. Write a program to simulate the queue system of a bank (Single window system). Assume there
are 10Windows that serve customers. Your program should assign customers to any of the windows
based on the clerk’s request. The program should also display how many customers each clerk served.
#include <iostream>
#include <queue>
#include <vector>
#include <ctime>
#include <cstdlib>
class Customer {
private:
int id;
int arrivalTime;
int serviceTime;
public:
id = i;
arrivalTime = a;
serviceTime = s;
int getId() {
return id;
int getArrivalTime() {
return arrivalTime;
int getServiceTime() {
return serviceTime;
}
};
class Bank {
private:
int currentTime;
queue<Customer*> customerQueue;
vector<int> customersServed;
int windowsAvailable;
public:
Bank() {
currentTime = 0;
windowsAvailable = NUM_WINDOWS;
customersServed.assign(NUM_WINDOWS, 0);
void addCustomer() {
return;
int id = customerQueue.size() + 1;
customerQueue.push(customer);
cout << "Customer " << id << " added to the queue at time " << arrivalTime << "." <<
endl;
void serveCustomers() {
if (customerQueue.empty()) {
return;
customerQueue.pop();
customersServed[windowNumber - 1]++;
windowsAvailable--;
cout << "Customer " << customer->getId() << " is being served at window " <<
windowNumber << " from time " << currentTime << " to
time " << serviceEndTime << "." << endl;
delete customer;
void update() {
if (customersServed[i] > 0) {
customersServed[i]--;
} else {
windowsAvailable++;
}
currentTime++;
void displayStats() {
};
int main() {
Bank bank;
bank.addCustomer();
bank.addCustomer();
bank.addCustomer();
bank.serveCustomers();
bank.update();
// Add more customers to the queue
bank.addCustomer();
bank.addCustomer();
bank.serveCustomers();
bank.update();
A tree is a non-linear data structure that has various applications. Here are some of the applications of
tree with example:
Example Description
XML and HTML Parsing XML and HTML documents use the tree structure
to represent information.
Binary Search Tree This is a data structure designed for fast searching
in large data sets. An example of this is the
implementation of sa earch functions in libraries.
Recursion is a programming technique in which a function calls itself one or more times within
its own body to solve a problem or perform a task. In other words, recursion involves defining a problem
or task in terms of itself repeatedly until a base case is reached that can be solved without recursion.
This allows complex problems to be broken down into smaller, simpler problems that can be solved
more easily.
10. Write a recursive function insertEnd to insert the at the end of the Linked List
if (head == nullptr) {
// If the list is empty, create a new node and make it the head
return;
if (head->next == nullptr) {
// If the list has only one node, create a new node and make it the next of the head
return;
insertEnd(head->next, val);
11. Define the following terms to support your answer with an appropriate example
A/ Double-ended queue is a linear data structure that allows insertion and deletion from both
the ends (front and rear). An example of a deque includes browser history, which can be traversed
backward and forward.
B/ Priority queue is stores items in a sorted order based on their priority. The highest priority
elements are placed at the front of the queue. An example of a priority queue is a medical emergency
room, where patients are treated based on the severity of their condition.
C/ Tree a non-linear data structure that consists of nodes and edges, where each node has zero
or more children except for the root node, which has no parent node. An example of a tree could be a
family genealogy chart.
D/ Binary tree is a type of tree data structure where each node has at most two children (left
and right). An example of a binary tree could be a decision tree in a game or program.
A binary search tree is a binary tree with a specific ordering property, where all nodes in the left
subtree of a node have values less than the node's value, and all nodes in the right subtree have values
greater than the value of the node. This property helps with efficient searching and sorting. An example
of a binary search tree could be an organization's directory ordered by employee ID numbers.
F/ Complete Binary tree is a binary tree where all levels of the tree are filled except possibly the
last level, which is filled from left to right. An example of a complete binary tree is a binary heap.
G/ Full binary tree is a binary tree where every node has either zero or two children. An
example of a full binary tree could be a decision tree with only yes/no answers.
H/ Level of binary tree is tree refers to the distance from the root node to a particular node. For
example, the level of the root node is 0, and the level of a leaf node (a node without children) would be
the depth of the tree.
I/ Depth of a binary tree is refers to the maximum level in the tree. In other words, it is the
length of the longest path from the root node to a leaf node.
12. Write the full code to implement the binary search tree
#include <iostream>
class Node {
public:
int data;
Node(int val) {
data = val;
};
class BST {
private:
Node *root;
if (!node) {
return node;
if (!node) {
return false;
} else {
return true;
}
void inorderHelper(Node *node) {
if (node) {
inorderHelper(node->left);
inorderHelper(node->right);
if (node) {
deleteHelper(node->left);
deleteHelper(node->right);
delete node;
public:
BST() {
root = nullptr;
~BST() {
deleteHelper(root);
}
bool search(int data) {
void inorder() {
inorderHelper(root);
};
int main() {
BST tree;
tree.insert(10);
tree.insert(5);
tree.insert(15);
tree.insert(3);
tree.insert(7);
tree.inorder();
std::cout << "Searching for 3: " << (tree.search(3) ? "Found" : "Not found") << '\n';
std::cout << "Searching for 13: " << (tree.search(13) ? "Found" : "Not found") << '\n';
return 0;
}
13. Given a Preorder=[10,2,1,5,3,4,7,6,8,9,12,11,13,14] Inorder =[1,2,3,4,5,6,7,8,9,10,11,12,13,14]
construct a binary search tree and write the post order traversal of the tree?
To construct a binary search tree from Preorder and Inorder traversals, we can follow the below steps:
Step 1: Take the first element of the preorder sequence and make it the root of the binary search tree.
Step 2: Find the position of the root element in the inorder sequence. Elements to the left of the index
are the left subtree, and elements to the right of the index are the right subtree.
Step 3: Recursively repeat the above two steps for the left and right subtrees.
10
2 12
1 5 11 13
3 7 11 14
4 6 8
To write the post-order traversal of the tree in C++, we can implement a recursive function that outputs
the node values in the following order: left subtree, right subtree, root node. Here is a sample C++ code:
#include <iostream>
#include <vector>
int val;
TreeNode *left;
TreeNode *right;
};
return NULL;
int rootIndex = 0;
if (inorder[i] == rootVal) {
rootIndex = i;
break;
return root;
}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {
int n = preorder.size();
if (root == NULL) {
return;
postOrder(root->left);
postOrder(root->right);
int main() {
postOrder(root);
return 0;
Output:
1 4 3 6 9 8 7 5 2 11 14 13 12 10
14. Give code segment for inorder, preorder and postorder traversal.
#include <iostream>
struct TreeNode {
int val;
TreeNode* left;
TreeNode* right;
};
if (root == NULL)
return;
inorderTraversal(root->left);
inorderTraversal(root->right);
if (root == NULL)
return;
preorderTraversal(root->left);
preorderTraversal(root->right);
if (root == NULL)
return;
postorderTraversal(root->left);
postorderTraversal(root->right);
int main() {
inorderTraversal(root);
preorderTraversal(root);
postorderTraversal(root);
return 0;
}
This code defines a TreeNode struct to represent a node in a binary tree. It then defines three functions
for doing an in-order, pre-order, and post-order traversal of the tree. Finally, the code creates a sample
tree and prints out the results of all three traversals.
15. Given list[10]={2,6,1,3,8,7,9,10,4,5,0}; use merge sort to sort the given items
#include <iostream>
int n1 = m - l + 1;
int n2 = r - m;
arr[k] = L[i];
i++;
else {
arr[k] = R[j];
j++;
k++;
arr[k] = L[i];
i++;
k++;
arr[k] = R[j];
j++;
k++;
if (l < r) {
int m = l + (r - l) / 2;
mergeSort(arr, l, m);
mergeSort(arr, m + 1, r);
merge(arr, l, m, r);
int main()
int n = sizeof(arr)/sizeof(arr[0]);
mergeSort(arr, 0, n - 1);
return 0;
Output:
Given array is
2 6 1 3 8 7 9 10 4 5
Sorted array is
1 2 3 4 5 6 7 8 9 10
16. How do we find the smallest node in a binary search tree? What is the runtime complexity to do
this in both an unbalanced and balanced binary search tree, in the worst case? How do we find the
largest node in a binary search tree? What are the runtime complexities for this?
To find the smallest node in a binary search tree, we need to keep moving toward the left-most node
until we reach the node with the minimum value. This is because a binary search tree follows the
property that each node in the left subtree is less than the root node's value and each node in the right
subtree is greater than the root node's value.
The runtime complexity to find the smallest node in an unbalanced binary search tree is O(n) in the
worst case, where n is the number of nodes in the tree since the algorithm has to traverse all the nodes
to get to the left-most node. In a balanced binary search tree, like an AVL tree, which maintains a
balance between the left and right subtree, the worst-case complexity to find the smallest node is O(log
n), where n is the number of nodes in the tree.
To find the largest node in a binary search tree, we need to repeat a similar procedure of moving
towards the right-most node instead of the left-most node as in the case of the smallest node. This is
because the right-most node of a binary search tree will always contain the maximum value in the tree.
The runtime complexity to find the largest node in an unbalanced binary search tree is O(n) in the worst
case, where n is the number of nodes in the tree since the algorithm has to traverse all the nodes to get
to the right-most node. In a balanced binary search tree, like an AVL tree, the worst-case complexity to
find the largest node is O(log n), where n is the number of nodes in the tree.
17. In a binary search tree, the successor of some node x is the next largest node after x. For example,
in a binary search tree containing the keys 24, 39, 41, 55, 87, 92, the successor of 41 is 55. How do
we find the successor of a node in a binary search tree? What is the runtime complexity of this
operation?
In order to find the successor of a node "x" in a binary search tree, the following steps should be taken:
1. If node "x" has a right sub-tree, the successor will be the leftmost (minimum) node in this sub-
tree.
2. If not, a successor can be found by traversing up from "x" until we reach a node "y" such that
we are coming from a left child of "y". The parent of "y" will then be the successor.
The runtime complexity of finding the successor of a node in a binary search tree is O(h), where h is the
height of the tree. If the tree is well-balanced, the height would be log(n) where n is the total number of
nodes in the tree, so the runtime complexity would be logarithmic. However, if the tree is degenerate
(e.g. every node has only one child and the tree is essentially a linked list), then the height of the tree
would be n, and the runtime would be linear in the number of nodes.
The main problem with binary search trees is that their performance can degrade to worst-case
scenarios if the tree becomes unbalanced or becomes degenerate. This significantly reduces their
efficiency for searching, inserting, and deleting operations. Additionally, when data is inserted into a
binary search tree in sorted order, it may result in an unbalanced tree with long nodes on one side,
leading to poor search performance. To mitigate these issues, self-balancing binary search trees such as
AVL trees or Red-Black trees are used.
An AVL tree is a self-balancing binary search tree that maintains a balance factor for each node in the
tree. This balance factor is based on the difference in heights between the left and right subtrees. The
value of the balance factor can be -1, 0, or 1, with -1 representing that the left subtree is one level
deeper than the right subtree, 0 representing that both the left and right subtrees are balanced, and 1
representing that the right subtree is one level deeper than the left subtree.
AVL trees guarantee that the height difference between any two subtrees is at most one. Whenever an
insertion or deletion is made in the tree and this property is violated, AVL trees automatically balance
themselves by performing rotations. These rotations are designed to bring the tree back into a balanced
state while maintaining the order of the nodes within the tree.
The time complexity of operations on an AVL tree is O(log n) since the height of the tree is always kept in
balance. AVL trees are used in applications where frequent insertions and deletions need to be
performed while maintaining a sorted order of the data.
20. Given list {2,6,1,3,8,7,9,10,4,5,0}; create the heap tree and sort the items using heap sort
To create a heap tree out of this list, we can use the following heapify algorithm:
1. Starting from the middle of the list and going backwards, rule down each element to its final position
within the heap.
2. To rule an element down, swap it with its largest child until it is larger than both of them.
3. Once the list is heapified, sort it by repeatedly removing the root node (the maximum) and placing it
at the end of the list. Heapify the remaining heap again and repeat until the heap is empty.
2(0)
/ \
6(1) 1(2)
/ \ / \
/ \ /
1. Remove the root node (2) and place it at the end of the list.
/ \
6(1) 1(2)
/ \ / \
/ \
5(7) 4(8)
3. Remove the new root node (10) and place it just before the previous maximum.
9(0)
/ \
6(1) 1(2)
/ \ / \
5(7)