Tribhuvan University
Faculty of Management
Office of the Dean
2023 AD / Regular Examination
Time: 3 Hrs. | Full Marks: 60 | Pass Marks: 30
Subjective Questions
- [2]
Define Asymptotic notations.
View model solution
Definition of Asymptotic Notations
Asymptotic Notations are mathematical notations used to describe the limiting behavior and growth rate of an algorithm’s running time or memory usage as the input size (
) approaches infinity. The three primary standard notations are:
- Big-O Notation (
): Represents the asymptotic upper bound (worst-case scenario). - Big-Omega Notation (
): Represents the asymptotic lower bound (best-case scenario). - Big-Theta Notation (
): Represents the asymptotically tight bound (average-case behavior where upper and lower bounds coincide).
- Big-O Notation (
- [2]
Draw a doubly linked list.
View model solution
Doubly Linked List (DLL)
A Doubly Linked List is a linear data structure in which each node contains three fields:
prev: A pointer/reference to the preceding node (orNULLif it is the first node).data: The actual value or payload stored in the node.next: A pointer/reference to the succeeding node (orNULLif it is the last node).
NULL <--- [prev | 10 | next] <===> [prev | 20 | next] <===> [prev | 30 | next] ---> NULL ^ ^ | | HEAD TAILKey Advantage:
Enables bidirectional traversal (
predecessor and successor access) and efficient deletion given a node pointer without requiring list traversal from head. - [2]
Define stack.
View model solution
Definition of Stack
A Stack is a linear, restricted data structure that follows the LIFO (Last In, First Out) principle: the element inserted last is the first one to be removed.
Core Operations:
push(item): Inserts an element onto the top of the stack (). pop(): Removes and returns the top element (). peek()/top(): Returns the element at the top without removing it (). isEmpty(): Checks whether the stack has zero elements ().
Applications:
Function call execution stack, undo/redo mechanisms, expression evaluation (postfix/infix conversion), and syntax/parenthesis matching.
- [2]
What is a priority queue?
View model solution
Priority Queue
A Priority Queue is an abstract data type (ADT) similar to a regular queue or stack, but where each element is associated with a priority value.
Operating Principles:
- Elements with higher priority are served before elements with lower priority.
- If two elements have the same priority, they are served according to their arrival order (FIFO).
- Types:
- Max-Priority Queue: The element with the highest numerical key/priority is dequeued first.
- Min-Priority Queue: The element with the lowest numerical key is dequeued first.
- Optimal Implementation: Implemented using a Binary Heap (Min-Heap or Max-Heap), providing
insertion and extraction.
- [2]
What do you mean by tail recursion?
View model solution
Tail Recursion
Tail Recursion is a special form of recursion where the recursive function call is the very last operation performed in the function execution path before returning. No pending computations (such as additions or multiplications) remain to be performed after the recursive call finishes.
Example:
// Non-tail recursive (multiplication pending after return) int fact(int n) { if (n <= 1) return 1; return n * fact(n - 1); } // Tail recursive (accumulates result; nothing pending) int factTail(int n, int accum = 1) { if (n <= 1) return accum; return factTail(n - 1, n * accum); }Advantage:
Modern compilers can apply Tail Call Optimization (TCO), transforming the recursive call into a simple iterative jump (
goto), thereby reusing the current stack frame and requiringauxiliary stack space instead of . - [2]
Define a balanced tree.
View model solution
Definition of a Balanced Tree
A Balanced Tree is a tree data structure whose height is bounded to
where is the total number of nodes, ensuring that search, insertion, and deletion operations execute in logarithmic time. Height-Balanced (AVL Tree Definition):
A binary tree is height-balanced if, for every node
in the tree, the difference in height between its left subtree and right subtree (known as the Balance Factor) is at most 1: Common Balanced Tree Structures:
AVL Trees, Red-Black Trees, B-Trees, and B+ Trees.
- [2]
Define hashing.
View model solution
Definition of Hashing
Hashing is a data storage and retrieval technique that transforms a search key of arbitrary size into a fixed-size index within an array (hash table) using a mathematical function known as a Hash Function:
Key Components:
- Hash Function: Maps keys uniformly and deterministically across table indices.
- Hash Table: Fixed array of buckets storing records or pointers.
- Collision Resolution: Handling situations where two distinct keys hash to the same bucket (e.g., Chaining or Open Addressing like Linear Probing).
Time Complexity:
Achieves average-case
time complexity for search, insert, and delete operations. - [2]
What do you mean by degree of a vertex?
View model solution
Degree of a Vertex in Graph Theory
The degree of a vertex
in a graph is the total number of edges incident to (connected to) that vertex. In Undirected Graphs:
: Total number of edges connected to vertex (a self-loop contributes 2 to the degree). - Handshaking Lemma: The sum of degrees of all vertices equals twice the number of edges:
In Directed Graphs (Digraphs):
- In-degree (
): Number of incoming edges directed towards vertex . - Out-degree (
): Number of outgoing edges directed away from vertex . - Total degree:
.
- [2]
List any two sorting algorithms.
View model solution
Two Sorting Algorithms
Two fundamental sorting algorithms widely used in computer science are:
-
Merge Sort:
- Paradigm: Divide-and-Conquer.
- Method: Recursively divides the array into two halves, sorts each half, and merges the sorted halves.
- Time Complexity: Worst:
, Best: , Average: . - Stability: Stable sort; requires
auxiliary space.
-
Quick Sort:
- Paradigm: Divide-and-Conquer (Partitioning).
- Method: Selects a ‘pivot’ element, partitions the array such that elements smaller than pivot precede it and larger elements follow it, then recursively sorts the sub-arrays.
- Time Complexity: Average/Best:
, Worst: .
-
- [2]
Why is Huffman algorithm needed?
View model solution
Why Huffman Algorithm is Needed
The Huffman Algorithm is an optimal greedy algorithm needed for lossless data compression (statistical encoding).
Technical Rationale:
- Variable-Length Prefix Coding: Traditional character encoding systems (such as ASCII or Unicode) assign a fixed number of bits (e.g., 8 bits) to every character regardless of how often it appears.
- Frequency-Based Economy: Huffman coding assigns shorter bit strings to characters that appear with high frequency (e.g., ‘e’, ‘t’, space) and longer bit strings to rarely occurring characters (e.g., ‘z’, ‘q’).
- Prefix Property: Ensures that no code word is a prefix of any other code word, allowing unambiguous, instant streaming decompression without delimiters.
- Significant Bandwidth/Storage Savings: Reduces text file and multimedia payload sizes by 20% to 70%.
- [5]
Explain Abstract Data Type.
View model solution
Abstract Data Type (ADT)
An Abstract Data Type (ADT) is a mathematical model for data types where a data type is defined solely by its behavior (semantics) from the perspective of a consumer—specifically in terms of possible values and permissible operations—completely independent of any specific physical implementation or programming language.
Key Principles of ADT:
- Data Abstraction: Hides internal data representation and memory layout from external callers.
- Encapsulation: Access to data elements is restricted exclusively through a well-defined public interface (functions/methods).
- Implementation Independence: The internal implementation can be changed (e.g., switching from array-based to linked list-based) without affecting existing client code that relies on the ADT interface.
Concrete Example: List ADT
+--------------------------------------------------------+ | List ADT Interface | | - insert(index, item) - remove(index) | | - get(index) - size() - isEmpty() | +---------------------------+----------------------------+ | +----------------+----------------+ | | v v +-----------------------+ +-----------------------+ | Array Implementation | | Singly Linked List | | (Contiguous memory, | | Implementation | | O(1) random access) | | (Dynamic nodes, O(1) | | | | head insertion) | +-----------------------+ +-----------------------+In this model, software modules call
insert()orget()without needing to know whether items are stored in contiguous memory blocks or dynamically linked heap nodes. - [5]
Explain Worst-Case time complexity.
View model solution
Worst-Case Time Complexity
The Worst-Case Time Complexity of an algorithm expresses the maximum amount of time (or number of elementary operations) that the algorithm requires to execute on any input of size
. It is formally denoted using Big-O notation ( ). Mathematical Definition:
Let
be the running time of an algorithm on an input belonging to the set of all possible inputs of size : Why Worst-Case Analysis is Essential in Software Engineering:
- Performance Guarantees (Upper Bound): Provides a strict mathematical guarantee that the algorithm will never perform worse than this threshold, regardless of how pathological the input distribution might be.
- Mission-Critical / Real-Time Systems: In avionics, medical devices, banking transactions, and automated driving, systems cannot tolerate unpredictable performance spikes; worst-case boundaries must be strictly bounded.
- Benchmarking Comparison: Provides an objective, reproducible metric to compare competing algorithms without depending on hardware variations.
Illustrative Examples:
- Linear Search: Worst case occurs when the target is absent or at the very end
. - Quick Sort: Worst case occurs when pivot selection always yields the most unbalanced partition (e.g., sorted array with last-element pivot)
. - Merge Sort: Worst case is strictly bounded to
under all circumstances.
- [5]
Write a function to traverse a binary tree in Preorder.
View model solution
Preorder Traversal of a Binary Tree
In a Preorder Traversal (Depth-First Traversal), nodes are visited in the following strict recursive sequence:
C++ Function Implementation:
#include <iostream> using namespace std; // Binary Tree Node Structure struct Node { int data; Node* left; Node* right; Node(int val) : data(val), left(nullptr), right(nullptr) {} }; // Recursive Preorder Traversal Function void preorderTraversal(Node* root) { // Base Case: empty tree / leaf child if (root == nullptr) { return; } // 1. Visit Current Root Node cout << root->data << " "; // 2. Traverse Left Subtree Recursively preorderTraversal(root->left); // 3. Traverse Right Subtree Recursively preorderTraversal(root->right); }Execution Example:
Consider the binary tree:
1 / \ 2 3 / \ 4 5- Preorder Sequence: Visit 1
Visit 2 Visit 4 Visit 5 Visit 3 1, 2, 4, 5, 3 - Complexity: Time:
(each node visited once), Space: call stack frames (where is tree height).
- Preorder Sequence: Visit 1
- [5]
Explain different application of queue.
View model solution
Practical Applications of Queue Data Structure
A Queue operates strictly on a FIFO (First In, First Out) discipline. Its core applications span systems programming, networking, and algorithmic computing:
- CPU Process Scheduling (Operating Systems):
- The OS Ready Queue stores processes waiting to be allocated CPU time slots in Round-Robin or Multi-Level Feedback Queue scheduling algorithms.
- Asynchronous I/O Buffering:
- Real-world hardware devices (keyboards, printers, network disk buffers) communicate via queues (e.g., printer spooling queue where print jobs execute in arrival order).
- Graph Traversal (Breadth-First Search - BFS):
- BFS algorithms require a FIFO queue to track discovered vertices and explore the graph level-by-level (used in shortest path algorithms on unweighted graphs).
- Network Packet Routing & Traffic Congestion:
- Internet routers, switches, and load balancers queue incoming TCP/IP packets into FIFO buffer queues before processing and forwarding.
- Event-Driven Architectures & Message Brokers:
- Enterprise message queues (e.g., RabbitMQ, Apache Kafka, AWS SQS) buffer asynchronous messages between decoupled microservices.
- CPU Process Scheduling (Operating Systems):
- [5]
How binary search differs from linear search? Explain.
View model solution
Binary Search vs. Linear Search
Parameter Linear Search (Sequential Search) Binary Search Prerequisite None (works on both unsorted and sorted data). Data must be sorted in ascending or descending order. Strategy Sequentially scans elements one-by-one from beginning to end. Divide-and-Conquer: Compares target with middle element, halving the search space each step. Data Structure Works on Arrays, Linked Lists, Streams. Requires contiguous direct-indexed arrays ( random access). Time Complexity (Best) (element found at index 0). (element found at exact middle). Time Complexity (Worst) (all elements inspected). (logarithmic reduction). Time Complexity (Average) . . Performance Example ( ) Up to comparisons. At most comparisons! - [5]
Explain Breadth First Traversal of a graph.
View model solution
Breadth First Traversal (BFS) of a Graph
Breadth First Search (BFS) is a graph exploration algorithm that visits vertices in increasing order of their distance (number of edges) from an arbitrary starting source vertex
. It systematically visits all immediate adjacent neighbors (distance 1), then all neighbors of neighbors (distance 2), and so forth. Core Data Structures:
- FIFO Queue: Stores discovered vertices waiting to have their adjacent edges explored.
- Visited Boolean Array: Prevents processing vertices multiple times and avoids infinite loops in cyclic graphs.
Algorithmic Procedure:
Algorithm BFS(Graph G, SourceVertex s): 1. Create a boolean array visited[] initialized to false. 2. Create an empty Queue Q. 3. Mark s as visited (visited[s] = true) and push s into Q: Q.enqueue(s). 4. While Q is NOT empty: a. u = Q.dequeue() b. Process / Print vertex u c. For each vertex v adjacent to u: if visited[v] == false: visited[v] = true Q.enqueue(v)Complexity:
- Time Complexity:
where is vertices and is edges (using Adjacency List). - Space Complexity:
for queue and visited map.
- [5]
Write an algorithm to find the factorial of n number using recursion.
View model solution
Recursive Algorithm to Find Factorial of
The mathematical definition of factorial for a non-negative integer
is defined recursively as: Pseudocode / Algorithm:
Algorithm Factorial(n): Input: An integer n >= 0 Output: The factorial value n! Step 1: [Check Input Validation] If n < 0 Then: Write "Error: Factorial undefined for negative numbers" Return -1 Step 2: [Base Case] If n == 0 or n == 1 Then: Return 1 Step 3: [Recursive Case] Else: Return n * Factorial(n - 1)Call Stack Execution Trace for
: Factorial(4)callsFactorial(3)callsFactorial(2)callsFactorial(1)hits base casereturns - Returns
. - Complexity: Time:
, Space: stack frames.
- [10]
Define an AVL tree. Construct an AVL tree from the given data: 14, 16, 22, 19, 15, 12, 21.
View model solution
AVL Tree Construction: Step-by-Step
An AVL Tree is a self-balancing Binary Search Tree (BST) where the balance factor
at every node. If , balance is restored via tree rotations (LL, RR, LR, RL). Given Input Sequence: 14, 16, 22, 19, 15, 12, 21
Step 1: Insert 14
- Tree:
(14)[]
Step 2: Insert 16
, insert right child of 14. - Tree:
14 (BF = -1) \ 16 (BF = 0) - Balanced.
Step 3: Insert 22
and , insert right child of 16. - Tree:
14 (BF = -2) <-- Unbalanced! Right-Right (RR) imbalance at node 14 \ 16 (BF = -1) \ 22 (BF = 0) - Fix: Perform Left Rotation (RR rotation) around node 14.
- New Root:
1616 (BF = 0) / \ 14 22 (BF = 0)
Step 4: Insert 19
and , insert left child of 22. - Tree:
16 (BF = -1) / \ 14 22 (BF = 1) / 19 (BF = 0) - Balanced (
).
Step 5: Insert 15
and , insert right child of 14. - Tree:
16 (BF = 0) / \ 14 22 \ / 15 19 - Balanced (
).
Step 6: Insert 12
, insert left child of 14. - Subtree at 14:
14 (BF = 0) / \ 12 15 - Tree remains completely balanced!
Step 7: Insert 21
. Insert right child of 19. - Subtree at 22:
22 (BF = 2) <-- Imbalance! Left child (19) has right child (21) => Left-Right (LR) / 19 (BF = -1) \ 21 - Fix: Perform Left-Right (LR) Rotation on node 22:
- Left-rotate node 19: 21 becomes parent of 19.
- Right-rotate node 22: 21 becomes parent of 22.
- Subtree becomes:
21 / \ 19 22
Final AVL Tree Structure:
16 (BF = 0) / \ 14 21 (BF = 0) / \ / \ 12 15 19 22All balance factors are 0. The tree is completely balanced with height
! - Tree:
- [10]
Sort the given data using quick sort algorithm: 17, 8, 91, 10, 111.
View model solution
Quick Sort Algorithm Trace
Given Input Data Array:
[17, 8, 91, 10, 111](, indices to ) Quick Sort follows the Divide-and-Conquer principle using the Lomuto partition scheme (choosing the last element as the
pivot):
Pass 1: Partition Full Array
[17, 8, 91, 10, 111]low = 0,high = 4,pivot = array[high] = 111- Initialize boundary index:
. - Scan
from to : , swap with , swap with , swap with , swap with
- Place pivot at correct sorted position: swap
with : - Swap
with (already at position 4).
- Swap
- Pivot 111 is fixed at index 4. Sub-problem: sort sub-array
[17, 8, 91, 10]from indexto .
Pass 2: Partition Sub-array
[17, 8, 91, 10]low = 0,high = 3,pivot = array[3] = 10- Boundary index:
. - Scan
from to : (No swap) , swap and : - Swap
(17) and (8)
- Swap
(No swap)
- Place pivot: swap
(index 1: value 17) with (index 3: value 10): - Array becomes:
[8, 10, 91, 17]
- Array becomes:
- Pivot 10 is fixed at index 1.
- Left sub-array:
[8](size 1, already sorted). - Right sub-array:
[91, 17](indices 2 to 3).
Pass 3: Partition Sub-array
[91, 17]low = 2,high = 3,pivot = array[3] = 17- Boundary index:
. - Scan
(No swap). - Place pivot: swap
(index 2: value 91) with (index 3: value 17): - Array becomes:
[8, 10, 17, 91, 111]
- Array becomes:
- Pivot 17 is fixed at index 2.
- Sub-array
[91]has size 1 (base case).
Final Sorted Output:
- [10]
Draw a minimum spanning tree of the below graph using Kruskal’s algorithm:
View model solution
Minimum Spanning Tree (MST) Using Kruskal’s Algorithm
A Minimum Spanning Tree (MST) connects all vertices of a connected, edge-weighted undirected graph with the minimum total edge weight and without forming any cycles (
edges). Graph Specification:
Vertices:
( vertices, required MST edges ). Edges with weights:
Step 1: Sort All Edges in Non-Decreasing Order of Weight
Edge Weight Action Reason 1 ACCEPT Connects disjoint sets and . 2 ACCEPT Connects and . 2 ACCEPT Connects and . 3 ACCEPT Connects and . 4 REJECT already connected via path (Forms cycle ). 5 ACCEPT Connects component to . 6 REJECT Cycle . 8 REJECT Cycle . 10 REJECT Cycle .
Resulting MST Edges:
with weight with weight with weight with weight with weight
(A) \ [2] (C) --- [1] --- (B) \ [5] (D) --- [2] --- (E) --- [3] --- (F)Total Minimum Weight of MST:
- [10]
Write an algorithm to perform insertion and deletion operation in a binary search tree.
View model solution
Algorithms for Insertion and Deletion in a Binary Search Tree (BST)
A Binary Search Tree (BST) satisfies the property: for every node
, all keys in the left subtree are smaller ( ) and all keys in the right subtree are greater ( ).
1. Insertion Algorithm in BST
Algorithm BST_Insert(root, key): Input: Root pointer of BST, value key to insert Output: Updated root pointer 1. If root is NULL Then: Create a new node N with data = key, left = NULL, right = NULL Return N 2. If key < root.data Then: root.left = BST_Insert(root.left, key) 3. Else If key > root.data Then: root.right = BST_Insert(root.right, key) 4. Else: // Duplicate key; do not insert (or handle according to policy) Return root 5. Return root- Time Complexity: Average:
, Worst (skewed tree): .
2. Deletion Algorithm in BST
Deletion involves three distinct cases:
- Case 1 (Node is a Leaf): Simply delete the node and set parent pointer to NULL.
- Case 2 (Node has One Child): Bypass the node by linking its parent directly to its child.
- Case 3 (Node has Two Children): Find the node’s Inorder Successor (smallest value in its right subtree), overwrite the node’s data with the successor’s data, and recursively delete the inorder successor from the right subtree.
Algorithm BST_Delete(root, key): Input: Root pointer of BST, value key to delete Output: Updated root pointer 1. If root is NULL Then Return NULL 2. If key < root.data Then: root.left = BST_Delete(root.left, key) 3. Else If key > root.data Then: root.right = BST_Delete(root.right, key) 4. Else: // Found node to delete! // Case 1 & 2: Zero or One Child If root.left is NULL Then: temp = root.right free(root) Return temp Else If root.right is NULL Then: temp = root.left free(root) Return temp // Case 3: Two Children Else: succ = FindMin(root.right) // Inorder successor root.data = succ.data root.right = BST_Delete(root.right, succ.data) 5. Return root - Time Complexity: Average:
- [10]
Illustrate push and pop operation in stack using linked lists.
View model solution
Push and Pop Operations in Stack Using Linked List
When a stack is implemented using a Singly Linked List, dynamic memory allocation allows the stack to grow and shrink dynamically without fixed array limits (eliminating stack overflow unless heap memory is exhausted).
- The
toppointer points to the head node of the linked list. - All insertions (
push) and deletions (pop) take place at the head node in strictlytime.
1. PUSH Operation (
) Inserting new element
valonto the stack:- Allocate memory for a new node:
newNode = new Node(val). - Point
newNode->nextto the currenttop:newNode->next = top. - Update
topto point to the new node:top = newNode.
Before Push(40): TOP ---> [ 30 | next ] ---> [ 20 | next ] ---> [ 10 | NULL ] Step: Allocate newNode [ 40 | next ] newNode->next = TOP TOP = newNode After Push(40): TOP ---> [ 40 | next ] ---> [ 30 | next ] ---> [ 20 | next ] ---> [ 10 | NULL ]
2. POP Operation (
) Removing the top element from the stack:
- Check for Stack Underflow: If
top == NULL, return error. - Store the current top node in a temporary pointer:
temp = top. - Advance
topto the next node:top = top->next. - Retrieve the data and free allocated heap memory:
val = temp->data; delete temp; return val;.
Before Pop(): TOP ---> [ 40 | next ] ---> [ 30 | next ] ---> [ 20 | next ] ---> [ 10 | NULL ] ^ | (temp) Step: TOP = TOP->next delete temp After Pop(): TOP ---> [ 30 | next ] ---> [ 20 | next ] ---> [ 10 | NULL ] Returned Value = 40C++ Implementation:
struct Node { int data; Node* next; Node(int val) : data(val), next(nullptr) {} }; class Stack { private: Node* topNode; public: Stack() : topNode(nullptr) {} void push(int val) { Node* newNode = new Node(val); newNode->next = topNode; topNode = newNode; } int pop() { if (isEmpty()) { cout << "Stack Underflow!\n"; return -1; } Node* temp = topNode; int poppedVal = temp->data; topNode = topNode->next; delete temp; return poppedVal; } bool isEmpty() { return topNode == nullptr; } }; - The