Download as docx, pdf, or txt
Download as docx, pdf, or txt
You are on page 1of 35

Adama Science and Technology University

School of Electrical Engineering and Computing


Course Title: Data Structures and Algorithms
Course Number: CSEg2101
Name: Samuel Tolossa
ID Number: Ugr/25454/14
Section: 7
Group: 14

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

2. Given the following Node definition for a singly linked list


Struct Node{
int data;
Node *next=NULL;
} list=NULL;
Answer the following questions
a.Write a function that inserts in the front
void insertAtBeginning(Node *&head, int data){
Node *newNode = new Node;
newNode->data = data;
newNode->next = head;
head = newNode;
}

b.Write a function that inserts in the end


void insertAtEnd(Node *&head, int data){
Node *newNode = new Node;
newNode->data = data;
newNode->next = NULL;
if (head == NULL){
head = newNode;
return;
}
Node *current = head;
while (current->next != NULL){
current = current->next;
}
current->next = newNode;
}

c.Write a function that inserts after the nth element


void insertAfterNth(Node *&head, int data, int n){
Node *newNode = new Node;
newNode->data = data;
if (head == NULL){
head = newNode;
return;
}
Node *current = head;
for (int i = 1; i < n; i++){
if (current->next == NULL){
return;
}
current = current->next;
}
newNode->next = current->next;
current->next = newNode;
}

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;
}
}
}

e.Write a function that deletes an nth element


void deleteNthNode(Node *&head, int n){
if (head == NULL){
return;
}
if (n == 1){
Node *temp = head;
head = head->next;
delete temp;
return;
}
Node *current = head;
for (int i = 1; i < n - 1; i++){
if (current->next == NULL){
return;
}
current = current->next;
}
Node *temp = current->next;
current->next = temp->next;
delete temp;
}

f.Write a function that copies the list


Node* copyList(Node *head){
if (head == NULL){
return NULL;
}
Node *newHead = new Node;
newHead->data = head->data;
Node *current = newHead;
Node *original = head->next;
while (original != NULL){
Node *newNode = new Node;
newNode->data = original->data;
current->next = newNode;
current = newNode;
original = original->next;
}
current->next = NULL;
return newHead;
}
g.Write a function that appends list2 to list1
void appendLists(Node *&list1, Node *list2){
if (list1 == NULL){
list1 = list2;
return;
}
Node *current = list

3. What is the major difference between Stack and Queue

Difference between Stack and Queue

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.

This is known as a Last-In, First-Out This is known as a First-In, First-Out (FIFO)


(LIFO) data structure data structure.

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").

A stack is like a stack of plates, A queue is like a line of people waiting to


where the last plate added is the be served, where the person who arrived
first one to be taken off the top. first is the first one to be served.

4. Implement a Stack using a linked list.

#include <iostream>

using namespace std;

struct Node {

int data;

Node* next;

};
Node* head = nullptr;

int size = 0;

void push(int data) {

Node* newNode = new Node();

newNode->data = data;

newNode->next = head;

head = newNode;

size++;

int pop() {

if (head == nullptr) {

return INT_MIN;

int data = head->data;

Node* temp = head;

head = head->next;

delete temp;

size--;

return data;

int peek() {

if (head == nullptr) {

return INT_MIN;

return head->data;

}
bool is_empty() {

return head == nullptr;

int get_size() {

return size;

int main() {

push(1);

push(2);

push(3);

cout << pop() << endl; // output: 3

return 0;

5. Write a program to reverse a string(character array) using a stack

#include <iostream>

#include <cstring>

using namespace std;

// Define a structure for the stack

struct Stack {

int top;

unsigned capacity;

char* array;

};

// Function to create a stack of given capacity

Stack* createStack(unsigned capacity) {


Stack* stack = new Stack;

stack->capacity = capacity;

stack->top = -1;

stack->array = new char[(stack->capacity * sizeof(char))];

return stack;

// Function to check if the stack is full

int isFull(Stack* stack) {

return stack->top == (stack->capacity - 1);

// Function to check if the stack is empty

int isEmpty(Stack* stack) {

return stack->top == -1;

// Function to push an item onto the stack

void push(Stack* stack, char item) {

if (isFull(stack))

return;

stack->array[++stack->top] = item;

// Function to pop an item from the stack

char pop(Stack* stack) {

if (isEmpty(stack))

return '\0';

return stack->array[stack->top--];

// Function to reverse a string using stack

void reverseString(char* str) {

int n = strlen(str);
Stack* stack = createStack(n);

// Push each character onto the stack

for (int i = 0; i < n; i++)

push(stack, str[i]);

// Pop each character from the stack and overwrite it to reverse the string

for (int i = 0; i < n; i++)

str[i] = pop(stack);

// Free the memory allocated to the stack

delete[] stack->array;

delete stack;

int main() {

char str[100];

cout << "Enter a string: ";

cin.getline(str, 100);

reverseString(str);

cout << "Reversed string: " << str << endl;

return 0;

6. Write a code to implement circular queue (all operations i.e. enqueue(), display, and deque()
operations)

#include <iostream>

using namespace std;

// Define the maximum size of the circular queue

#define MAX_SIZE 5
// Define a circular queue class

class CircularQueue {

private:

int front, rear;

int items[MAX_SIZE];

public:

CircularQueue() {

front = -1;

rear = -1;

// Function to check if the queue is full

bool isFull() {

if ((front == 0 && rear == MAX_SIZE - 1) || (rear == (front - 1) % (MAX_SIZE - 1))) {

return true;

return false;

// Function to check if the queue is empty

bool isEmpty() {

if (front == -1) {

return true;

return false;

// Function to insert an element into the queue


void enqueue(int element) {

if (isFull()) {

cout << "Queue is full." << endl;

return;

if (front == -1) {

front = 0;

rear = (rear + 1) % MAX_SIZE;

items[rear] = element;

cout << element << " enqueued." << endl;

// Function to remove an element from the queue

int dequeue() {

int element;

if (isEmpty()) {

cout << "Queue is empty." << endl;

return -1;

element = items[front];

if (front == rear) {

front = -1;

rear = -1;

} else {

front = (front + 1) % MAX_SIZE;

cout << element << " dequeued." << endl;

return element;
}

// Function to display the elements in the queue

void display() {

if (isEmpty()) {

cout << "Queue is empty." << endl;

return;

cout << "Elements in the queue: ";

int i;

for (i = front; i != rear; i = (i + 1) % MAX_SIZE) {

cout << items[i] << " ";

cout << items[i] << endl;

};

int main() {

CircularQueue q;

// Insert elements into the queue

q.enqueue(1);

q.enqueue(2);

q.enqueue(3);

q.enqueue(4);

q.enqueue(5);

// Display the elements in the queue

q.display();
// Remove elements from the queue

q.dequeue();

q.dequeue();

// Display the elements in the queue

q.display();

// Insert more elements into the queue

q.enqueue(6);

q.enqueue(7);

// Display the elements in the queue

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>

using namespace std;

// Define the number of windows


#define NUM_WINDOWS 10

// Define the maximum number of customers in the queue

#define MAX_CUSTOMERS 100

// Define a customer class

class Customer {

private:

int id;

int arrivalTime;

int serviceTime;

public:

Customer(int i, int a, int s) {

id = i;

arrivalTime = a;

serviceTime = s;

int getId() {

return id;

int getArrivalTime() {

return arrivalTime;

int getServiceTime() {

return serviceTime;

}
};

// Define a bank class

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);

srand(time(0)); // Seed the random number generator

void addCustomer() {

if (customerQueue.size() >= MAX_CUSTOMERS) {

cout << "Customer queue is full." << endl;

return;

int id = customerQueue.size() + 1;

int arrivalTime = currentTime;

int serviceTime = rand() % 5 + 1; // Random service time between 1 and 5 minutes

Customer* customer = new Customer(id, arrivalTime, serviceTime);

customerQueue.push(customer);
cout << "Customer " << id << " added to the queue at time " << arrivalTime << "." <<
endl;

void serveCustomers() {

if (customerQueue.empty()) {

cout << "No customers in the queue." << endl;

return;

while (windowsAvailable > 0 && !customerQueue.empty()) {

Customer* customer = customerQueue.front();

customerQueue.pop();

int windowNumber = NUM_WINDOWS - windowsAvailable + 1;

customersServed[windowNumber - 1]++;

windowsAvailable--;

int serviceEndTime = currentTime + customer->getServiceTime();

cout << "Customer " << customer->getId() << " is being served at window " <<
windowNumber << " from time " << currentTime << " to
time " << serviceEndTime << "." << endl;

delete customer;

void update() {

if (windowsAvailable < NUM_WINDOWS) {

for (int i = 0; i < NUM_WINDOWS; i++) {

if (customersServed[i] > 0) {

customersServed[i]--;

} else {

windowsAvailable++;
}

currentTime++;

void displayStats() {

cout << "Customers served at each window: ";

for (int i = 0; i < NUM_WINDOWS; i++) {

cout << customersServed[i] << " ";

cout << endl;

};

int main() {

Bank bank;

// Add some customers to the queue

bank.addCustomer();

bank.addCustomer();

bank.addCustomer();

// Serve the customers

bank.serveCustomers();

// Update the bank's state

bank.update();
// Add more customers to the queue

bank.addCustomer();

bank.addCustomer();

// Serve the customers

bank.serveCustomers();

// Update the bank's state

bank.update();

8. List the application of the tree with an example

A tree is a non-linear data structure that has various applications. Here are some of the applications of
tree with example:

Example Description

File Systems File structures are organized in a hierarchical


structure, which means files within a directory can
be organized in a tree-like structure.

Network Routing Routing algorithms use trees to represent the


structure of network routing tables.

XML and HTML Parsing XML and HTML documents use the tree structure
to represent information.

Compiler Design Compiler Design uses trees to parse and represent


programming language statements.

Decision Trees In Machine Learning, decision trees are used to


make decisions and run predictions based on the
features of the dataset.

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.

Expression Trees Used to evaluate and generate mathematical


expressions such as algebraic expressions.
9. What is recursion?

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

void insertEnd(Node* head, int val) {

if (head == nullptr) {

// If the list is empty, create a new node and make it the head

head = new Node{val, nullptr};

return;

if (head->next == nullptr) {

// If the list has only one node, create a new node and make it the next of the head

head->next = new Node{val, nullptr};

return;

// Recursively call insertEnd on the next node

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.

E/ Binary search tree

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 *left, *right;

Node(int val) {

data = val;

left = right = nullptr;

};
class BST {

private:

Node *root;

Node* insertHelper(Node *node, int data) {

if (!node) {

return new Node(data);

} else if (data < node->data) {

node->left = insertHelper(node->left, data);

} else if (data > node->data) {

node->right = insertHelper(node->right, data);

return node;

bool searchHelper(Node *node, int data) {

if (!node) {

return false;

} else if (data < node->data) {

return searchHelper(node->left, data);

} else if (data > node->data) {

return searchHelper(node->right, data);

} else {

return true;

}
void inorderHelper(Node *node) {

if (node) {

inorderHelper(node->left);

std::cout << node->data << ' ';

inorderHelper(node->right);

void deleteHelper(Node *node) {

if (node) {

deleteHelper(node->left);

deleteHelper(node->right);

delete node;

public:

BST() {

root = nullptr;

~BST() {

deleteHelper(root);

void insert(int data) {

root = insertHelper(root, data);

}
bool search(int data) {

return searchHelper(root, data);

void inorder() {

inorderHelper(root);

std::cout << '\n';

};

int main() {

BST tree;

tree.insert(10);

tree.insert(5);

tree.insert(15);

tree.insert(3);

tree.insert(7);

std::cout << "Inorder traversal: ";

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.

Using the given Preorder=10,2,1,5,3,4,7,6,8,9,12,11,13,14 and Inorder =1,2,3,4,5,6,7,8,9,10,11,12,13,14,


we can construct the binary search tree as shown below:

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>

using namespace std;


struct TreeNode {

int val;

TreeNode *left;

TreeNode *right;

TreeNode(int x) : val(x), left(NULL), right(NULL) {}

};

TreeNode* buildTreeHelper(vector<int>& preorder, vector<int>& inorder, int preStart, int


preEnd, int inStart, int inEnd) {

if (preStart > preEnd || inStart > inEnd) {

return NULL;

int rootVal = preorder[preStart];

int rootIndex = 0;

for (int i = inStart; i <= inEnd; i++) {

if (inorder[i] == rootVal) {

rootIndex = i;

break;

TreeNode* root = new TreeNode(rootVal);

int leftSubtreeSize = rootIndex - inStart;

root->left = buildTreeHelper(preorder, inorder, preStart + 1, preStart + leftSubtreeSize,


inStart, rootIndex - 1);

root->right = buildTreeHelper(preorder, inorder, preStart + leftSubtreeSize + 1, preEnd,


rootIndex + 1, inEnd);

return root;

}
TreeNode* buildTree(vector<int>& preorder, vector<int>& inorder) {

int n = preorder.size();

return buildTreeHelper(preorder, inorder, 0, n - 1, 0, n - 1);

void postOrder(TreeNode* root) {

if (root == NULL) {

return;

postOrder(root->left);

postOrder(root->right);

cout << root->val << " ";

int main() {

vector<int> preorder = {10,2,1,5,3,4,7,6,8,9,12,11,13,14};

vector<int> inorder = {1,2,3,4,5,6,7,8,9,10,11,13,14};

TreeNode* root = buildTree(preorder, inorder);

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>

using namespace std;

struct TreeNode {

int val;

TreeNode* left;

TreeNode* right;

TreeNode(int x) : val(x), left(NULL), right(NULL) {}

};

void inorderTraversal(TreeNode* root) {

if (root == NULL)

return;

inorderTraversal(root->left);

cout << root->val << " ";

inorderTraversal(root->right);

void preorderTraversal(TreeNode* root) {

if (root == NULL)

return;

cout << root->val << " ";

preorderTraversal(root->left);

preorderTraversal(root->right);

void postorderTraversal(TreeNode* root) {

if (root == NULL)

return;
postorderTraversal(root->left);

postorderTraversal(root->right);

cout << root->val << " ";

int main() {

TreeNode* root = new TreeNode(1);

root->left = new TreeNode(2);

root->right = new TreeNode(3);

root->left->left = new TreeNode(4);

root->left->right = new TreeNode(5);

cout << "Inorder traversal: ";

inorderTraversal(root);

cout << endl;

cout << "Preorder traversal: ";

preorderTraversal(root);

cout << endl;

cout << "Postorder traversal: ";

postorderTraversal(root);

cout << endl;

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>

using namespace std;

// Merge two sub-arrays of arr[]

// First array is arr[l .. m]

// Second array is arr[m+1 .. r]

void merge(int arr[], int l, int m, int r)

int n1 = m - l + 1;

int n2 = r - m;

// Create temporary arrays

int L[n1], R[n2];

// Copy data to temporary arrays L[] and R[]

for (int i = 0; i < n1; i++)

L[i] = arr[l + i];

for (int j = 0; j < n2; j++)

R[j] = arr[m + 1 + j];

// Merge the temporary arrays back into arr[l..r]

int i = 0; // Initial index of first sub-array

int j = 0; // Initial index of second sub-array


int k = l; // Initial index of merged sub-array

while (i < n1 && j < n2) {

if (L[i] <= R[j]) {

arr[k] = L[i];

i++;

else {

arr[k] = R[j];

j++;

k++;

// Copy the remaining elements of L[], if there are any

while (i < n1) {

arr[k] = L[i];

i++;

k++;

// Copy the remaining elements of R[], if there are any

while (j < n2) {

arr[k] = R[j];

j++;

k++;

// Main function to sort an array of given size using merge sort


void mergeSort(int arr[], int l, int r)

if (l < r) {

// Find the middle point

int m = l + (r - l) / 2;

// Sort first and second halves

mergeSort(arr, l, m);

mergeSort(arr, m + 1, r);

// Merge the sorted halves

merge(arr, l, m, r);

// Driver program to test the above functions

int main()

int arr[10] = {2,6,1,3,8,7,9,10,4,5};

int n = sizeof(arr)/sizeof(arr[0]);

cout << "Given array is \n";

for (int i = 0; i < n; i++)

cout << arr[i] << " ";

mergeSort(arr, 0, n - 1);

cout << "\nSorted array is \n";

for (int i = 0; i < n; i++)


cout << arr[i] << " ";

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.

18. What is the problem with Binary Search Trees.

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.

19. Discuss AVL trees

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.

Using this algorithm, we can create the following heap tree:

2(0)

/ \

6(1) 1(2)

/ \ / \

3(3) 8(4) 7(5) 9(6)

/ \ /

10(7) 4(8) 5(9)

Now let's sort the list using heap sort:

1. Remove the root node (2) and place it at the end of the list.

2. Heapify the remaining heap, giving us:


10(0)

/ \

6(1) 1(2)

/ \ / \

3(3) 8(4) 7(5) 9(6)

/ \

5(7) 4(8)

3. Remove the new root node (10) and place it just before the previous maximum.

4. Heapify the remaining heap, giving us:

9(0)

/ \

6(1) 1(2)

/ \ / \

3(3) 8(4) 7(5) 4(6)

5(7)

5. Repeat steps 3-4 until the heap is completely empty.

The sorted list is: {0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10}

You might also like