DS-Question Bank UNIT 5 Solution

Download as pdf or txt
Download as pdf or txt
You are on page 1of 53

Department of Computer Science and Engineering

United College of Engineering & Research, Prayagraj Pin -


211010 (India)

Data Structure (BCS-301) Question


Bank

Q
No UNIT 5
.
Explain Binary Tree. Explain Representation of Binary tree. Also explain different operation that can be
1 performed on Binary tree.

Solution:
A binary tree is a data structure in which each node has at most two children, referred to as the
left and right child. It is used to represent hierarchical data and is foundational in various
algorithms, such as searching and sorting.
Representation of Binary Tree in C
In C, a binary tree can be represented using a structure that includes data and pointers to the left
and right children. Here's how you can define a binary tree node:
#include <stdio.h>
#include <stdlib.h>
// Define the structure for a tree node
struct Node
{
int data;
struct Node* left;
struct Node* right;
};
// Function to create a new node
struct Node* createNode(int data)
{
struct Node* newNode = (struct Node*) malloc (sizeof (struct Node));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
Operations on Binary Tree
Here are some common operations that can be performed on a binary tree, along with their
implementations in C:
1. Insertion: Inserting a new node into the binary tree.
For simplicity, here’s how to insert a node in a binary search tree (BST):
struct Node* insert(struct Node* root, int data)
{
if (root == NULL)
{
return createNode(data);
}
if (data < root->data)
{
root->left = insert(root->left, data);
}
else
{
root->right = insert(root->right, data);
}
return root;
}
2.Traversal: Different methods to traverse the tree.
• In-order Traversal (Left, Root, Right):

void inorderTraversal(struct Node* root) {


if (root != NULL)
{
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}

. Pre-order Traversal (Root, Left, Right)

void preorderTraversal(struct Node* root) {


if (root != NULL)
{
printf("%d ", root->data);
preorderTraversal(root->left);
preorderTraversal(root->right);
}
}
. Post-order Traversal (Left, Right, Root):
void postorderTraversal(struct Node* root) {
if (root != NULL)
{
postorderTraversal(root->left);
postorderTraversal(root->right);
printf("%d ", root->data);
}
}
3.Searching: Finding a node in the binary search tree.
struct Node* search(struct Node* root, int key) {
if (root == NULL || root->data == key) {
return root;
}
if (key < root->data) {
return search(root->left, key);
}
else {
return search(root->right, key);
}
}
4.Deletion: Deleting a node from the binary search tree.
struct Node* deleteNode(struct Node* root, int key) {
if (root == NULL)
return root;
if (key < root->data) {
root->left = deleteNode(root->left, key);
}
else if (key > root->data)
{
root->right = deleteNode(root->right, key);
}
else
{
// Node with only one child or no child if (root->left == NULL) { struct Node* temp = root->right;
free(root); return temp;
}
else if (root->right == NULL) {
struct Node* temp = root->left;
free(root);
return temp;
} // Node with two children: Get the inorder successor (smallest in the right subtree) struct Node*
temp = root->right;
while (temp && temp->left != NULL) {
temp = temp->left;
}
root->data = temp->data; // Copy the inorder successor's content to this node
root->right = deleteNode(root->right, temp->data); // Delete the inorder successor
}
return root;
}
5. Calculating Height: Finding the height of the binary tree.
int height(struct Node* root) {
if (root == NULL)
return -1; // Height of empty tree is -1
int leftHeight = height(root->left);
int rightHeight = height(root->right);
return (leftHeight > rightHeight ? leftHeight : rightHeight) + 1;
}

Explain the differences between a binary search tree (BST) and heap. For the given sequence of numbers,
2 construct a heap and a BST: 34, 23, 67, 45, 12, 54, 87, 43, 98, 75, 84, 93, 31
Solution:
Differences Between a Binary Search Tree (BST) and a Heap
Binary Search Tree (BST):
1. Structure: A BST is a binary tree where each node has at most two children, and the left child
contains values less than the parent node, while the right child contains values greater than
the parent node.
2. Ordering: The in-order traversal of a BST results in a sorted sequence of values.
3. Use Cases: BSTs are typically used for dynamic set operations like searching, inserting, and
deleting. They allow efficient searching with average time complexity of O(log n) if balanced.
4. Balance: If the tree becomes unbalanced (e.g., inserting sorted data), it can degrade to a
linked list with O(n) time complexity for search operations.
Heap:
1. Structure: A heap is a complete binary tree that satisfies the heap property: for a max-heap,
every parent node has a value greater than or equal to its children; for a min-heap, every
parent node has a value less than or equal to its children.
2. Ordering: Heaps do not maintain a strict ordering of all elements; only the parent-child
relationship is preserved.
3. Use Cases: Heaps are often used to implement priority queues, where the highest (or lowest)
priority element can be accessed in O(1) time, and insertion and deletion operations can be
done in O(log n) time.
4. Implementation: Heaps are often implemented using arrays, making them efficient in terms
of space.
Construction of a Heap and a BST from the Given Sequence
Given the sequence of numbers: 34, 23, 67, 45, 12, 54, 87, 43, 98, 75, 84, 93, 31.
1. Constructing a Binary Search Tree (BST)
Here’s how to construct a BST in C:

#include <stdio.h>
#include <stdlib.h>

// Define the structure for a BST node


struct BSTNode {
int data;
struct BSTNode* left;
struct BSTNode* right;
};

// Function to create a new BST node


struct BSTNode* createBSTNode(int data) {
struct BSTNode* newNode = (struct BSTNode*)malloc(sizeof(struct BSTNode));
newNode->data = data;
newNode->left = newNode->right = NULL;
return newNode;
}

// Function to insert a node in BST


struct BSTNode* insertBST(struct BSTNode* root, int data) {
if (root == NULL) {
return createBSTNode(data);
}
if (data < root->data) {
root->left = insertBST(root->left, data);
} else {
root->right = insertBST(root->right, data);
}
return root;
}

// Function to do in-order traversal


void inorderBST(struct BSTNode* root) {
if (root != NULL) {
inorderBST(root->left);
printf("%d ", root->data);
inorderBST(root->right);
}
}

int main() {
int values[] = {34, 23, 67, 45, 12, 54, 87, 43, 98, 75, 84, 93, 31};
int n = sizeof(values) / sizeof(values[0]);
struct BSTNode* root = NULL;

// Insert values into the BST


for (int i = 0; i < n; i++) {
root = insertBST(root, values[i]);
}

// In-order traversal
printf("In-order traversal of BST: ");
inorderBST(root);
printf("\n");
return 0;
}
2. Constructing a Max Heap
Here’s how to construct a max heap in C:

#include <stdio.h>
#include <stdlib.h>

// Define the structure for a heap


struct MaxHeap {
int* array;
int capacity;
int size;
};

// Function to create a new max heap


struct MaxHeap* createMaxHeap(int capacity) {
struct MaxHeap* heap = (struct MaxHeap*)malloc(sizeof(struct MaxHeap));
heap->capacity = capacity;
heap->size = 0;
heap->array = (int*)malloc(capacity * sizeof(int));
return heap;
}

// Function to swap two values


void swap(int* a, int* b) {
int temp = *a;
*a = *b;
*b = temp;
}

// Function to heapify down


void heapifyDown(struct MaxHeap* heap, int index) {
int largest = index;
int left = 2 * index + 1;
int right = 2 * index + 2;

if (left < heap->size && heap->array[left] > heap->array[largest]) {


largest = left;
}
if (right < heap->size && heap->array[right] > heap->array[largest]) {
largest = right;
}
if (largest != index) {
swap(&heap->array[index], &heap->array[largest]);
heapifyDown(heap, largest);
}
}

// Function to insert a value into the max heap


void insertMaxHeap(struct MaxHeap* heap, int value) {
if (heap->size == heap->capacity) {
printf("Heap is full!\n");
return;
}
heap->array[heap->size] = value;
heap->size++;

// Heapify up
for (int i = heap->size / 2 - 1; i >= 0; i--) {
heapifyDown(heap, i);
}
}

// Function to print the heap


void printHeap(struct MaxHeap* heap) {
for (int i = 0; i < heap->size; i++) {
printf("%d ", heap->array[i]);
}
printf("\n");
}

int main() {
int values[] = {34, 23, 67, 45, 12, 54, 87, 43, 98, 75, 84, 93, 31};
int n = sizeof(values) / sizeof(values[0]);

// Create a max heap


struct MaxHeap* heap = createMaxHeap(n);

// Insert values into the heap


for (int i = 0; i < n; i++) {
insertMaxHeap(heap, values[i]);
}

// Print the heap


printf("Max Heap: ");
printHeap(heap);

// Clean up
free(heap->array);
free(heap);

return 0;
}
Explanation of the Code
1. BST Code:
o Defines a structure for a BST node.
o Implements functions to create nodes, insert into the BST, and perform in-order
traversal.
o In the main function, it constructs the BST from the provided values and prints the in-
order traversal.
2. Max Heap Code:
o Defines a structure for a max heap.
o Implements functions to create the heap, insert values, and heapify.
o In the main function, it constructs the max heap from the provided values and prints
the heap.

3 Can you find a unique tree when any two traversals are given? Using the following traversals construct the
corresponding binary tree:
INORDER: H K D B I L E A F C M J G
PREORDER: A B D H K E I L C F G J M
Also find the Post Order traversal of obtained tree.
Solution:
To construct a binary tree from given traversals, we can use the properties of the preorder and
inorder traversals.
Given Traversals
• Inorder: H K D B I L E A F C M J G
• Preorder: A B D H K E I L C F G J M
Steps to Construct the Binary Tree
1. Identify the Root: The first element of the preorder traversal is always the root of the tree.
In this case, A is the root.
2. Split Inorder Traversal: Find the index of A in the inorder traversal. This index will help us
split the inorder list into left and right subtrees:
o Left Subtree: Elements before A in inorder
o Right Subtree: Elements after A in inorder
Inorder split:
o Left Subtree: H K D B I L E
o Right Subtree: F C M J G
3. Recursive Construction: We recursively apply the same process for the left and right subtrees
using the remaining elements in the preorder list:
o The next elements in preorder will correspond to the left and right subtrees in the
same manner.
Construction Code
Here’s a C implementation to construct the binary tree and perform postorder traversal:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Node {
char data;
struct Node* left;
struct Node* right;
};

// Function to create a new node


struct Node* createNode(char data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Function to find the index of a value in an array


int findIndex(char* arr, int start, int end, char value) {
for (int i = start; i <= end; i++) {
if (arr[i] == value) {
return i;
}
}
return -1; // Not found
}

// Recursive function to construct the binary tree


struct Node* buildTree(char* inorder, char* preorder, int inorderStart, int inorderEnd, int*
preorderIndex) {
if (inorderStart > inorderEnd) {
return NULL;
}

// Get the root from the preorder traversal


char rootValue = preorder[*preorderIndex];
(*preorderIndex)++;

// Create the root node


struct Node* root = createNode(rootValue);
// Find the root index in inorder traversal
int rootIndex = findIndex(inorder, inorderStart, inorderEnd, rootValue);

// Build the left and right subtrees


root->left = buildTree(inorder, preorder, inorderStart, rootIndex - 1, preorderIndex);
root->right = buildTree(inorder, preorder, rootIndex + 1, inorderEnd, preorderIndex);

return root;
}

// Postorder traversal (Left, Right, Root)


void postOrderTraversal(struct Node* root) {
if (root != NULL) {
postOrderTraversal(root->left);
postOrderTraversal(root->right);
printf("%c ", root->data);
}
}

int main() {
char inorder[] = "HKDBILEAFCMJG";
char preorder[] = "ABDHKELCFGJM";
int preorderIndex = 0;

// Construct the binary tree


struct Node* root = buildTree(inorder, preorder, 0, strlen(inorder) - 1, &preorderIndex);

// Perform postorder traversal


printf("Postorder traversal: ");
postOrderTraversal(root);
printf("\n");

return 0;
}
Explanation of the Code
1. Node Structure: A simple structure to represent a node in the binary tree.
2. Create Node: A function to allocate memory and initialize a new node.
3. Find Index: A function to find the index of a given value in the inorder array.
4. Build Tree: A recursive function that constructs the binary tree using the inorder and
preorder traversals. It finds the root, splits the inorder traversal for left and right subtrees,
and recursively builds the tree.
5. Postorder Traversal: A function to print the postorder traversal of the tree (left, right, root).
6. Main Function: Initializes the traversals, constructs the tree, and prints the postorder
traversal.
Output
When you run the above code, you will get the postorder traversal of the constructed binary tree:
mathematica

Postorder traversal: H K D E I L B F G J M C A
Explain B-Tree. Construct a B-Tree of order 4 with the alphabets (letters) arrive in the sequence as
follows: a g f b k d h m j e s i r x c l n t u p
4
Solution:

A B-Tree is a self-balancing tree data structure that maintains sorted data and allows for efficient
insertion, deletion, and search operations. It is designed to work well on disk storage systems and
databases, allowing for high levels of concurrency and minimal disk reads. Key characteristics of B-
Trees include:
1. Order: A B-Tree of order m can have a maximum of m - 1 keys and m children. Each node
(except the root) must contain at least ⌈m/2⌉ - 1 keys.
2. Sorted Keys: Keys in each node are kept in sorted order.
3. Balanced: All leaf nodes are at the same depth, ensuring that the tree remains balanced.
4. Multi-way: Unlike binary trees, B-Trees can have multiple children, allowing them to store
more keys in a single node.
Constructing a B-Tree of Order 4
Order 4 means each node can have at most 3 keys and 4 children. Let's construct a B-Tree using the
sequence of letters provided:
a, g, f, b, k, d, h, m, j, e, s, i, r, x, c, l, n, t, u, p.

Step-by-Step Insertion Process


1. Insert 'a':
[a]
2. Insert 'g':
[a g]
3. Insert 'f':
[a f g]
4. Insert 'b':
[a b f g]
5. Insert 'k': (Node overflows)
o Split node: b becomes the new root, and we push up f.
[b]
/ \
[a] [f g k]
6. Insert 'd':
[b]
/ \
[a d] [f g k]
7. Insert 'h':
[b]
/ \
[a d] [f g h k]
8. Insert 'm':
[b]
/ \
[a d] [f g h k m]
9. Insert 'j': (Node overflows)
o Split node: h pushed up.
[b h]
/ | \
[a d] [f g] [k m]
10. Insert 'e':
[b h]
/ | \
[a d e] [f g] [k m]

11. **Insert 's'**:


```
[b h]
/ | \
[a d e] [f g] [k m s]
12. Insert 'i':
[b h]
/ | \
[a d e] [f g i] [k m s]

13. **Insert 'r'**: (Node overflows)


- Split node: `g` pushed up.
```
[b g h]
/ | | \
[a d e] [f] [i r] [k m s]
14. Insert 'x':
[b g h]
/ | | \
[a d e] [f] [i r] [k m s x]

15. **Insert 'c'**:


```
[b g h]
/ | | \
[a c d e] [f] [i r] [k m s x]
16. Insert 'l':
[b g h]
/ | | \
[a c d e] [f] [i r l] [k m s x]

17. **Insert 'n'**:


```
[b g h]
/ | | \
[a c d e] [f] [i l n r] [k m s x]
18. Insert 't': (Node overflows)
o Split node: l pushed up.
[b g h l]
/ | | \
[a c d e] [f] [i r] [k m n s t x]

19. **Insert 'u'**:


```
[b g h l]
/ | | \
[a c d e] [f] [i r] [k m n s t u x]
20. Insert 'p':
[b g h l]
/ | | \
[a c d e] [f] [i r] [k m n p s t u x]

### Final B-Tree Structure

The B-Tree after all insertions looks like this:

[g]
/ \
[b h] [l]
/ | | \
[a c d e] [f] [i r] [k m n p s t u x]
5 Construct a tree for the given inorder and postorder traversals.
Inorder : D G B A H E I C F
Postorder : G D B H I E F C A

Solutions:
To construct a binary tree from given inorder and postorder traversals, we can follow a systematic
approach:
Given Traversals
• Inorder: D G B A H E I C F
• Postorder: G D B H I E F C A
Steps to Construct the Tree
1. Identify the Root: The last element of the postorder traversal is always the root of the tree. In
this case, the root is A.
2. Split Inorder Traversal: Find the index of the root (A) in the inorder traversal. This index will
help us split the inorder list into left and right subtrees:
o Left Subtree: Elements before A in inorder
o Right Subtree: Elements after A in inorder
For our case:
o Left Subtree (Inorder): D G B
o Right Subtree (Inorder): H E I C F
3. Recursive Construction: We recursively apply the same process for the left and right subtrees
using the remaining elements in the postorder list:
o The postorder elements corresponding to the left subtree will come from the postorder
list before the root.
Step-by-Step Construction
1. Identify the Root
• Root = A
2. Split Inorder and Postorder
• Inorder: D G B (left of A), H E I C F (right of A)
• Postorder: Last element is A, so remaining for left and right subtrees.
Left Subtree
• Postorder for Left Subtree: G D B
• Inorder for Left Subtree: D G B
• Root for left subtree is B.
• Inorder Split:
o Left: D
o Right: G
• Postorder for Left Subtree:
o Left: D
o Right: G
Right Subtree
• Postorder for Right Subtree: H I E F C
• Inorder for Right Subtree: H E I C F
• Root for right subtree is C.
• Inorder Split:
o Left: H E I
o Right: F
• Postorder for Right Subtree:
o Left: H I E
o Right: F
Recursively Constructing the Tree
1. For Left Subtree of A (Root B):
o Left of B: Root D (No children)
o Right of B: Root G (No children)
2. For Right Subtree of A (Root C):
o Left of C: Root E
▪ Left of E: Root H (No children)
▪ Right of E: Root I (No children)
o Right of C: Root F (No children)
Final Tree Structure
Now, let's put it all together. The final binary tree can be represented as:
mathematica
Copy code
A
/\
B C
/\ \
D G F
/
E
/\
H I
Implementation in C
Here's how you might implement this in C:

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

struct Node {
char data;
struct Node* left;
struct Node* right;
};

// Function to create a new node


struct Node* createNode(char data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}

// Function to find the index of a value in an array


int findIndex(char* arr, int start, int end, char value) {
for (int i = start; i <= end; i++) {
if (arr[i] == value) {
return i;
}
}
return -1; // Not found
}

// Recursive function to construct the binary tree


struct Node* buildTree(char* inorder, char* postorder, int inorderStart, int inorderEnd, int*
postIndex) {
if (inorderStart > inorderEnd) {
return NULL;
}

// Get the root from the postorder traversal


char rootValue = postorder[*postIndex];
(*postIndex)--;

// Create the root node


struct Node* root = createNode(rootValue);
// Find the root index in inorder traversal
int rootIndex = findIndex(inorder, inorderStart, inorderEnd, rootValue);

// Build the right and left subtrees (right first because we are using postorder)
root->right = buildTree(inorder, postorder, rootIndex + 1, inorderEnd, postIndex);
root->left = buildTree(inorder, postorder, inorderStart, rootIndex - 1, postIndex);

return root;
}

// Function to perform inorder traversal


void inorderTraversal(struct Node* root) {
if (root != NULL) {
inorderTraversal(root->left);
printf("%c ", root->data);
inorderTraversal(root->right);
}
}

int main() {
char inorder[] = "DGBAEIHCF";
char postorder[] = "GDBHIEFC A";
int postIndex = strlen(postorder) - 1;

// Construct the binary tree


struct Node* root = buildTree(inorder, postorder, 0, strlen(inorder) - 1, &postIndex);

// Perform inorder traversal


printf("Inorder traversal of constructed tree: ");
inorderTraversal(root);
printf("\n");

return 0;
}
6 Discuss following with reference to trees.
i Height of the tree
ii Complete Binary Tree
iii Extended Binary tree
iv Sibling
v Full Binary Tree
vi Complete Binary Tree

Solutions
(i) Height of the Tree
Definition: The height of a tree is defined as the length of the longest path from the root node to a
leaf node. It can be thought of as the number of edges on the longest downward path between the
root and a leaf.
Properties:
• For an empty tree, the height is -1.
• For a tree with only one node (the root), the height is 0.
• The height of a binary tree with n nodes can be at most n-1 (in case of a skewed tree) and at
least ⌊log₂(n)⌋ (in case of a balanced tree).
(ii) Complete Binary Tree
Definition: A complete binary tree is a type of binary tree in which every level, except possibly the
last, is completely filled, and all nodes are as far left as possible.
Properties:
• The last level may not be completely filled, but all nodes must be filled from left to right.
• A complete binary tree with h height has between 2^h and 2^(h+1) - 1 nodes.
• It can be efficiently represented using an array, where for any node at index i, the left child is
at 2i + 1, and the right child is at 2i + 2.
(iii) Extended Binary Tree
Definition: An extended binary tree is a binary tree that is modified by adding null links to represent
absent children, effectively making every node have either 0 or 2 children.
Properties:
• In an extended binary tree, each node either has two children or is a leaf node (having no
children).
• This representation can help in visualizing recursive algorithms and simplifies some operations
(like traversal).
(iv) Sibling
Definition: In the context of trees, siblings are nodes that share the same parent.
Properties:
• Sibling nodes can be at the same level and typically belong to the same subtree.
• Understanding sibling relationships is important for tree traversal algorithms and operations
such as balancing or restructuring the tree.
(v) Full Binary Tree
Definition: A full binary tree (also known as a proper or strict binary tree) is a type of binary tree in
which every node other than the leaves has exactly two children.
Properties:
• In a full binary tree, the number of leaf nodes is always one more than the number of internal
nodes.
• If a full binary tree has n internal nodes, then it has n + 1 leaf nodes.
• The height of a full binary tree with n internal nodes is log₂(n + 1).
(vi) Complete Binary Tree (repeated)
Definition: As previously defined, a complete binary tree is one in which all levels are fully filled
except possibly the last, which is filled from left to right.
Properties:
• It is distinct from a full binary tree, as a complete binary tree does not require all nodes to
have two children.
• Complete binary trees are often used in implementing heaps (binary heaps), as their structure
allows for efficient operations like insertion and deletion.
7. Write a short note on Threaded binary tree. Explain the significance of maintaining threads in Binary Search
Tree. Write an algorithm to insert a node in thread binary tree.
Solutions
Threaded Binary Tree
A threaded binary tree is a type of binary tree that makes use of the null pointers in the tree nodes
to point to their in-order predecessor and successor. This effectively creates a structure that allows
for efficient in-order traversal without the need for a stack or recursion.
Characteristics
• Threading: In a threaded binary tree, a null pointer in a node indicates that the node has a
predecessor or successor. If a node's left pointer is null, it points to its in-order predecessor; if
the right pointer is null, it points to its in-order successor.
• In-order Traversal: This structure allows for an easy in-order traversal. Instead of using a
stack or recursion, you can follow the threads to visit nodes in order.
• Storage Efficiency: By utilizing null pointers for threading, no additional space is needed for
storing references to predecessors and successors.
Significance of Maintaining Threads in Binary Search Trees (BST)
1. Efficient Traversal: Maintaining threads allows for in-order traversal of the tree in a linear
fashion without the overhead of a stack or recursive function calls.
2. Improved Performance: This can lead to improved performance in scenarios where frequent
traversals are required, such as in applications involving sorted data retrieval.
3. Reduced Memory Usage: It minimizes the memory footprint compared to storing additional
pointers or using stack-based traversal techniques.
Algorithm to Insert a Node in a Threaded Binary Tree
Here's a simple algorithm to insert a new node into a threaded binary tree:
1. Start at the root of the tree.
2. Use a loop to find the appropriate location for the new node based on binary search tree
properties (i.e., left children are less than the parent node, right children are greater).
3. When a null pointer is found, insert the new node:
o If it's the left null pointer, set it to point to the new node and adjust the new node's
right pointer to point to the current node (the successor).
o If it's the right null pointer, set it to point to the new node and adjust the new node's
left pointer to point to the current node (the predecessor).
4. Update the threaded links accordingly.
C Implementation of Node Insertion in a Threaded Binary Tree
Here's a simple implementation in C to demonstrate inserting a node in a threaded binary tree:

#include <stdio.h>
#include <stdlib.h>

struct ThreadedNode {
int data;
struct ThreadedNode *left, *right;
int leftThread, rightThread; // 1 if thread, 0 if child
};

// Function to create a new node


struct ThreadedNode* createNode(int data) {
struct ThreadedNode* newNode = (struct ThreadedNode*)malloc(sizeof(struct ThreadedNode));
newNode->data = data;
newNode->left = newNode->right = NULL;
newNode->leftThread = newNode->rightThread = 0;
return newNode;
}

// Function to insert a new node in a threaded binary tree


void insertNode(struct ThreadedNode** root, int data) {
struct ThreadedNode* newNode = createNode(data);
if (*root == NULL) {
*root = newNode;
return;
}

struct ThreadedNode* current = *root;


struct ThreadedNode* parent = NULL;

// Find the position to insert the new node


while (current != NULL) {
parent = current;
if (data < current->data) {
if (current->leftThread == 0) {
current = current->left; // Move to left child
} else {
break; // Stop if left thread
}
} else {
if (current->rightThread == 0) {
current = current->right; // Move to right child
} else {
break; // Stop if right thread
}
}
}

// Insert the new node


if (data < parent->data) {
newNode->left = parent->left;
newNode->right = parent;
parent->left = newNode;
parent->leftThread = 1;

if (newNode->left != NULL) {
// Update predecessor's right thread
struct ThreadedNode* predecessor = newNode->left;
predecessor->right = newNode;
}
} else {
newNode->right = parent->right;
newNode->left = parent;
parent->right = newNode;
parent->rightThread = 1;

if (newNode->right != NULL) {
// Update successor's left thread
struct ThreadedNode* successor = newNode->right;
successor->left = newNode;
}
}
}

// In-order traversal of threaded binary tree


void inOrderTraversal(struct ThreadedNode* root) {
if (root == NULL) return;

// Find the leftmost node


struct ThreadedNode* current = root;
while (current->leftThread == 0) {
current = current->left;
}

// Traverse the threaded binary tree


while (current != NULL) {
printf("%d ", current->data);
if (current->rightThread == 1) {
current = current->right; // Move to thread
} else {
current = current->right;
while (current != NULL && current->leftThread == 0) {
current = current->left; // Move to leftmost child
}
}
}
}

// Main function
int main() {
struct ThreadedNode* root = NULL;

insertNode(&root, 10);
insertNode(&root, 5);
insertNode(&root, 20);
insertNode(&root, 3);
insertNode(&root, 7);
insertNode(&root, 15);
insertNode(&root, 30);

printf("In-order traversal of the threaded binary tree: ");


inOrderTraversal(root);
printf("\n");

return 0;
}
8 Create a Binary Search Tree for the following data and do in-order, Preorder and Post-order traversal of
the tree: 50, 60, 25, 40, 30, 70, 35, 10, 55, 65, 5
Solution
To create a Binary Search Tree (BST) from the given data, we will insert the numbers one by one
while maintaining the properties of the BST. The numbers to be inserted are:
50, 60, 25, 40, 30, 70, 35, 10, 55, 65, 5
Step-by-Step Construction of the BST
1. Insert 50: This becomes the root.

50
2. Insert 60: This goes to the right of 50.

50
\
60
3. Insert 25: This goes to the left of 50.

50
/ \
25 60
4. Insert 40: This goes to the right of 25.

50
/ \
25 60
\
40
5. Insert 30: This goes to the left of 40.

50
/ \
25 60
\
40
/
30
6. Insert 70: This goes to the right of 60.

50
/ \
25 60
\ \
40 70
/
30
7. Insert 35: This goes to the right of 30.

50
/ \
25 60
\ \
40 70
/
30
\
35
8. Insert 10: This goes to the left of 25.

50
/ \
25 60
/ \ \
10 40 70 / 30
35

9. **Insert 55**: This goes to the left of 60.

50
/ \
25 60
/\/
10 40 55 70 / 30
35

10. **Insert 65**: This goes to the left of 70.


```
50
/ \
25 60
/ \ / \
10 40 55 70
/ /
30 65
\
35
11. Insert 5: This goes to the left of 10.

50
/ \
25 60
/\/
10 40 55 70 / / / 5 30 65
35

### Final Binary Search Tree Structure

50
/ \
25 60
/\/
10 40 55 70 / / / 5 30 65
35
markdown
Copy code

### Traversals
Now, we will perform in-order, pre-order, and post-order traversals of the constructed BST.

1. **In-Order Traversal (Left, Root, Right)**:


- Visit left subtree, root, then right subtree.
- Result: `5, 10, 25, 30, 35, 40, 50, 55, 60, 65, 70`

2. **Pre-Order Traversal (Root, Left, Right)**:


- Visit root, then left subtree, then right subtree.
- Result: `50, 25, 10, 5, 40, 30, 35, 60, 55, 70, 65`

3. **Post-Order Traversal (Left, Right, Root)**:


- Visit left subtree, then right subtree, then root.
- Result: `5, 10, 35, 30, 40, 25, 55, 65, 70, 60, 50`

### Summary of Traversals

- **In-Order**: `5, 10, 25, 30, 35, 40, 50, 55, 60, 65, 70`
- **Pre-Order**: `50, 25, 10, 5, 40, 30, 35, 60, 55, 70, 65`
- **Post-Order**: `5, 10, 35, 30, 40, 25, 55, 65, 70, 60, 50`
9 Write a C function for non-recursive post order traversal of a binary tree.
Solution.
To perform a non-recursive post-order traversal of a binary tree, we can use two stacks or a single
stack along with a pointer to keep track of the last visited node. Here, I’ll show you how to do it
using a single stack.
C Function for Non-Recursive Post-Order Traversal
Here's a complete C function to perform non-recursive post-order traversal:

#include <stdio.h>
#include <stdlib.h>

// Definition of a binary tree node


struct Node {
int data;
struct Node* left;
struct Node* right;
};

// Function to create a new node


struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->left = newNode->right = NULL;
return newNode;
}

// Function for non-recursive post-order traversal


void postOrderTraversal(struct Node* root) {
if (root == NULL) {
return;
}

struct Node* stack[100]; // Stack for nodes


int top = -1; // Initialize stack pointer
struct Node* lastVisited = NULL; // To track the last visited node

while (top != -1 || root != NULL) {


// Traverse to the leftmost node
while (root != NULL) {
stack[++top] = root; // Push root to stack
root = root->left; // Move to left child
}

// Peek the node at the top of the stack


struct Node* peekNode = stack[top];

// If the right child is NULL or already visited


if (peekNode->right == NULL || peekNode->right == lastVisited) {
// Visit the node
printf("%d ", peekNode->data);
lastVisited = stack[top--]; // Pop the node and mark it as visited
} else {
// If right child exists, go to right child
root = peekNode->right;
}
}
}
// Example usage
int main() {
struct Node* root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);

printf("Post-order traversal: ");


postOrderTraversal(root);
printf("\n");

return 0;
}
Explanation of the Code
1. Node Structure: The Node structure defines a binary tree node with data and pointers to the
left and right children.
2. Create Node Function: createNode allocates memory for a new node and initializes its data
and pointers.
3. Post-Order Traversal Function:
o We use a stack to keep track of nodes.
o The outer loop continues until both the stack is empty and the root is NULL.
o The inner loop traverses down the leftmost path, pushing each node onto the stack.
o After reaching the leftmost node, we check the top of the stack:
▪ If the right child of the node at the top of the stack is NULL or already visited,
we visit that node (print its data) and pop it from the stack.
▪ If there is a right child that has not been visited, we set root to that right child
and continue.
4. Main Function: An example tree is created, and the post-order traversal is called.
Example Output
For the tree constructed in the example, the output will be:
Post-order traversal: 4 5 2 3 1
10 Define an AVL tree. Obtain an AVL tree by inserting one integer at a time in the following sequence. 150,
155, 160, 115, 110, 140, 120, 145, 130, 147, 170, 180. Show all the steps.

Solution

Definition of an AVL Tree


An AVL tree is a self-balancing binary search tree (BST) where the difference in heights between the
left and right subtrees (known as the balance factor) of any node is at most 1. This property ensures
that the tree remains balanced, allowing for efficient search, insert, and delete operations with a
time complexity of O(log⁡n)O(\log n)O(logn).
AVL Tree Insertion Steps
We'll insert the integers from the sequence 150, 155, 160, 115, 110, 140, 120, 145, 130, 147, 170,
180 one by one into the AVL tree. After each insertion, we will check and maintain the balance of
the tree by performing rotations if necessary.
Insertion Steps
1. Insert 150

150
2. Insert 155
150
\
155
3. Insert 160
o Inserting 160 causes the tree to become unbalanced.

150
\
155
\
160
o Balance Factor: Node 150 has a balance factor of -2 (right-heavy).
o Rotation: Left Rotation on 150.
155
/ \
150 160
4. Insert 115
155
/ \
150 160
/
115

5. **Insert 110**

155
/ \
150 160
/ 115 / 110

- **Balance Factor**: Node `150` has a balance factor of `2` (left-heavy).


- **Rotation**: Right Rotation on `150`.

155
/ \
115 160
/
110 150
6. **Insert 140**

155
/ \
115 160
/
110 150 / 140

7. **Insert 120**

155
/ \
115 160
/
110 150 /
140 120

- **Balance Factor**: Node `115` has a balance factor of `1`, Node `150` has a balance factor of
`1`, and Node `155` remains balanced.

8. **Insert 145**

155
/ \
115 160
/
110 150 /
140 120
145

9. **Insert 130**

155
/ \
115 160
/
110 150 /
140 120
145 / 130

- **Balance Factor**: Node `120` has a balance factor of `1`, Node `150` has a balance factor of
`1`, but Node `155` has a balance factor of `2` (right-heavy).
- **Rotation**: Right rotation on `150`, then left rotation on `155`.

140
/ \
115 155
/\/
110 120 150 160
145 / 130

10. **Insert 147**

140
/ \
115 155
/\/
110 120 150 160
145 / 130
147

- **Balance Factor**: Still balanced.

11. **Insert 170**

140
/ \
115 155
/\/
110 120 150 160
145 / 130
147
170

12. **Insert 180**

140
/ \
115 155
/\/
110 120 150 160
145 / 130
147
170
180

- **Balance Factor**: Node `160` has a balance factor of `-2`.


- **Rotation**: Left rotation on `160`.

140
/ \
115 155
/\/
110 120 150 170 /
160 180 / 145 / 130
147

### Final AVL Tree

After inserting all the integers, the final AVL tree looks like this:

140
/ \
115 155
/\/
110 120 150 170 /
160 180 / 145 / 130
147
11 Suppose character a, b, c, d, e, f has probabilities 0.07, 0.09, 0.12, 0.22, 0.23, 0.27 respectively. Find
an optional Huffman code and draw the Huffman tree. Also find the average code length.

Solution.
to find an optimal Huffman code for the given set of characters and their probabilities, we need to
follow these steps:
1. List the characters and their probabilities:
• Character a: 0.07
• Character b: 0.09
• Character c: 0.12
• Character d: 0.22
• Character e: 0.23
• Character f: 0.27
2. Create a Priority Queue (Min-Heap) to start building the Huffman tree:
The priority queue stores characters based on their probabilities, starting with the smallest.
Step 1: Insert each character with its corresponding probability into the priority queue:
• a: 0.07
• b: 0.09
• c: 0.12
• d: 0.22
• e: 0.23
• f: 0.27
Step 2: Merge the two nodes with the lowest probabilities.
• Merge a (0.07) and b (0.09) → new node with probability 0.16.
o New heap: [(0.12, c), (0.16, ab), (0.22, d), (0.23, e), (0.27, f)]
Step 3: Merge the next two lowest probabilities.
• Merge c (0.12) and ab (0.16) → new node with probability 0.28.
o New heap: [(0.22, d), (0.23, e), (0.27, f), (0.28, abc)]
Step 4: Merge the next two lowest probabilities.
• Merge d (0.22) and e (0.23) → new node with probability 0.45.
o New heap: [(0.27, f), (0.28, abc), (0.45, de)]
Step 5: Merge the next two lowest probabilities.
• Merge f (0.27) and abc (0.28) → new node with probability 0.55.
o New heap: [(0.45, de), (0.55, fabc)]
Step 6: Merge the remaining two nodes.
• Merge de (0.45) and fabc (0.55) → new node with probability 1.00 (root of the tree).
o New heap: [(1.00, root)]
At this point, the heap has only one node, which means we've finished constructing the Huffman
tree.
3. Huffman Tree Structure:
• Root (1.00)
o Left child: de (0.45)
▪ Left child: d (0.22)
▪ Right child: e (0.23)
o Right child: fabc (0.55)
▪ Left child: f (0.27)
▪ Right child: abc (0.28)
▪ Left child: a (0.07)
▪ Right child: b (0.09)
▪ Right child: c (0.12)
4. Assigning Huffman Codes:
Starting from the root, assign a "0" for left branches and a "1" for right branches:
• f: 01
• a: 100
• b: 101
• c: 11
• d: 00
• e: 01
5. Average Code Length Calculation:
The average code length is the weighted average length of the Huffman codes, calculated as follows:
Average Code Length=∑(Probability of Character×Length of Huffman Code for Character)

• L(a)=3L(a) = 3L(a)=3 bits, probability = 0.07 → 0.07×3=0.21


• L(b)=3L(b) = 3L(b)=3 bits, probability = 0.09 → 0.09×3=0.27
• L(c)=2L(c) = 2L(c)=2 bits, probability = 0.12 → 0.12×2=0.24
• L(d)=2L(d) = 2L(d)=2 bits, probability = 0.22 → 0.22×2=0.44
• L(e)=2L(e) = 2L(e)=2 bits, probability = 0.23 → 0.23×2=0.46
• L(f)=2L(f) = 2L(f)=2 bits, probability = 0.27 → 0.27×2=0.54
Now, summing all the products:
Average Code Length=0.21+0.27+0.24+0.44+0.46+0.54=2.16 bits
Final Answer:
• Huffman codes:
o a: 100
o b: 101
o c: 11
o d: 00
o e: 01
o f: 10
• Average Code Length: 2.16 bits
12 If E and I denotes the external and internal path length of a binary tree having n internal nodes then show
that E=I+2n.
Solution

To show that the external path length (E) of a binary tree is related to the internal path length (I)
by the formula:
E=I+2n
where n is the number of internal nodes, we need to carefully define and understand the concepts of
internal and external path lengths in the context of binary trees.
Definitions:
1. Internal Path Length (I): The sum of the depths of all internal nodes in the binary tree. The
depth of a node is defined as the number of edges on the path from the root to the node.
o If a node is at depth d, then the contribution of that node to the internal path length is
d.
2. External Path Length (E): The sum of the depths of all external nodes (or leaf nodes) in the
binary tree. External nodes are the nodes that would be reached if we kept following left or
right child pointers in an empty subtree (i.e., the "holes" or "ends" of the tree).
o The contribution of each external node is its depth in the tree.
3. Number of Internal Nodes (n): This is the total number of nodes in the tree that are not
external (leaf) nodes.
Proof:
Step 1: Relation between External and Internal Nodes
In a binary tree, each internal node has exactly 2 children (since it's a full binary tree). Thus, for
every internal node, there are two child nodes, which are either internal or external nodes. The
number of external nodes m in a binary tree with n internal nodes can be shown to be:
m=n+1
This result follows from the fact that in a full binary tree, the number of external nodes is always
one more than the number of internal nodes. This is a key property of binary trees.
Step 2: Total Path Length Calculation
Let’s now compute the total path length (the sum of the depths of all nodes) in the binary tree. This
includes both the internal path length I and the external path length E.

The total path length is the sum of the depths of all internal and external nodes:
Total Path Length=I+E
However, there's another important property of binary trees that we can use. If the tree has nnn
internal nodes, then the total number of nodes in the tree is n+m=2n+1 (internal plus external
nodes). The depth of each node is counted in terms of the number of edges from the root to that
node.
Step 3: The Depth of Internal and External Nodes
Consider the following observations:
• The total path length for internal nodes I can be computed as the sum of their depths.
• The total path length for external nodes E can similarly be computed as the sum of their
depths.
There’s a relationship between the total depth of internal nodes and the total depth of external
nodes. Specifically, when we look at the total depth of all nodes in the tree (internal and external),
it can be shown that:

Total Path Length of all nodes=2n+ (height of the tree)


Using this total path length, we can derive the relationship between I and E.
Step 4: Use the Result m=n+1
Since there are n+1 external nodes, and we already know that the total path length of all nodes is
related to the internal path length and external path length by the formula:
E=I+2n
This gives the desired result.
13 Explain why does the time complexity of search operation in B-Tree is better than Binary Search Tree (BST).
Insert the following keys into an initially empty B-tree of order 5: a, g, f, b, k, d, h, m, j, e, s, i, r, x, c, l, n,
t, u, p.
Find the resultant B-Tree after deleting keys j, t and d in sequence.

Solution

In a Binary Search Tree (BST), the search operation works by comparing the key at the current node
with the desired key, and recursively searching either the left or the right subtree based on the
comparison. The time complexity for searching in a BST is:
• O(h), where h is the height of the tree.
• In the worst case, the tree could be highly unbalanced, where the height is O(n), and thus the
search time could be O(n), which is inefficient.
In contrast, a B-tree is a self-balancing tree data structure that ensures balanced height by
maintaining nodes with multiple keys and children. The search operation in a B-tree works by
traversing the tree from the root to the appropriate leaf node, following the child pointers.
• B-tree height is much smaller compared to a BST because each node can have many children
(up to the order of the tree). The height of a B-tree is O(log_d n), where d is the order of the
tree, and n is the number of keys stored in the tree.
o This means that the number of levels you need to traverse in a B-tree is much smaller
than in a binary tree.
Thus, a B-tree can offer better search time in comparison to a BST because:
1. The height of a B-tree grows logarithmically, while a BST can degrade to linear height.
2. In each node of a B-tree, you perform a binary search over multiple keys, reducing the
number of node accesses.
B-Tree Insertion and Deletion
Now, let’s proceed with the insertion of the keys into a B-tree of order 5 and then delete the keys j,
t, and d.
Definitions:
• B-tree of order 5: This means each node can have at most 4 keys and at most 5 children
(since the order is 5).
• The tree must remain balanced, meaning all leaf nodes must be at the same level.
Insert Keys into B-tree of Order 5
We are given the keys: a, g, f, b, k, d, h, m, j, e, s, i, r, x, c, l, n, t, u, p.
Let’s go through the insertion steps one by one:
Step-by-Step Insertion:
1. Insert a:
The tree is empty. Insert a into the root.
Tree: [a]
2. Insert g:
Insert g into the root, which already has one key.
Tree: [a, g]
3. Insert f:
Insert f. The keys in the root node are now [a, f, g]. The tree is still valid.
Tree: [a, f, g]
4. Insert b:
Insert b. The keys in the root are now [a, b, f, g]. The tree is still valid.
Tree: [a, b, f, g]
5. Insert k:
The root has 4 keys. We need to split it. Split the node into two:
o Middle key: f (this will become the new root).
o Left child: [a, b].
o Right child: [g, k].
Tree:
[f]
/ \
[a, b] [g, k]
6. **Insert d**:
Insert **d** into the left child **[a, b]**.
Tree:
[f]
/ \
[a, b, d] [g, k]

7. **Insert h**:
Insert **h** into the right child **[g, k]**.
Tree:
[f]
/ \
[a, b, d] [g, h, k]

8. **Insert m**:
Insert **m** into the right child **[g, h, k]**. The node now has 4 keys, so we split it:
- **Middle key**: **h** (this will be pushed up into the root).
- Left child: **[g]**.
- Right child: **[k, m]**.
Tree:
[f, h]
/ | \
[a, b, d] [g] [k, m]

9. **Insert j**:
Insert **j** into the right child **[k, m]**.
Tree:
[f, h]
/ | \
[a, b, d] [g] [j, k, m]
less

10. **Insert e**:


Insert **e** into the left child **[a, b, d]**.
Tree:
```
[f, h]
/ | \
[a, b, d, e] [g] [j, k, m]
```

11. **Insert s**:


Insert **s** into the right child **[j, k, m]**. The node now has 4 keys, so we split it:
- **Middle key**: **k** (this will be pushed up into the root).
- Left child: **[j]**.
- Right child: **[m, s]**.
Tree:
```
[f, h, k]
/ | | \
[a, b, d, e] [g] [j] [m, s]
```

12. **Insert i**:


Insert **i** into the right child **[j]**.
Tree:
```
[f, h, k]
/ | | \
[a, b, d, e] [g] [i, j] [m, s]
```

13. **Insert r**:


Insert **r** into the right child **[m, s]**.
Tree:
```
[f, h, k]
/ | | \
[a, b, d, e] [g] [i, j] [m, r, s]
```

14. **Insert x**:


Insert **x** into the right child **[m, r, s]**. The node now has 4 keys, so we split it:
- **Middle key**: **r** (this will be pushed up into the root).
- Left child: **[m]**.
- Right child: **[s, x]**.
Tree:
```
[f, h, k, r]
/ | | | \
[a, b, d, e] [g] [i, j] [m] [s, x]
```

15. **Insert c**:


Insert **c** into the left child **[a, b, d, e]**.
Tree:
```
[f, h, k, r]
/ | | | \
[a, b, c, d, e] [g] [i, j] [m] [s, x]
```

16. **Insert l**:


Insert **l** into the right child **[m]**.
Tree:
```
[f, h, k, r]
/ | | | \
[a, b, c, d, e] [g] [i, j] [l, m] [s, x]
```

17. **Insert n**:


Insert **n** into the right child **[l, m]**.
Tree:
```
[f, h, k, r]
/ | | | \
[a, b, c, d, e] [g] [i, j] [l, m, n] [s, x]
```

18. **Insert t**:


Insert **t** into the right child **[s, x]**.
Tree:
```
[f, h, k, r]
/ | | | \
[a, b, c, d, e] [g] [i, j] [l, m, n] [s, t, x]
```

19. **Insert u**:


Insert **u** into the right child **[s, t, x]**.
Tree:
```
[f, h, k, r]
/ | | | \
[a, b, c, d, e] [g] [i, j] [l, m, n] [s, t, u, x]
```

20. **Insert p**:


Insert **
14 Discuss left skewed and right skewed binary tree. Construct an AVL tree by inserting the following elements in
the order of their occurrence: 60, 2, 14, 22, 13, 111, 92, 86.
Solution.

Left-Skewed and Right-Skewed Binary Trees


In a Binary Search Tree (BST), the terms left-skewed and right-skewed describe the structural
imbalances in the tree. Both of these terms refer to cases where the tree is not balanced, which
affects the efficiency of operations like search, insert, and delete.
1. Left-Skewed Binary Tree:
A left-skewed binary tree is a tree where every node has only a left child, and no right children.
This leads to a structure resembling a linked list where all nodes "lean" toward the left.
• Example Structure:
10
/
9
/
8
/
7

- **Characteristics:**
- Every node (except the leaf node) has only a left child.
- The height of the tree is equal to the number of nodes, i.e., the tree has **n - 1** height, where
**n** is the number of nodes.
- In such a tree, the time complexity for searching, inserting, and deleting elements becomes
**O(n)**, which is inefficient.

#### 2. **Right-Skewed Binary Tree:**

A **right-skewed binary tree** is the opposite of the left-skewed tree. In a right-skewed tree, every
node has only a right child and no left child. This also leads to a structure that resembles a linked
list but with all nodes "leaning" to the right.

- **Example Structure:**
10
\
9
\
8
\
7

- **Characteristics:**
- Every node (except the leaf node) has only a right child.
- The height of the tree is also equal to **n - 1**.
- The time complexity for search, insert, and delete operations becomes **O(n)**, leading to
inefficient performance.

In both cases, the height of the tree increases linearly with the number of nodes, resulting in
inefficient operations. This is why **balanced** binary trees, such as **AVL trees** or **Red-Black
trees**, are preferred for efficient searching, inserting, and deleting.

---

### AVL Tree Construction

An **AVL tree** is a **self-balancing binary search tree** where the difference between the heights of
the left and right subtrees of every node (called the **balance factor**) is at most 1. If this balance
factor becomes greater than 1 or less than -1 at any node, the tree is rebalanced using **rotations**
(either **left** or **right** rotations).

We are given the following elements for insertion in an AVL tree: **60, 2, 14, 22, 13, 111, 92, 86**.
Let's go step by step to insert these elements while maintaining the AVL property.

### Insertion Steps:

#### Step 1: Insert 60


- The tree is empty, so **60** becomes the root.
60

#### Step 2: Insert 2


- **2** is less than 60, so it becomes the left child of 60.
60
/
2

- **Balance check**: The balance factor of 60 is **+1** (left subtree height = 1, right subtree height =
0), which is within the allowed range of -1, 0, +1. So, the tree is balanced.

#### Step 3: Insert 14


- **14** is less than 60 but greater than 2, so it becomes the right child of 2.
60
/
2
14

- **Balance check**: The balance factor of 2 is **-1** (left subtree height = 0, right subtree height =
1). The balance factor of 60 is **+2**, which indicates the tree is unbalanced at node 60. To restore
balance, we perform a **left rotation** on node 60.

**Left Rotation on 60**:


- After rotating, 2 becomes the new root, 60 becomes the right child of 2, and 14 remains the right
child of 2.
2
\
14
/ \
60

#### Step 4: Insert 22


- **22** is greater than 2, less than 60, and greater than 14, so it becomes the right child of 14.
2
\
14
/ \
60 22

- **Balance check**: The balance factor of node 14 is **0** (left and right subtrees are balanced). The
tree is still balanced.

#### Step 5: Insert 13


- **13** is greater than 2, less than 14, and less than 22, so it becomes the left child of 14.
2
\
14
/ \
13 60
\
22

- **Balance check**: The balance factor of node 14 becomes **+1** (left subtree height = 1, right
subtree height = 2). This balance factor is within the allowed range, so the tree remains balanced.

#### Step 6: Insert 111


- **111** is greater than 2, greater than 14, and greater than 60, so it becomes the right child of 60.
2
\
14
/ \
13 60
\ \
22 111

- **Balance check**: The balance factor of node 60 is **-1** (left subtree height = 0, right subtree
height = 2). The tree is still balanced.

#### Step 7: Insert 92


- **92** is greater than 2, greater than 14, greater than 60, and less than 111, so it becomes the left
child of 111.
2
\
14
/ \
13 60
\ \
22 111
/
92

- **Balance check**: The balance factor of node 111 is **+1** (left subtree height = 1, right subtree
height = 0). The balance factor of node 60 is **-1**, and the tree is balanced.

#### Step 8: Insert 86


- **86** is greater than 2, greater than 14, greater than 60, less than 111, and greater than 92, so it
becomes the right child of 92.
2
\
14
/ \
13 60
\ \
22 111
/ \
92 86

- **Balance check**: The balance factor of node 92 becomes **-1**, which is acceptable. The balance
factor of node 60 is **-2**, indicating an imbalance. To restore balance, we perform a **left
rotation** on node 111.

**Left Rotation on 111**:


- After rotating, 92 becomes the new right child of 60, and 86 becomes the left child of 92.
2
\
14
/ \
13 60
\ \
22 92
/ \
86 111

### Final AVL Tree:

After inserting all the elements, the AVL tree is as follows:

2
\
14
/ \
13 60
\ \
22 92
/ \
86 111

### Key Points:


- The tree is balanced at each step with the balance factor of every node being **-1**, **0**, or
**+1**.
- The **left rotation** and **right rotation** operations were used to restore balance when needed.

### Summary of Rotations:


- **Left Rotation** is performed when the right subtree is heavier than the left.
- **Right Rotation** is performed when the left subtree is heavier than the right.
15 Discuss the difference between a general tree and a binary tree. What is a complete binary tree? Give an
algorithm for deleting a value X from a given binary tree.
Solution.

Difference Between a General Tree and a Binary Tree


1. Structure:
o General Tree: A general tree can have any number of child nodes for each node. There
are no restrictions on how many children a node can have. It can be visualized as a tree
with branches that can fan out into many directions, meaning each node can have zero
or more children.
o Binary Tree: A binary tree is a special type of tree where each node has at most two
children, often referred to as the "left" and "right" child. This restriction makes the
structure of binary trees more constrained compared to general trees.
2. Node Degree:
o General Tree: The degree of a node (the number of children it has) can vary from 0 to
any number.
o Binary Tree: The degree of each node is restricted to 0, 1, or 2, meaning it can have at
most two children.
3. Applications:
o General Tree: Often used in situations where hierarchical relationships can have
varying numbers of subordinates (e.g., organizational charts, file systems).
o Binary Tree: Typically used in data structures such as binary search trees (BST), heaps,
and syntax trees in compilers, as the binary tree structure provides efficient algorithms
for searching, inserting, and deleting elements.
Complete Binary Tree
A Complete Binary Tree is a special type of binary tree in which:
• Every level of the tree is fully filled except possibly for the last level.
• If the last level is not complete, the nodes are as far left as possible.
In other words, a complete binary tree has a compact structure where all levels, except possibly the
last, are completely filled, and the last level is filled from left to right.
Properties of a Complete Binary Tree:
• The number of nodes in a complete binary tree is maximized for a given height.
• If the binary tree has n nodes, the height h of the tree is approximately log₂(n).
Algorithm for Deleting a Value X from a Given Binary Tree
To delete a node with value X from a binary tree, the algorithm can be broken down into several
cases. The most straightforward approach for a general binary tree is to find the node to delete,
remove it, and reorganize the tree accordingly. A commonly used method for deletion in a binary
tree is to replace the node with a suitable node from the tree (often the deepest rightmost node in
the tree).
Steps for Deleting Node X:
1. Find the node X: Perform a tree traversal (pre-order, in-order, or post-order) to locate the
node with the value X.
2. Case 1 – Node X is a leaf node (no children):
o If the node has no children, simply remove the node from the tree by setting the
reference to that node in its parent to null.
3. Case 2 – Node X has one child:
o If the node has one child (either left or right), replace the node with its single child by
adjusting the parent's pointer to point to the child of X.
4. Case 3 – Node X has two children:
o If the node has two children, the deletion process is a bit more involved:
▪ Find the deepest node (the node that is farthest from the root).
▪ Swap the values of the node X with the deepest node.
▪ Delete the deepest node, which will now be a leaf node. This can be done by
setting the parent's reference to the deepest node to null.
5. Rebalance or adjust any required pointers after deletion if needed (depending on the
structure of the tree and whether it needs to be rebalanced).
Example:
Let's walk through the deletion of a node X from a binary tree.
Assume the following binary tree structure:
markdown
Copy code
10
/ \
5 15
/\ / \
3 8 12 18
Suppose we want to delete the node with value 15:
• Step 1: The node 15 has two children, so we need to find the deepest node in the tree. In this
case, the deepest node is 18.
• Step 2: Swap the values of 15 and 18:
After the swap:
markdown
Copy code
10
/ \
5 18
/\ / \
3 8 12 15
• Step 3: Delete the deepest node 18 (which is now a leaf node).
After deletion:
markdown
Copy code
10
/ \
5 15
/\ /
3 8 12
This results in a binary tree with the value 15 deleted. The tree now has a new structure, and the
node deletion has been successfully performed.
16 Explain Huffman algorithm. Construct Huffman tree for “UTTAR PRADESH” with its optimal code.
Solution.

Huffman Algorithm Overview


The Huffman coding algorithm is a popular method used for lossless data compression. The idea
behind Huffman coding is to assign shorter codes to more frequent characters and longer codes to
less frequent characters, making the overall encoding more efficient.
The key steps of the Huffman coding algorithm are:
1. Frequency Analysis:
o Compute the frequency of each character in the input text (or data).
2. Build a Priority Queue (Min-Heap):
o Create a leaf node for each unique character and its frequency, then insert these nodes
into a priority queue (min-heap) where the node with the lowest frequency has the
highest priority.
3. Construct the Huffman Tree:
o While there is more than one node in the priority queue:
1. Extract the two nodes with the lowest frequencies.
2. Create a new internal node with these two nodes as children. The frequency of
the new node is the sum of the frequencies of the two children.
3. Insert the new internal node back into the priority queue.
o Continue this process until there is only one node left in the priority queue, which will
be the root of the Huffman tree.
4. Generate Huffman Codes:
o Assign binary codes to each character by traversing the tree from the root to each leaf
node. Typically, assign '0' for left branches and '1' for right branches.
5. Encoding:
o The character corresponding to each leaf node is assigned a binary string based on its
path from the root.
Example: Constructing a Huffman Tree for "UTTAR PRADESH"
Let's walk through the steps of constructing the Huffman tree for the string "UTTAR PRADESH".
Step 1: Frequency Analysis
First, we count the frequency of each character in the string. We ignore spaces during frequency
counting.
U: 1, T: 2, A: 2, R: 2, P: 1, D: 1, E: 1, S: 1, H: 1
Step 2: Build the Min-Heap
We create a min-heap based on the frequency of characters. The leaf nodes are created for each
character, and they are inserted into the priority queue (min-heap).
Character Frequency
U 1
T 2
A 2
R 2
P 1
D 1
E 1
S 1
H 1
Step 3: Construct the Huffman Tree
Now, we start building the tree by combining the two nodes with the lowest frequencies:
1. Combine U (1) and P (1) → create a new node with frequency 2.
2. Combine D (1) and E (1) → create a new node with frequency 2.
3. Combine S (1) and H (1) → create a new node with frequency 2.
4. Now, we have the following nodes:
(U, P) -> 2, (D, E) -> 2, (S, H) -> 2, T -> 2, A -> 2, R -> 2
Combine (U, P) and (D, E) → create a new node with frequency 4.
5. Combine (S, H) (2) and T (2) → create a new node with frequency 4.
6. Now, we have the following nodes:
(U, P, D, E) -> 4, (S, H, T) -> 4, A -> 2, R -> 2
Combine A and R → create a new node with frequency 4.
7. Combine (U, P, D, E) and (S, H, T) → create a new node with frequency 8.
8. Combine (A, R) and the 8-node (U, P, D, E, S, H, T) → create the final node with frequency
12.
Final Huffman Tree:
The final tree will look like this:
(12)
/ \
(4) (8)
/ \ / \
(A, R) (4) (U, P, D, E) (S, H, T)
/ \ / | \
(U, P) (D, E) (S, H) T
Step 4: Assign Huffman Codes
Starting from the root, we assign binary codes:
• (A, R) → 00
o A → 000
o R → 001
• (U, P, D, E) → 10
o (U, P) → 100
▪ U → 1000
▪ P → 1001
o (D, E) → 101
▪ D → 1010
▪ E → 1011
• (S, H, T) → 11
o (S, H) → 110
▪ S → 1100
▪ H → 1101
o T → 111
Optimal Huffman Code for "UTTAR PRADESH"
Here is the optimal Huffman code for each character:
Character Huffman Code
U 1000
T 111
A 000
R 001
P 1001
D 1010
E 1011
S 1100
H 1101
Final Encoded String:
To encode the string "UTTAR PRADESH" using the Huffman codes, we simply replace each character
with its corresponding Huffman code:
Original string: "UTTAR PRADESH"
Huffman-encoded string:
1000 111 111 000 001 1001 000 1010 1011 1100 1101 111
Summary of the Huffman Algorithm Steps:
1. Count the frequency of characters.
2. Build a priority queue (min-heap) based on frequencies.
3. Construct the Huffman tree by combining nodes with the lowest frequencies.
4. Generate binary codes for each character by traversing the tree.
5. Encode the data using the generated Huffman codes.
17 Define AVL Trees. Explain its rotation operations with example. Construct an AVL tree with the values 10
to 19 numbers into an initially empty tree..
Solution.
AVL Trees:
An AVL tree (Adelson-Velsky and Landis tree) is a self-balancing binary search tree (BST) in which
the difference between the heights of the left and right subtrees of any node (called the balance
factor) is at most 1. This ensures that the tree remains balanced, allowing for efficient search,
insert, and delete operations.
Balance Factor:
For any node in an AVL tree, the balance factor is defined as:
Balance Factor=Height of left subtree−Height of right subtree
• If the balance factor is -1, 0, or 1, the tree is balanced at that node.
• If the balance factor is less than -1 or greater than 1, the tree is unbalanced and requires a
rotation to restore balance.
Types of Rotations in AVL Trees:
When the balance factor of a node becomes greater than 1 or less than -1, the tree needs to be
rebalanced. This is done using rotations. There are four types of rotations used to balance the AVL
tree:
1. Left Rotation (LL Rotation):
o Applied when the right subtree is taller than the left subtree of a node, i.e., a right-
heavy subtree.
o Example: The imbalance occurs due to an insertion into the right child of the right
subtree.
Steps:
o Perform a left rotation around the unbalanced node.
Example:
Before Left Rotation:
x
\
y
\
z

After Left Rotation (around x):


y
/\
x z
2. Right Rotation (RR Rotation):
o Applied when the left subtree is taller than the right subtree of a node, i.e., a left-
heavy subtree.
o Example: The imbalance occurs due to an insertion into the left child of the left
subtree.
Steps:
o Perform a right rotation around the unbalanced node.
Example:
Before Right Rotation:
z
/
y
/
x

After Right Rotation (around z):


y
/\
x z
3. Left-Right Rotation (LR Rotation):
o A double rotation: First, a left rotation on the left child of the node (creating a left-
heavy situation), followed by a right rotation on the unbalanced node.
o Example: The imbalance occurs due to an insertion into the right child of the left
subtree.
Steps:
o First, perform a left rotation on the left child.
o Then perform a right rotation on the unbalanced node.
Example:
Before LR Rotation:
x
/
y
\
z

After LR Rotation (left rotation on y, then right rotation on x):


y
/\
x z
4. Right-Left Rotation (RL Rotation):
o A double rotation: First, a right rotation on the right child of the node (creating a
right-heavy situation), followed by a left rotation on the unbalanced node.
o Example: The imbalance occurs due to an insertion into the left child of the right
subtree.
Steps:
o First, perform a right rotation on the right child.
o Then perform a left rotation on the unbalanced node.
Example:
Before RL Rotation:
z
\
y
/
x

After RL Rotation (right rotation on y, then left rotation on z):


y
/\
x z

Constructing an AVL Tree with Numbers 10 to 19:


Let's construct an AVL tree with the values 10 to 19 inserted into an initially empty tree. We'll insert
the values one by one and apply rotations where necessary to maintain the balance.
Insert values: 10, 11, 12, 13, 14, 15, 16, 17, 18, 19
Step-by-Step Construction:
1. Insert 10: Tree is simply 10.
10
2. Insert 11: Tree remains balanced.
10
\
11
3. Insert 12: Imbalance at node 10 (Right-Right case).
o Perform Left Rotation at node 10.
Before rotation:
10
\
11
\
12
After Left Rotation:
11
/ \
10 12
4. Insert 13: Tree remains balanced.
11
/ \
10 12
\
13
5. Insert 14: Imbalance at node 12 (Right-Right case).
o Perform Left Rotation at node 12.
Before rotation:
11
/ \
10 12
\
13
\
14
After Left Rotation:
11
/ \
10 13
/ \
12 14
6. Insert 15: Tree remains balanced.
11
/ \
10 13
/ \
12 14
\
15
7. Insert 16: Imbalance at node 14 (Right-Right case).
o Perform Left Rotation at node 14.
Before rotation:
11
/ \
10 13
/ \
12 14
\
15
\
16
After Left Rotation:
11
/ \
10 13
/ \
12 15
/ \
14 16
8. Insert 17: Tree remains balanced.
11
/ \
10 13
/ \
12 15
/ \
14 16
\
17
9. Insert 18: Imbalance at node 16 (Right-Right case).
o Perform Left Rotation at node 16.
Before rotation:
11
/ \
10 13
/ \
12 15
/ \
14 16
\
17
\
18
After Left Rotation:
11
/ \
10 13
/ \
12 15
/ \
14 17
/ \
16 18
10. Insert 19: Tree remains balanced.
markdown
Copy code
11
/ \
10 13
/ \
12 15
/ \
14 17
/ \
16 18
\
19

Final AVL Tree:


After inserting all values from 10 to 19, the final AVL tree will look like this:
markdown
Copy code
13
/ \
11 17
/ \ / \
10 12 15 18
/ \
14 16
\
19
• The AVL tree is balanced at every node.
• The height difference between left and right subtrees at each node is either 0, 1, or -1.
This ensures that the tree remains balanced, and all operations (search, insert, delete) can be done
efficiently in O(log n) time.
18 A scheme for storing binary trees in an array X is as follows. Indexing of X starts at 1 instead of 0. the root is
stored at X[1]. For a node stored at X[i], the left child, if any, is stored in X[2i] and the right child, if any, in
X[2i+1]. To be able to store any binary tree on n vertices the minimum size of X should be. [GATE CS 2006]

Solution.

To determine the minimum size of the array X required to store a binary tree with n vertices
(nodes), let's carefully analyse the indexing scheme given:
Given Scheme:
• Indexing starts at 1 (instead of 0).
• The root of the binary tree is stored at X[1].
• For a node stored at X[i]:
o The left child (if any) is stored at X[2i].
o The right child (if any) is stored at X[2i + 1].
Important Observations:
1. Array Structure:
o The array stores nodes in a specific pattern. For example:
▪ X[1] is the root node.
▪ X[2] is the left child of the root.
▪ X[3] is the right child of the root.
▪ X[4] is the left child of X[2], and so on.
o This indexing follows the structure of a complete binary tree where each node is
placed in a level-order manner.
2. Maximum Index:
o For any binary tree with n nodes, the last node (the n-th node) will be stored at index
n. The array must be large enough to accommodate up to this n-th node.
o The array indexing starts at 1 and goes up to n (the total number of nodes).
3. No Need for Extra Space:
o The structure of the binary tree ensures that the array can represent the tree correctly
by indexing each node and its children as described. Even if the tree is sparse, the
array will not require more than n elements because:
▪ The leaf nodes and internal nodes are indexed consecutively.
▪ There is no wasted space in the array, and you don’t need to reserve extra
positions for non-existent children of leaf nodes.
Conclusion:
• The minimum size of the array X must be exactly n to store a binary tree with n vertices
(since the array is indexed starting from 1 and can store up to the n-th node).
Thus, the minimum size of the array X to store any binary tree with n vertices is n.
Answer:
The minimum size of the array X to store a binary tree with n nodes is n.
19 Write the various properties of B- Tree. Show the results of inserting the keys F, S, Q, K, C, L, H, T, V, W, M,
R, N, P, A, B in order into an empty B-Tree of order 5.

Solution.
Properties of B-Trees:
A B-tree is a self-balancing search tree in which nodes can have multiple keys and children. It is
commonly used in databases and file systems because it allows for efficient insertion, deletion, and
search operations. The B-tree properties are designed to keep the tree balanced and minimize the
number of disk accesses.
Here are the key properties of a B-tree of order m:
1. Order:
o The order mmm of a B-tree defines the maximum number of children each node can
have. A B-tree of order mmm has the following properties:
▪ Each node can have at most m children.
▪ Each node can have at most m−1 keys.
▪ Each internal node (non-leaf node) has at least ⌈m/2⌉ children, except for the
root, which can have fewer than this minimum.
2. Root Node:
o The root node must have at least one child if it is not a leaf node.
o If the root is a leaf, it may contain fewer than ⌈m/2⌉ keys.
3. Leaf Nodes:
o Leaf nodes do not have children. They must contain between ⌈m/2⌉−1 keys.
4. Balanced Tree:
o All leaf nodes are at the same level, ensuring the tree is balanced.
o This property ensures that the height of the tree is logarithmic with respect to the
number of keys, leading to efficient search, insert, and delete operations.
5. Insertion:
o Insertion in a B-tree maintains its properties, and if a node overflows (i.e., has more
than m−1keys), the node splits into two nodes, and the middle key is promoted to the
parent node.
6. Deletion:
o Deletion involves removing a key from a leaf or internal node and ensuring that the tree
remains balanced. If a node becomes underfull (i.e., it has fewer than ⌈m/2⌉−1 keys), it
may borrow a key from a sibling or merge with a sibling.

B-Tree of Order 5 (Order m=5)


A B-tree of order 5 can have at most 4 keys per node and 5 children per node. Each internal node
must have at least 2 children, and each leaf node must have between 2 and 4 keys (inclusive).
Inserting Keys into an Empty B-Tree of Order 5
We will insert the following sequence of keys into an initially empty B-tree of order 5:
Sequence of Keys: F, S, Q, K, C, L, H, T, V, W, M, R, N, P, A, B
We will perform the insertions step by step and show the resulting tree at each step.
Step 1: Insert F
The tree is initially empty. We insert F into the root node.
Root: [F]
Step 2: Insert S
Insert S. The root node now contains two keys: F and S.
Root: [F, S]
Step 3: Insert Q
Insert Q. The root node now contains three keys: F, Q, S.
Root: [F, Q, S]
Step 4: Insert K
Insert K. The root node is full (4 keys), so we split the root into two nodes. The middle key (Q) is
promoted to a new root.
After splitting:
[Q]
/ \
[F, K] [S]
Step 5: Insert C
Insert C into the left child [F, K]. After inserting:
[Q]
/ \
[C, F, K] [S]
Step 6: Insert L
Insert L into the right child [S]. After inserting:
[Q]
/ \
[C, F, K] [L, S]
Step 7: Insert H
Insert H into the left child [C, F, K]. The left child becomes full, so it splits. The middle key (F) is
promoted to the root.
After the split:
[F, Q]
/ | \
[C, H] [K] [L, S]
Step 8: Insert T
Insert T into the right child [L, S]. After inserting:
[F, Q]
/ | \
[C, H] [K] [L, S, T]
Step 9: Insert V
Insert V into the right child [L, S, T]. The right child becomes full, so it splits. The middle key (S) is
promoted to the root.
After the split:
[F, Q, S]
/ | | \
[C, H] [K] [L] [T, V]
Step 10: Insert W
Insert W into the rightmost child [T, V]. After inserting:
[F, Q, S]
/ | | \
[C, H] [K] [L] [T, V, W]
Step 11: Insert M
Insert M into the middle child [K]. After inserting:
[F, Q, S]
/ | | \
[C, H] [K, M] [L] [T, V, W]
Step 12: Insert R
Insert R into the right child [L]. After inserting:
[F, Q, S]
/ | | \
[C, H] [K, M] [L, R] [T, V, W]
Step 13: Insert N
Insert N into the right child [L, R]. After inserting:
[F, Q, S]
/ | | \
[C, H] [K, M] [L, N, R] [T, V, W]
Step 14: Insert P
Insert P into the rightmost child [T, V, W]. After inserting:
[F, Q, S]
/ | | \
[C, H] [K, M] [L, N, R] [P, T, V, W]
Step 15: Insert A
Insert A into the left child [C, H]. After inserting:
[F, Q, S]
/ | | \
[A, C, H] [K, M] [L, N, R] [P, T, V, W]
Step 16: Insert B
Insert B into the left child [A, C, H]. The left child becomes full, so it splits. The middle key (C) is
promoted to the root.
After the split:
[C, F, Q, S]
/ | | | \
[A, B] [H] [K, M] [L, N, R] [P, T, V, W]
Final B-Tree:
The final B-tree after inserting all the keys in order is:
[C, F, Q, S]
/ | | | \
[A, B] [H] [K, M] [L, N, R] [P, T, V, W]
Summary:
• The tree is balanced, and all leaf nodes are at the same level.
• The keys have been inserted into the tree while maintaining the B-tree properties, ensuring
that each node has between ⌈m/2⌉ and m−1 keys, and the tree remains balanced with each
node having the appropriate number of children.

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy