Data Structures
Data Structures
PART I
4. Define Array.
An array is a data structure consisting of a collection of elements (values or variables), each
identified by at least one array index or key. An array is stored such that the position of each
element can be computed from its index tuple by a mathematical formula. An array is a linear
data structure that stores elements of the same data type together in contiguous memory
locations.
6. What are the ways to represent the two dimensional Array in memory.
There are two ways to represent a two-dimensional array in memory: row-major and column-
major. A 2D array has a type such as int[][] or String[][], with two pairs of square
brackets. The elements of a 2D array are arranged in rows and columns, and the new operator
for 2D arrays specifies both the number of rows and the number of columns1.
In row-major order, the elements of each row are stored together in memory. In other words,
all the elements of the first row are stored first, followed by all the elements of the second
row, and so on. This is the default way that Java stores 2D arrays1.
In column-major order, the elements of each column are stored together in memory. In other
words, all the elements of the first column are stored first, followed by all the elements of the
second column, and so on
7. Calculate the address of the element a[2][4] of an array a[3][5] in row major order.
To calculate the address of the element a[2][4] of an array a[3][5] in row major order, we can
use the following formula:
Address of A [i] [j] = B + W * ((i * N) + j)
Where, B is the base address of the array. W is the size of each element in bytes. N is the
number of columns in the array.
In this case, we have: B = address of a[0][0] W = size of each element in bytes N = 5
So, to find the address of a[2][4], we have: Address of A 2 [4] = B + W * ((2 * 5) + 4) = B +
W * (14)
struct structureName
{
dataType member1;
dataType member2;
...
};
9. Define Stack.
A stack is a linear data structure in which the insertion of a new element and removal of an
existing element takes place at the same end represented as the top of the stack. A stack has
only one end, whereas a queue has two ends (front and rear). It contains only one pointer top
pointer pointing to the topmost element of the stack.
12. Write the rules for converting an Infix notation to Postfix form.
To convert infix notation to postfix notation, we use the stack data structure. Here are the
rules for conversion123:
1. Start scanning from left to right.
2. If there is an operand, store it into the output string.
3. If there is an operator, push it onto the stack.
4. If there is a left parenthesis, push it onto the stack.
5. If there is a right parenthesis, pop operators from the stack and add them to the output
string until you reach the left parenthesis.
6. If there is an operator with higher precedence than the top of the stack, push it onto
the stack.
7. If there is an operator with lower or equal precedence than the top of the stack, pop
operators from the stack and add them to the output string until you reach an operator
with lower precedence or a left parenthesis.
8. Repeat steps 2-7 until you have scanned all characters in the infix expression.
9. Pop any remaining operators from the stack and add them to the output string.
PART IA
8. Explain the Insertion and Deletion algorithm for Circular linked List.
In a circular linked list, the last node of the list points to the first node of the list instead of
pointing to null. This makes it easier to traverse the list in a circular manner.
The insertion and deletion algorithms for a circular linked list are similar to those for a singly
linked list. Here are the algorithms for insertion and deletion in a circular linked list:
Insertion: To insert an element into a circular linked list, we first create a new node
with the given data. If the list is empty, we set the new node as the head of the list and
point its next field to itself. If the list is not empty, we traverse the list until we reach
the last node. We then set the next field of the last node to point to the new node and
set the next field of the new node to point to the head of the list.
Deletion: To delete an element from a circular linked list, we first find the node that
contains the element. We then set the next field of the previous node to point to the
next node of the current node. If we are deleting the head of the list, we set the head
of the list to point to its next field.
PART II
10. What are the advantages of binary tree linked representation over array representation?
The linked list representation of binary trees has several advantages over the array
representation:
1. Dynamic size: Linked lists can grow or shrink dynamically as needed, whereas arrays
have a fixed size.
2. Ease of insertion/deletion: Inserting or deleting a node in a linked list is easier than in
an array because it does not require shifting elements.
3. Ease in randomly accessing a node: In a linked list, accessing an element at a random
position requires traversing the list from the beginning, whereas in an array it can be
done in constant time.
4. Both dynamic size and ease in insertion/deletion.
PART IIA
ANSWER ALLQUESTION
1. Explain various tree traversal algorithms with implementation.
There are two types of tree traversal algorithms: Depth-First Search (DFS) and Breadth-First
Search (BFS). DFS is further classified into three categories: Preorder Traversal, Inorder
Traversal, and Postorder Traversal1.
Here’s an implementation of the Preorder Traversal algorithm in Python:
class Node:
def __init__(self, value):
self.left = None
self.right = None
self.value = value
def preorder(node):
if node is not None:
print(node.value)
preorder(node.left)
preorder(node.right)
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
preorder(root)
class Node:
def __init__(self, value):
self.left = None
self.right = None
self.value = value
def inorder(node):
if node is not None:
inorder(node.left)
print(node.value)
inorder(node.right)
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
inorder(root)
class Node:
def __init__(self, value):
self.left = None
self.right = None
self.value = value
def postorder(node):
if node is not None:
postorder(node.left)
postorder(node.right)
print(node.value)
root = Node(1)
root.left = Node(2)
root.right = Node(3)
root.left.left = Node(4)
root.left.right = Node(5)
postorder(root)
class BinarySearchTree:
def __init__(self):
self.root = None
def inorder_traversal(self):
if self.root is not None:
self._inorder_traversal(self.root)
bst = BinarySearchTree()
bst.insert(5)
bst.insert(3)
bst.insert(7)
bst.insert(1)
bst.insert(9)
print(bst.search(3))
print(bst.search(10))
bst.inorder_traversal()
3. Write down an algorithm to find the max and smallest element in a tree structure.
To find the smallest element in a binary tree, we can traverse the left subtree of the root node
until we reach a leaf node. The value of that leaf node will be the smallest element in the
tree. Similarly, to find the largest element, we can traverse the right subtree of the root node
until we reach a leaf node. The value of that leaf node will be the largest element in the tree1.
This algorithm is used to find the minimum and maximum elements in a binary search tree.
The function Min finds the minimum element by recursively traversing the left subtree of the
root node until it reaches the leftmost leaf node. Similarly, the function Max finds the
maximum element by recursively traversing the right subtree of the root node until it reaches
the rightmost leaf node.
2. Recursive algorithm:
The non-recursive algorithm uses a stack to traverse the tree. It starts with the root node and
traverses the left subtree until it reaches the leftmost leaf node. It then pops the nodes from
the stack and prints their values. Finally, it traverses the right subtree.
The recursive algorithm is simpler and more intuitive. It traverses the left subtree recursively,
then prints the value of the root node, and finally traverses the right subtree recursively.
5. Explain how arithmetic expressions are manipulated using binary trees. Illustrate with
example.
Arithmetic expressions can be represented using binary trees called expression trees. In an
expression tree, each leaf node represents an operand (such as a number or variable), and
each internal node represents an operator (such as addition or multiplication). The root of the
tree represents the entire expression. Here’s an example of how to create an expression tree
for the arithmetic expression “2 * (3 + 4)”
*
/\
2 +
/\
3 4
To evaluate this expression, we traverse the tree in post-order (left subtree, right subtree,
root) and perform the corresponding operation at each internal node. In this case, we would
first evaluate the left subtree (3 + 4), then multiply the result by 2.
Here is an example of how to insert a node into a binary tree using Python:
class Node:
def __init__(self, val):
self.left = None
self.right = None
self.val = val
Here is an example of how to find the kth element in a binary tree using C:
struct Node
{
int data;
struct Node* left;
struct Node* right;
};
kthSmallestUtil(root->left, k, count);
count++;
if (count == k)
{
printf("%d", root->data);
return;
}
kthSmallestUtil(root->right, k, count);
}
PART III
1. Define B tree.
A B-tree is a self-balancing tree data structure that maintains sorted data and allows searches,
sequential access, insertions, and deletions in logarithmic time. It is a generalized form of a
binary search tree. Unlike traditional binary search trees, B-Trees are characterized by the
large number of keys that they can store in a single node, which is why they are also known
as “large key” trees
2. Define sentinel.
In data structures, a sentinel is a special value that is used to indicate the end of a list or array.
It is used to simplify the code for operations that traverse the list or array. For example, when
traversing a linked list, a sentinel can be used to indicate the end of the list instead of
checking for null pointers.
11. What are the methods used for implementing priority queue?
There are several ways to implement a priority queue, including using an array, linked list,
heap, or binary search tree. Each method has its own advantages and disadvantages, and the
best choice will depend on the specific needs of your application
PART IIIA
7 (0)
/\
6 (+1) 9 (0)
/\
8 (0) 10 (0)
The balance factor of node 6 is +1 and that of node 9 is +2. This means that we do a right
rotation and pull the 7 above the 9 as shown below:
9 (0)
/\
7 (-1) 10 (0)
/\
6 (0) 8 (0)
3. Write down an algorithm to insert a given elementin a binary tree and AVL structure.
Here’s an algorithm to insert an element in AVL tree:
Algorithm Insert (T, x)
1. Perform the standard BST insertion for x.
2. Starting from x, travel up and find the first unbalanced node.
3. Let z be the first unbalanced node, y be the child of z that comes on the path
from x to z and x be the grandchild of z that comes on the path from x to z.
4. Re-balance the tree by performing appropriate rotations on the subtree rooted
at z.
5. Update the height of z and y.
6. Return the new root of the subtree.
Here, T is the AVL tree and x is the element that needs to be inserted.
4. Write down an algorithm to find the max and smallest elementin a tree structure.
Here’s an algorithm to find the maximum and minimum element in a binary tree:
Algorithm FindMaxMin (T)
1. If T is empty, return null.
2. If T has no left child, return T.
3. Recursively find the minimum element in the left subtree of T.
4. If T has no right child, return T.
5. Recursively find the maximum element in the right subtree of T.
Here, T is the binary tree.
5. Explain how arithmetic expressions are manipulated using binary trees. Illustrate with
example.
Arithmetic expressions can be manipulated using binary trees. An arithmetic expression tree
is a binary tree in which each internal node corresponds to an operator and each leaf node
corresponds to an operand. The general strategy for producing an algebraic expression from a
binary expression tree is to recursively produce a parenthesized left expression, then print out
the operator at the root, and finally recursively produce a parenthesized right
expression. This general strategy (left, node, right) is known as an in-order traversal 1.
Here’s an example:
Expression Tree for 3 + ((5+9)*2)
+
/ \
3 *
/ \
+ 2
/\
5 9
6. Write an algorithm to perform basic operations in priority queue.
Here are the basic operations of a priority queue:
Insertion: This operation inserts an item into the queue. The item can be inserted at
the end of the queue or at the front of the queue or at the middle based on the priority
value.
Deletion: This operation deletes the item with the highest priority from the queue.
Peek: This operation retrieves, but does not remove, the head of this queue, or returns
null if this queue is empty 1.
Here’s an algorithm to perform basic operations in priority queue:
Algorithm PriorityQueue:
1. Create a new empty priority queue.
2. Insert an item into the priority queue.
3. Remove an item from the priority queue.
4. Retrieve but do not remove the head of the priority queue.
class BinaryHeap:
def __init__(self):
self.heap = []
def extract_min(self):
if len(self.heap) == 0:
return None
elif len(self.heap) == 1:
return self.heap.pop()
else:
min_val = self.heap[0]
self.heap[0] = self.heap.pop()
self._bubble_down(0)
return min_val
if smallest_child != index:
self.heap[index], self.heap[smallest_child] = self.heap[smallest_child],
self.heap[index]
self._bubble_down(smallest_child)
PART IV
1. Define graph.
A graph is a collection of vertices (also called nodes) and edges that connect pairs of vertices.
A graph can be directed or undirected, depending on whether the edges have a direction or
not. In a directed graph, the edges have a direction and are represented by arrows. In an
undirected graph, the edges do not have a direction and are represented by lines .
16. What is the worst case running time for Dijikstra’s Algorithm?
The worst case running time for Dijkstra’s Algorithm is O((|E|+|V|)log|V|) when using a
binary min-heap as a priority queue1. However, the worst case for Dijkstra binary heap
implementation is O(V log V + E log V) in the case of a sparse graph that has a lot of lone
vertices
PART IVA
1
/\
2-3
/\/\
4-5-6
The algorithm starts by selecting an arbitrary vertex, say vertex 1. Then it selects the edge
with the smallest weight that connects vertex 1 to another vertex, say vertex 2. The algorithm
then adds vertex 2 to the tree and repeats the process until all vertices are included in the
tree³.
In this example, the minimum spanning tree would be:
1
\
2
/\
4-5
/\
3-6
1
/\
2-3
/\/\
4-5-6
The DFS algorithm starts at vertex 1 and visits all vertices in the following order: 1, 2, 4, 5, 3,
6².
Here's an implementation of DFS in Python:
while stack:
vertex = stack.pop()
return visited
1
/\
2-3
/\/\
4-5-6
The BFS algorithm starts at vertex 1 and visits all vertices in the following order: 1, 2, 3, 4, 5,
6².
while queue:
vertex = queue.pop(0)
return visited
#include<stdio.h>
#include<stdlib.h>
#define MAX 100
int n,a[MAX][MAX],visited[MAX],stack[MAX],top=-1;
void dfs(int v)
{
int i;
visited[v]=1;
for(i=1;i<=n;i++)
if(a[v][i] && !visited[i])
dfs(i);
stack[++top]=v;
}
void topological_sort()
{
int i;
for(i=1;i<=n;i++)
visited[i]=0;
for(i=1;i<=n;i++)
if(!visited[i])
dfs(i);
printf("\nTopological order:");
for(i=top;i>=0;i--)
printf("%d ",stack[i]);
}
int main()
{
int i,j;
printf("Enter number of vertices:");
scanf("%d",&n);
printf("\nEnter the adjacency matrix:\n");
for(i=1;i<=n;i++)
for(j=1;j<=n;j++)
scanf("%d",&a[i][j]);
topological_sort();
return 0;
}
PART V
1. Define Program.
A program is a set of instructions that a computer uses to perform a specific function. It
contains a list of ingredients (called variables, which can represent numeric data, text, or
images) and a list of directions (called statements) that tell the computer how to execute a
specific task.
2. Define Algorithm.
An algorithm is a set of instructions that are followed in order to solve a problem or perform
a task. It is essentially a step-by-step procedure that outlines exactly what needs to be done to
complete the task at hand
def factorial(n):
if n == 1:
return 1
else:
return n * factorial(n-1)
This function calculates the factorial of a number using recursion. The base case is when n
equals 1, and the function returns 1. If n is greater than 1, the function calls itself with n-1 as
the argument.
PART VA
2. Design an algorithm that accepts a positive integer and reverse the order of its digits.
Here is an algorithm that accepts a positive integer and reverses the order of its digits:
1. Get the least significant digit (right-most digit) of the number.
2. Append it at the end of the reverse number.
3. Remove the right-most digit from the number.
4. Repeat steps 1-3 until all digits have been processed.
5. Here is an example of how this algorithm works:
6. Suppose we want to reverse the digits of 1234.
7. The least significant digit is 4. Append it to the end of the reverse number, which is
initially 0. The reverse number becomes 4.
8. Remove the right-most digit from 1234, which gives us 123.
9. The least significant digit of 123 is 3. Append it to the end of the reverse number,
which is now 4. The reverse number becomes 43.
10. Remove the right-most digit from 123, which gives us 12.
11. The least significant digit of 12 is 2. Append it to the end of the reverse number,
which is now 43. The reverse number becomes 432.
12. Remove the right-most digit from 12, which gives us 1.
13. The least significant digit of 1 is 1. Append it to the end of the reverse number, which
is now 432. The reverse number becomes 4321.
14. So, if we apply this algorithm to the positive integer 1234, we get the reversed integer
4321.
3. Explain the Base conversion algorithm to convert a decimal integer to its corresponding
octal representation.
To convert a decimal integer to its corresponding octal representation, you can use the
repeated division and remainder algorithm. Here are the steps:
1. Divide the decimal number by 8.
2. Save the result and the remainder.
3. Repeat step 1, but this time, divide the result from the last calculation.
4. Once again, save the result and the remainder.
5. Repeat until the result reaches 0.
6. Take the remainders in reversed order.
For example, let’s say we want to convert the decimal integer 123 to its corresponding octal
representation.
1. 123 / 8 = 15 remainder 3
2. Save 3 as the first digit of the octal representation.
3. 15 / 8 = 1 remainder 7
4. Save 7 as the second digit of the octal representation.
5. 1 / 8 = 0 remainder 1
6. Save 1 as the third digit of the octal representation.
7. So, the octal representation of decimal integer 123 is 173.
1 6 30
2 3 14
3 4 16
The backtracking algorithm would start by considering whether or not to include item 1 in the
knapsack. If it does include item 1, then it would consider whether or not to include item 2. If
it does include item 2, then it would consider whether or not to include item 3. If it does not
include item 3, then it would backtrack and try another option.
The main difference between backtracking algorithms and dynamic programming algorithms
is that backtracking algorithms explore all possible solutions to a problem, while dynamic
programming algorithms only explore those solutions that are necessary to solve the problem.