Thursday, 18 January 2024

DSA Short Notes

 

Time complexity refers to the amount of time an algorithm takes to complete as a function of the input size.

Space complexity is the amount of memory space required by an algorithm concerning the input size.

Data structure is a way of organizing and storing data to perform operations efficiently.

Array vs. Linked List:

  • Arrays are contiguous memory locations, while linked lists use pointers.
  • Arrays are preferable when direct access is needed, and linked lists are suitable for dynamic size and frequent insertions/deletions
Recursion is a programming technique where a function calls itself.

Tree Terminology:

  • a. Root: The topmost node.
  • b. Leaf: A node with no children.
  • c. Parent: A node with children.
  • d. Child: A node connected to a parent

Big O notation helps compare algorithms and predict their behavior with large inputs.

Abstract Data Type is a high-level description of a set of operations without specifying the implementation.

Stack vs. Queue:

  • Stack follows Last In First Out (LIFO), while Queue follows First In First Out (FIFO).
  • Stack is suitable for function call tracking, and Queue is used in task scheduling

Pointers store the memory address of variables. In data structures, they facilitate dynamic memory allocation and efficient traversal.

Tree Traversal:

  • Pre-order: Root, Left, Right
  • In-order: Left, Root, Right

Dynamic memory allocation in C involves using functions like malloc() to request memory during program execution. The process typically consists of the following steps: allocation, checking for all allocation success, Usage, deallocation

Hash tables are crucial data structures that provide efficient data retrieval and storage.

A doubly-linked list is a linked list in which each node contains a data element and two pointers, one pointing to the next node (next pointer) and another pointing to the previous node (prev pointer).

Breadth-First Search (BFS):

  • Traversal Order: Level-wise, exploring all neighbors at the current level before moving to the next level.
  • Memory Usage: More memory-intensive due to the need to store all nodes at the current level.
  • Implementation: Typically implemented using a queue.

Depth-First Search (DFS):

  • Traversal Order: Goes as deep as possible before backtracking.
  • Memory Usage: Less memory-intensive as it explores as far as possible along each branch before backtracking.
  • Implementation: Typically implemented using recursion or a stack.

Hashing is a technique that maps data to a fixed-size array called a hash table.

Linear Search when the data is unordered, or the dataset is small.
Binary Search when dealing with large, sorted datasets, as it offers faster search times. find the middle element.

Heap is a specialized tree-based data structure that satisfies the heap property.


DATA STRUCTURE data structure is a way of organizing and storing data in a computer's memory in order to perform efficient operations on that data.

Major Operations : searching, sorting, insertion, updation, deletion

Advantages of Data structures :
Efficiency : makes the programs efficient in term of time and space .
Reusability: multiple client programs can use the data structure.
Abstraction: client does not worry about the implementation part.


Linear data structure
data elements are stored in a linear or sequential order, that is, data is stored in consecutive memory locations.

Non-linear data structure
data represented by a hierarchical order. Examples of non-linear data structure include graphs, trees etc

Arrays are one of the most basic and widely used data structures in C. An array is a collection of elements of the same data type stored in contiguous memory locations
SYNTAX: data_type array_name[size] = {element1, element2, ..., elementN};

Arrays are useful because of sorting, searching, process multiple values quickly

Basic operations:
Traversal - print elements of an array
Insertion - add elements at particular index
Deletion - delete an element at particular index
Search - search an element of given index
update - update an element at particular index





Linked list are dynamic in nature that is memory is allocated as and when required
Linked List can be defined as collection of objects called nodes that are randomly stored in the memory













Monday, 15 January 2024

DSA Practice Question paper

 

Data Structure using C - Question Paper

Time: 3 Hours Maximum Marks: 100


Section A: Descriptive Questions (20 Marks)

Question 1 (2 marks): Explain the concept of time complexity and space complexity in algorithm analysis. Provide a brief example of each.

Ans. Time Complexity and Space Complexity:

  • Time complexity refers to the amount of time an algorithm takes to complete as a function of the input size.
  • Space complexity is the amount of memory space required by an algorithm concerning the input size.
  • Example: Linear Search has a time complexity of O(n), and Bubble Sort has a space complexity of O(1).

Question 2 (2 marks): Define the term "Data Structure." Discuss the importance of selecting an appropriate data structure in programming.

Ans. Data Structure:

  • A data structure is a way of organizing and storing data to perform operations efficiently.
  • Choosing the right data structure is crucial as it impacts the efficiency of operations.

Question 3 (2 marks): Differentiate between an array and a linked list. In what scenarios would you prefer using one over the other?

Ans. Array vs. Linked List:

  • Arrays are contiguous memory locations, while linked lists use pointers.
  • Arrays are preferable when direct access is needed, and linked lists are suitable for dynamic size and frequent insertions/deletions.

Question 4 (2 marks): What is recursion, and how does it work in programming? Provide a simple example of a recursive function.

Ans. Recursion:

  • Recursion is a programming technique where a function calls itself.
  • Example: Factorial function: `int factorial(int n) { return n <= 1 ? 1 : n * factorial(n - 1); }`

Question 5 (2 marks): Explain the purpose of the following terms in the context of trees: a. Root b. Leaf c. Parent d. Child

Ans. Tree Terminology:

  • a. Root: The topmost node.
  • b. Leaf: A node with no children.
  • c. Parent: A node with children.
  • d. Child: A node connected to a parent.

Question 6 (2 marks): Explain the concept of Big O notation in algorithm analysis. How does it help in evaluating the efficiency of algorithms?

Ans. Big O Notation:

  • Big O notation expresses the upper bound of an algorithm's time complexity.
  • Helps compare algorithms and predict their behavior with large inputs.

Question 7 (2 marks): Define the term "Abstract Data Type (ADT)." Provide an example of an ADT and explain its characteristics.

Ans. Abstract Data Type (ADT):

  • ADT is a high-level description of a set of operations without specifying the implementation.
  • Example: Stack - supports push, pop, and isEmpty operations.

Question 8 (2 marks): Differentiate between a stack and a queue. Provide examples of real-world scenarios where each data structure is applicable.

Ans. Stack vs. Queue:

  • Stack follows Last In First Out (LIFO), while Queue follows First In First Out (FIFO).
  • Stack is suitable for function call tracking, and Queue is used in task scheduling.

Question 9 (2 marks): Discuss the importance of pointers in C programming, particularly in the context of data structures.

Ans. Pointers in C:

  • Pointers store the memory address of variables. In data structures, they facilitate dynamic memory allocation and efficient traversal.

Question 10 (2 marks): Explain the concept of "tree traversal" in binary trees. Provide examples of pre-order and in-order traversal.

Ans. Tree Traversal:

  • Pre-order: Root, Left, Right
  • In-order: Left, Root, Right

Section B: Algorithm Design and Analysis (50 Marks)

Question 1 (5 marks): Describe the process of dynamic memory allocation and deallocation in C. Highlight the differences between malloc() and free().

Ans. Dynamic memory allocation in C involves using functions like malloc() to request memory during program execution. The process typically consists of the following steps:

  • Allocation: Use malloc(size) to allocate a block of memory of the specified size.
  • Checking for Allocation Success: Verify if the allocation was successful by checking if the returned pointer is not NULL.
  • Usage: Utilize the allocated memory for storing data.
  • Deallocation: Use free(pointer) to release the allocated memory when it's no longer needed.

Differences between malloc() and free():

  • malloc(size): Allocates a specified amount of memory and returns a pointer to the first byte.
  • free(pointer): Releases the memory pointed to by the given pointer, making it available for reuse.

Question 2 (5 marks): Discuss the significance of hash tables in data structures. Provide an example illustrating the use of a hash table.

Ans. Hash tables are crucial data structures that provide efficient data retrieval and storage. They offer constant-time average complexity for search, insert, and delete operations. A hash table consists of an array and a hash function that maps keys to indices in the array.

Example: Consider a system where you want to store and retrieve the contact details of individuals using their phone numbers. In this scenario, a hash table can be employed, where the phone numbers serve as keys. The hash function could be based on the last few digits of the phone number, and the array would store the corresponding contact details.


Question 3 (5 marks): Explain the concept of a doubly-linked list. Provide code snippets for inserting and deleting nodes in a doubly-linked list.

Ans. A doubly-linked list is a linked list in which each node contains a data element and two pointers, one pointing to the next node (next pointer) and another pointing to the previous node (prev pointer).

Code snippets:

// Node structure for doubly-linked list struct Node { int data; struct Node* next; struct Node* prev; }; // Insertion at the end void insertEnd(struct Node** head, int newData) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); struct Node* last = *head; newNode->data = newData; newNode->next = NULL; if (*head == NULL) { newNode->prev = NULL; *head = newNode; return; } while (last->next != NULL) last = last->next; last->next = newNode; newNode->prev = last; } // Deletion by key void deleteNode(struct Node** head, int key) { struct Node* temp = *head; while (temp != NULL && temp->data != key) temp = temp->next; if (temp == NULL) return; if (temp->prev != NULL) temp->prev->next = temp->next; if (temp->next != NULL) temp->next->prev = temp->prev; free(temp); }



Question 4 (5 marks): Compare and contrast breadth-first search (BFS) and depth-first search (DFS) algorithms. Offer examples of situations where each is preferable.

Ans. Breadth-First Search (BFS):

  • Traversal Order: Level-wise, exploring all neighbors at the current level before moving to the next level.
  • Memory Usage: More memory-intensive due to the need to store all nodes at the current level.
  • Implementation: Typically implemented using a queue.

Depth-First Search (DFS):

  • Traversal Order: Goes as deep as possible before backtracking.
  • Memory Usage: Less memory-intensive as it explores as far as possible along each branch before backtracking.
  • Implementation: Typically implemented using recursion or a stack.

Comparison:

  • Space Complexity: BFS generally requires more memory than DFS.
  • Completeness: BFS is complete for finite graphs; DFS might get stuck in infinite loops.
  • Optimality: BFS is optimal for finding the shortest path in an unweighted graph, while DFS might not be.

Preferable Situations:

  • BFS: Useful for finding the shortest path in an unweighted graph, network broadcasting, and puzzles with multiple solutions.
  • DFS: Suitable for topological sorting, detecting cycles in a graph, and puzzles with a single solution.

Question 5 (5 marks): Discuss the concept of a priority queue. Provide a real-world example where a priority queue would be beneficial.

Ans. Priority Queue:

  • A data structure that stores elements with associated priorities.
  • Elements with higher priorities are served before elements with lower priorities.

Real-world Example: Consider an emergency room where patients are prioritized based on the severity of their condition. A priority queue could be employed to efficiently manage patient queues. Patients with higher medical urgency (higher priority) would be treated before those with lower urgency.


Question 6 (5 marks): Discuss the role of the "malloc" function in dynamic memory allocation in C. Provide an example of its usage.

Ans. malloc Function:

  • malloc (Memory Allocation) is a standard library function in C used for dynamic memory allocation.
  • It allocates a specified number of bytes and returns a pointer to the beginning of the allocated memory.

Example Usage:

#include <stdio.h> #include <stdlib.h> int main() { int *arr; int n = 5; // Allocate memory for an array of 5 integers arr = (int *)malloc(n * sizeof(int)); if (arr == NULL) { printf("Memory allocation failed!"); return 1; // Error code } // Use the allocated memory as needed // Deallocate the memory when done free(arr); return 0; }



Question 7 (5 marks): Explain the concept of hashing. How does collision resolution work in hash tables? Provide an example.

Ans. Hashing Concept:

  • Hashing is a technique that maps data to a fixed-size array called a hash table.
  • It uses a hash function to compute an index into the array where the desired information can be found.

Collision Resolution:

  • Collision occurs when two different keys hash to the same index.
  • Collision resolution techniques include chaining (using linked lists at each array index) and open addressing (finding the next open slot).

Example: Consider a hash table storing employee information. The hash function could be based on employee IDs, and collision resolution might involve chaining. If two employees have the same hash, their information is stored in a linked list at that hash index.


Question 8 (5 marks): Design a C program to implement a circular linked list. Include functions for insertion and deletion of nodes.

#include <stdio.h> #include <stdlib.h> struct Node { int data; struct Node* next; }; // Function to insert a new node at the end void insertEnd(struct Node** head, int newData) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); struct Node* last = *head; newNode->data = newData; newNode->next = *head; if (*head == NULL) { *head = newNode; newNode->next = *head; return; } while (last->next != *head) last = last->next; last->next = newNode; } // Function to delete a node by key void deleteNode(struct Node** head, int key) { if (*head == NULL) return; struct Node* current = *head, *prev = NULL; while (current->data != key) { prev = current; current = current->next; if (current == *head) return; // Key not found, exit to avoid infinite loop } if (current->next == *head && prev == NULL) { free(current); *head = NULL; return; } if (prev == NULL) *head = current->next; prev->next = current->next; free(current); } // Function to display the circular linked list void displayList(struct Node* head) { struct Node* temp = head; if (head != NULL) { do { printf("%d ", temp->data); temp = temp->next; } while (temp != head); } printf("\n"); } int main() { struct Node* head = NULL; insertEnd(&head, 1); insertEnd(&head, 2); insertEnd(&head, 3); insertEnd(&head, 4); printf("Circular Linked List: "); displayList(head); deleteNode(&head, 3); printf("Circular Linked List after deletion: "); displayList(head); return 0; }


Question 9 (5 marks): Compare the time complexities of linear search and binary search algorithms. In what scenarios would you prefer one over the other?

Ans. Linear Search:

  • Time Complexity: O(n) - linear in the size of the array.
  • Suitable for small datasets or unsorted arrays.

Binary Search:

  • Time Complexity: O(log n) - logarithmic, applicable to sorted arrays.
  • Preferable for large sorted datasets, as it significantly reduces the number of comparisons.

Scenario Preference:

  • Use Linear Search when the data is unordered, or the dataset is small.
  • Use Binary Search when dealing with large, sorted datasets, as it offers faster search times.

Question 10 (5 marks): Discuss the concept of a heap in the context of priority queues. How is a heap represented, and what operations can be performed on it?

Ans. Heap Concept:

  • A heap is a specialized tree-based data structure that satisfies the heap property.
  • In a max heap, the key of each node is greater than or equal to the keys of its children.
  • In a min heap, the key of each node is less than or equal to the keys of its children.

Representation:

  • Heaps are commonly represented using arrays. The root of the heap is at index 0, and for any element at index i, its left child is at 2i + 1 and the right child is at 2i + 2.

Operations:

  • Insertion: Add a new element to the heap while maintaining the heap property.
  • Deletion: Remove the root element (max or min) and adjust the heap.
  • Heapify: Convert an array into a heap, ensuring the heap property.

Example: Consider a max heap:

9 / \ 7 5 / \ 3 4



Section C: Programming and Problem Solving(Any Five) (30 Marks)

Question 1 (6 marks): Implement a C program to perform sorting on a doubly-linked list. Explain the logic behind your implementation.

Ans. #include <stdio.h> #include <stdlib.h> struct Node { int data; struct Node* next; struct Node* prev; }; // Function to insert a new node at the end void insertEnd(struct Node** head, int newData) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); struct Node* last = *head; newNode->data = newData; newNode->next = NULL; if (*head == NULL) { newNode->prev = NULL; *head = newNode; return; } while (last->next != NULL) last = last->next; last->next = newNode; newNode->prev = last; } // Function to perform sorting on a doubly-linked list using bubble sort void sortDoublyLinkedList(struct Node** head) { if (*head == NULL || (*head)->next == NULL) return; struct Node *i, *j; int temp; for (i = *head; i->next != NULL; i = i->next) { for (j = i->next; j != NULL; j = j->next) { if (i->data > j->data) { // Swap data temp = i->data; i->data = j->data; j->data = temp; } } } } // Function to display the doubly-linked list void displayList(struct Node* head) { while (head != NULL) { printf("%d ", head->data); head = head->next; } printf("\n"); } int main() { struct Node* head = NULL; // Insert elements into the doubly-linked list insertEnd(&head, 3); insertEnd(&head, 1); insertEnd(&head, 4); insertEnd(&head, 2); printf("Original Doubly-Linked List: "); displayList(head); // Sort the doubly-linked list sortDoublyLinkedList(&head); printf("Sorted Doubly-Linked List: "); displayList(head); return 0; }

Explanation:

  • The program uses the bubble sort algorithm to sort the doubly-linked list.
  • The sortDoublyLinkedList function compares adjacent elements and swaps them if they are in the wrong order.
  • The sorting process is repeated until the entire list is sorted.
  • The displayList function is used to print the elements of the doubly-linked list.

Question 2 (6 marks): Design and implement a binary search tree (BST) in C. Include functions for insertion, deletion, and searching.

Ans. #include <stdio.h> #include <stdlib.h> struct Node { int data; struct Node* left; struct Node* right; }; // Function to create a new node struct Node* createNode(int key) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = key; newNode->left = newNode->right = NULL; return newNode; } // Function to insert a key into the BST struct Node* insertNode(struct Node* root, int key) { if (root == NULL) return createNode(key); if (key < root->data) root->left = insertNode(root->left, key); else if (key > root->data) root->right = insertNode(root->right, key); return root; } // Function to find the minimum value node in a BST struct Node* findMin(struct Node* node) { while (node->left != NULL) node = node->left; return node; } // Function to delete a key from the BST 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 struct Node* temp = findMin(root->right); root->data = temp->data; root->right = deleteNode(root->right, temp->data); } return root; } // Function to search for a key in the BST struct Node* searchNode(struct Node* root, int key) { if (root == NULL || root->data == key) return root; if (key < root->data) return searchNode(root->left, key); else return searchNode(root->right, key); } // Function to print the inorder traversal of the BST void inorderTraversal(struct Node* root) { if (root != NULL) { inorderTraversal(root->left); printf("%d ", root->data); inorderTraversal(root->right); } } int main() { struct Node* root = NULL; root = insertNode(root, 50); insertNode(root, 30); insertNode(root, 20); insertNode(root, 40); insertNode(root, 70); insertNode(root, 60); insertNode(root, 80); printf("Inorder Traversal: "); inorderTraversal(root); printf("\n"); printf("Searching for 40



Question 3 (6 marks): Write a program in C to evaluate a postfix expression using a stack. Explain the algorithm and include error handling mechanisms.

Ans. #include <stdio.h> #include <stdlib.h> #include <ctype.h> #define MAX_SIZE 100 struct Stack { int top; int items[MAX_SIZE]; }; // Function to initialize an empty stack void initialize(struct Stack* stack) { stack->top = -1; } // Function to check if the stack is empty int isEmpty(struct Stack* stack) { return stack->top == -1; } // Function to push an element onto the stack void push(struct Stack* stack, int value) { if (stack->top == MAX_SIZE - 1) { printf("Error: Stack Overflow\n"); exit(1); } stack->items[++stack->top] = value; } // Function to pop an element from the stack int pop(struct Stack* stack) { if (isEmpty(stack)) { printf("Error: Stack Underflow\n"); exit(1); } return stack->items[stack->top--]; } // Function to evaluate a postfix expression int evaluatePostfix(char postfix[]) { struct Stack stack; initialize(&stack); for (int i = 0; postfix[i] != '\0'; i++) { if (isdigit(postfix[i])) push(&stack, postfix[i] - '0'); else { int operand2 = pop(&stack); int operand1 = pop(&stack); switch (postfix[i]) { case '+': push(&stack, operand1 + operand2); break; case '-': push(&stack, operand1 - operand2); break; case '*': push(&stack, operand1 * operand2); break; case '/': if (operand2 == 0) { printf("Error: Division by zero\n"); exit(1); } push(&stack, operand1 / operand2); break; default: printf("Error: Invalid operator\n"); exit(1); } } } return pop(&stack); } int main() { char postfixExpression[] = "23*5+"; int result = evaluatePostfix(postfixExpression); printf("Result of the postfix expression: %d\n", result); return 0; }

Explanation:

  • The program uses a stack to evaluate a postfix expression.
  • It iterates through each character in the postfix expression.
  • If the character is a digit, it is pushed onto the stack.
  • If the character is an operator, two operands are popped from the stack, the operation is performed, and the result is pushed back onto the stack.
  • Error handling mechanisms are included for stack overflow, stack underflow, division by zero, and invalid operators.

Question 4 (6 marks): Given a graph represented as an adjacency matrix, write a C program to perform a depth-first search (DFS). Provide the output for a sample graph.

Ans. #include <stdio.h> #define MAX_VERTICES 5 int visited[MAX_VERTICES] = {0}; void depthFirstSearch(int graph[MAX_VERTICES][MAX_VERTICES], int vertex) { printf("%d ", vertex); visited[vertex] = 1; for (int i = 0; i < MAX_VERTICES; i++) { if (graph[vertex][i] == 1 && !visited[i]) depthFirstSearch(graph, i); } } int main() { int graph[MAX_VERTICES][MAX_VERTICES] = { {0, 1, 1, 0, 0}, {1, 0, 0, 1, 0}, {1, 0, 0, 1, 1}, {0, 1, 1, 0, 1}, {0, 0, 1, 1, 0} }; printf("Depth-First Search starting from vertex 0: "); depthFirstSearch(graph, 0); return 0; }

Output: 
Depth-First Search starting from vertex 0: 0 1 3 2 4

Explanation:

  • The program performs depth-first search (DFS) on a sample graph represented as an adjacency matrix.
  • It starts the traversal from vertex 0 and prints the vertices in the order they are visited.
  • The visited array is used to keep track of visited vertices to avoid revisiting them during the traversal.
  • The output represents the order of vertices visited during the depth-first search.


Question 5 (6 marks): Design and implement a stack using an array in C. Include functions for push, pop, and display.

Ans. #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 100 struct Stack { int top; int items[MAX_SIZE]; }; // Function to initialize an empty stack void initialize(struct Stack* stack) { stack->top = -1; } // Function to check if the stack is empty int isEmpty(struct Stack* stack) { return stack->top == -1; } // Function to check if the stack is full int isFull(struct Stack* stack) { return stack->top == MAX_SIZE - 1; } // Function to push an element onto the stack void push(struct Stack* stack, int value) { if (isFull(stack)) { printf("Error: Stack Overflow\n"); exit(1); } stack->items[++stack->top] = value; } // Function to pop an element from the stack int pop(struct Stack* stack) { if (isEmpty(stack)) { printf("Error: Stack Underflow\n"); exit(1); } return stack->items[stack->top--]; } // Function to display the stack void displayStack(struct Stack* stack) { if (isEmpty(stack)) { printf("Stack is empty\n"); return; } printf("Stack: "); for (int i = 0; i <= stack->top; i++) { printf("%d ", stack->items[i]); } printf("\n"); } int main() { struct Stack stack; initialize(&stack); push(&stack, 1); push(&stack, 2); push(&stack, 3); displayStack(&stack); int popped = pop(&stack); printf("Popped element: %d\n", popped); displayStack(&stack); return 0; }

Explanation:

  • The program demonstrates the implementation of a stack using an array in C.
  • It includes functions for initializing, checking if the stack is empty or full, pushing an element, popping an element, and displaying the stack.
  • Error handling mechanisms are included for stack overflow and underflow.

Question 6 (6 marks): Design and implement a stack using an array in C. Include functions for push, pop, and display.

Ans. #include <stdio.h> #include <stdlib.h> #define MAX_SIZE 100 struct Queue { int front, rear, size; int items[MAX_SIZE]; }; // Function to initialize an empty queue void initialize(struct Queue* queue) { queue->front = queue->rear = -1; queue->size = 0; } // Function to check if the queue is empty int isEmpty(struct Queue* queue) { return queue->size == 0; } // Function to check if the queue is full int isFull(struct Queue* queue) { return queue->size == MAX_SIZE; } // Function to enqueue an element void enqueue(struct Queue* queue, int value) { if (isFull(queue)) { printf("Error: Queue Overflow\n"); exit(1); } if (isEmpty(queue)) queue->front = 0; queue->rear = (queue->rear + 1) % MAX_SIZE; queue->items[queue->rear] = value; queue->size++; } // Function to dequeue an element int dequeue(struct Queue* queue) { if (isEmpty(queue)) { printf("Error: Queue Underflow\n"); exit(1); } int frontItem = queue->items[queue->front]; queue->front = (queue->front + 1) % MAX_SIZE; queue->size--; if (isEmpty(queue)) initialize(queue); return frontItem; } // Function to display the queue void displayQueue(struct Queue* queue) { if (isEmpty(queue)) { printf("Queue is empty\n"); return; } printf("Queue: "); int i = queue->front; do { printf("%d ", queue->items[i]); i = (i + 1) % MAX_SIZE; } while (i != (queue->rear + 1) % MAX_SIZE); printf("\n"); } int main() { struct Queue queue; initialize(&queue); enqueue(&queue, 1); enqueue(&queue, 2); enqueue(&queue, 3); displayQueue(&queue); int dequeued = dequeue(&queue); printf("Dequeued element: %d\n", dequeued); displayQueue(&queue); return 0; }



Question 7 (6 marks): Write a program in C to find the shortest path in a weighted graph using Dijkstra's algorithm. Explain the algorithm and provide a sample graph for testing.

Ans. #include <stdio.h> #include <stdlib.h> #include <limits.h> #define V 5 // Number of vertices in the graph int minDistance(int dist[], int sptSet[]) { int min = INT_MAX, min_index; for (int v = 0; v < V; v++) if (sptSet[v] == 0 && dist[v] <= min) min = dist[v], min_index = v; return min_index; } void printSolution(int dist[]) { printf("Vertex Distance from Source\n"); for (int i = 0; i < V; i++) printf("%d \t\t %d\n", i, dist[i]); } void dijkstra(int graph[V][V], int src) { int dist[V]; int sptSet[V]; for (int i = 0; i < V; i++) dist[i] = INT_MAX, sptSet[i] = 0; dist[src] = 0; for (int count = 0; count < V - 1; count++) { int u = minDistance(dist, sptSet); sptSet[u] = 1; for (int v = 0; v < V; v++) if (!sptSet[v] && graph[u][v] && dist[u] != INT_MAX && dist[u] + graph[u][v] < dist[v]) dist[v] = dist[u] + graph[u][v]; } printSolution(dist); } int main() { int graph[V][V] = {{0, 2, 0, 6, 0}, {2, 0, 3, 8, 5}, {0, 3, 0, 0, 7}, {6, 8, 0, 0, 9}, {0, 5, 7, 9, 0}}; dijkstra(graph, 0); return 0; }

Explanation:

  • Dijkstra's algorithm finds the shortest path in a weighted graph from a source vertex to all other vertices.
  • The program initializes an array dist to store the shortest distances from the source vertex to each vertex in the graph.
  • It maintains a set sptSet to keep track of vertices included in the shortest path tree.
  • The minDistance function finds the vertex with the minimum distance value.
  • The dijkstra function iteratively selects the minimum distance vertex, updates the distance values of its adjacent vertices, and repeats until all vertices are included in the shortest path tree.
  • The printSolution function displays the shortest distances from the source vertex to all vertices.


Question 8 (6 marks): Implement a binary search tree (BST) in C and write a function to check if it is a valid BST. Explain the logic behind your solution.

Ans. #include <stdio.h> #include <stdlib.h> #include <limits.h> struct Node { int data; struct Node* left; struct Node* right; }; struct Node* createNode(int key) { struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); newNode->data = key; newNode->left = newNode->right = NULL; return newNode; } int isBSTUtil(struct Node* root, int minValue, int maxValue) { if (root == NULL) return 1; if (root->data < minValue || root->data > maxValue) return 0; return isBSTUtil(root->left, minValue, root->data - 1) && isBSTUtil(root->right, root->data + 1, maxValue); } int isBST(struct Node* root) { return isBSTUtil(root, INT_MIN, INT_MAX); } int main() { struct Node* root = createNode(2); root->left = createNode(1); root->right = createNode(3); // Uncomment the following line to make the tree invalid // root->right->left = createNode(1); if (isBST(root)) printf("The tree is a valid BST.\n"); else printf("The tree is not a valid BST.\n"); return 0; }

Explanation:

  • The program defines a structure Node for a binary tree node.
  • The createNode function creates a new node with the given key.
  • The isBSTUtil function recursively checks if the tree rooted at a given node is a valid BST within a specified range (minValue and maxValue).
  • The isBST function is a wrapper function that calls isBSTUtil with initial minimum and maximum values.
  • The main function creates a sample BST and checks whether it is a valid BST or not.
  • The logic checks if the value of each node is within the permissible range for a valid BST.