0% found this document useful (0 votes)
10 views17 pages

Dsbig 5

The document outlines various algorithmic notations, including Big-O, Omega, Theta, Little-o, and Little-omega, with examples of their applications in time complexity analysis. It also discusses the time complexity of matrix multiplication, pattern matching algorithms, and comparisons of different linked list types, along with their advantages and disadvantages. Additionally, it covers queue operations, applications of queues, and provides a step-by-step insertion process for a B-Tree of order 5.

Uploaded by

sanofficial341
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
10 views17 pages

Dsbig 5

The document outlines various algorithmic notations, including Big-O, Omega, Theta, Little-o, and Little-omega, with examples of their applications in time complexity analysis. It also discusses the time complexity of matrix multiplication, pattern matching algorithms, and comparisons of different linked list types, along with their advantages and disadvantages. Additionally, it covers queue operations, applications of queues, and provides a step-by-step insertion process for a B-Tree of order 5.

Uploaded by

sanofficial341
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 17

17(a) Algorithmic Notations with Examples (5 Marks)

1. Big-O Notation (O) – Upper Bound


Definition: Describes the worst-case time complexity.

Example:

Linear Search: O(n) (element may be at the end).

Binary Search: O(log n) (optimal for sorted arrays).

2. Omega Notation (Ω) – Lower Bound


Definition: Describes the best-case time complexity.

Example:

Bubble Sort: Ω(n) (if the array is already sorted).

3. Theta Notation (Θ) – Tight Bound


Definition: Describes both upper and lower bounds (exact growth rate).

Example:

Merge Sort: Θ(n log n) (always splits input equally).

4. Little-o Notation (o) – Strict Upper Bound


Definition: f(n) grows strictly slower than g(n).

Example:

2n = o(n²) because 2n grows slower than n².

5. Little-omega Notation (ω) – Strict Lower Bound


Definition: f(n) grows strictly faster than g(n).

Example:

n² = ω(n log n) because n² grows faster.

17(b) Time Complexity of Matrix Multiplication (5 Marks)


Given: Two matrices A (m × n) and B (n × p).
Operation Count:
Total Multiplications: m × n × p (each element of A multiplies with B’s column).

Total Additions: m × (n - 1) × p (summing products for each cell in C).

Step Count:
Pseudocode:

c
for (i = 0; i < m; i++) { // m iterations
for (j = 0; j < p; j++) { // p iterations
C[i][j] = 0;
for (k = 0; k < n; k++) { // n iterations
C[i][j] += A[i][k] * B[k][j]; // 1 multiplication + 1 addition
}
}
}
Total Steps:

Outer Loop (i): m times.

Middle Loop (j): p times.

Inner Loop (k): n times.

Total: m × p × n multiplications + m × p × (n - 1) additions ≈ O(mnp).

Example:
Multiply A[2×3] and B[3×2]:

Multiplications: 2 × 3 × 2 = 12.

Additions: 2 × 2 × 2 = 8 (since n-1 = 2).

Total Operations: 12 + 8 = 20.

Key Points:
Standard Time Complexity: O(n³) for square matrices (m = n = p).

Optimized Algorithms: Strassen’s algorithm reduces to O(n^2.81).

Key Takeaways
Algorithmic Notations:

O for worst-case, Ω for best-case, Θ for exact growth.

Matrix Multiplication:

Brute-force: O(n³) for square matrices.

Operation Count: m × n × p multiplications.

18(a) Pattern Matching & Naive Pattern Searching Algorithm (5 Marks)


Pattern Matching:
Definition: Finding the starting positions of a substring (pattern) within a main string (text).

Example:

Text: "ABABDABACDABABCABAB"

Pattern: "ABABC" → Found at index 10.

Naive Pattern Searching Algorithm:


Algorithm Steps:

Slide the pattern over the text one by one.

For each position i in the text, check if the pattern matches starting at i.
If a mismatch occurs, shift the pattern by 1 position.

Pseudocode:

c
void naiveSearch(char *text, char *pattern) {
int n = strlen(text);
int m = strlen(pattern);
for (int i = 0; i <= n - m; i++) {
int j;
for (j = 0; j < m; j++) {
if (text[i + j] != pattern[j])
break;
}
if (j == m) {
printf("Pattern found at index %d\n", i);
}
}
}
Time Complexity:

Worst Case: O(m × n) (e.g., text = "AAAAA", pattern = "AAA").

Best Case: O(n) (pattern found at the first index).

18(b) C Program to Find Substring (5 Marks)


Program:
c
#include <stdio.h>
#include <string.h>

void findSubstring(char *str, char *sub) {


int strLen = strlen(str);
int subLen = strlen(sub);
int found = 0;

for (int i = 0; i <= strLen - subLen; i++) {


int j;
for (j = 0; j < subLen; j++) {
if (str[i + j] != sub[j])
break;
}
if (j == subLen) {
printf("Substring found at index: %d\n", i);
found = 1;
}
}
if (!found) {
printf("Substring not found.\n");
}
}

int main() {
char text[100], pattern[100];
printf("Enter the main string: ");
fgets(text, sizeof(text), stdin);
text[strcspn(text, "\n")] = ’\0’; // Remove newline

printf("Enter the substring to find: ");


fgets(pattern, sizeof(pattern), stdin);
pattern[strcspn(pattern, "\n")] = ’\0’;

findSubstring(text, pattern);
return 0;
}
Explanation:
Input Handling:

fgets reads strings (including spaces).

strcspn removes the trailing newline.

Substring Search:

Outer loop slides the substring over the text.

Inner loop checks for a match.

Output: Prints all starting indices where the substring is found.

Example:
Input:

Text: "Hello World"

Substring: "llo"

Output: Substring found at index: 2

Key Takeaways
Naive Algorithm:

Simple but inefficient for large texts (O(mn) time).

Optimizations: Use KMP (O(n)) or Boyer-Moore.

C Program:

Handles spaces in input strings.

Works for case-sensitive matches.

19(a) Comparison of Singly, Doubly, and Circular Linked Lists (5 Marks)


Feature Singly Linked List Doubly Linked List Circular Linked List
Node Structure data, next pointer data, next, prev pointers data, next (last node points to head)
Traversal Direction Forward only Forward and backward Forward (loop)
Memory Overhead Low (1 pointer per node) High (2 pointers per node) Low (1 pointer per node)
Insertion/Deletion O(1) at head, O(n) elsewhere O(1) at head/tail, O(n) in middle O(1) at head/tail
Applications Stacks, Queues (FIFO) Browser history, Undo/Redo Round-robin scheduling
Example 10 → 20 → 30 → NULL NULL ← 10 ↔ 20 ↔ 30 → NULL 10 → 20 → 30 → (back to 10)
19(b) Advantages & Disadvantages of Doubly Linked List (5 Marks)
Advantages:
Bidirectional Traversal:

Can move forward (next) and backward (prev).

Example: Browser history navigation (back/forward buttons).

Efficient Deletions:

O(1) deletion at head/tail (no need to traverse).

Flexible Insertions:

Easier to insert before/after a node without full traversal.

Disadvantages:
Higher Memory Usage:

Stores two pointers (next + prev) per node → 33% more memory than singly linked lists.

Complex Implementation:

More pointer manipulations (e.g., updating prev and next during insertions/deletions).

Slower Operations:

Extra pointer updates slightly slow down insert/delete compared to singly linked lists.

Key Trade-off:
Use Case: Choose doubly linked lists when backward traversal is needed (e.g., undo/redo features).

Avoid: When memory is critical (e.g., embedded systems).

Key Takeaways
Singly Linked List:

Simple, low memory, but no backward traversal.

Doubly Linked List:

Flexible but memory-heavy.

Circular Linked List:

End loops to head, useful for cyclic data.


20(a) Algorithm to Delete a Node by Item in a Singly Linked List (5 Marks)
Algorithm:
Start at the head node.

Check if the list is empty:

If head == NULL, print "List is empty" and exit.

Handle head deletion:

If head->data == item, update head = head->next and free the old head.

Traverse the list:

Use current and prev pointers:

prev trails current to re-link nodes after deletion.

While current != NULL:

If current->data == item:

Set prev->next = current->next.

Free current.

Exit.

Else, move prev = current and current = current->next.

If item not found, print "Item not in list".

Pseudocode:
c
void deleteNode(struct Node **head, int item) {
if (*head == NULL) {
printf("List is empty.\n");
return;
}
struct Node *current = *head;
struct Node *prev = NULL;

// Case 1: Delete head


if (current->data == item) {
*head = current->next;
free(current);
return;
}

// Traverse list
while (current != NULL && current->data != item) {
prev = current;
current = current->next;
}
// Case 2: Item not found
if (current == NULL) {
printf("Item not found.\n");
return;
}

// Case 3: Delete middle/last node


prev->next = current->next;
free(current);
}
Time Complexity:
Best Case: O(1) (item at head).

Worst Case: O(n) (item at tail or absent).

20(b) Algorithm to Convert Infix to Prefix (5 Marks)


Steps:
Reverse the infix expression and replace ( with ) and vice versa.

Convert to postfix using a stack:

Operands → Output.

Operators → Push to stack if higher precedence than top.

Pop stack to output if lower precedence or ).

Reverse the postfix result to get prefix.

Pseudocode:
c
#include <stdio.h>
#include <string.h>
#include <ctype.h>

#define MAX 100

char stack[MAX];
int top = -1;

void push(char c) { stack[++top] = c; }


char pop() { return stack[top--]; }

int precedence(char op) {


switch(op) {
case ’+’: case ’-’: return 1;
case ’*’: case ’/’: return 2;
case ’^’: return 3;
default: return 0;
}
}

void infixToPrefix(char *infix, char *prefix) {


char temp[MAX], postfix[MAX];
int i = 0, j = 0;
// Step 1: Reverse infix and swap parentheses
strrev(infix);
for (int k = 0; infix[k]; k++) {
if (infix[k] == ’(’) infix[k] = ’)’;
else if (infix[k] == ’)’) infix[k] = ’(’;
}

// Step 2: Convert to postfix


while (infix[i]) {
if (isalnum(infix[i])) {
postfix[j++] = infix[i++];
} else if (infix[i] == ’(’) {
push(infix[i++]);
} else if (infix[i] == ’)’) {
while (stack[top] != ’(’) {
postfix[j++] = pop();
}
pop(); // Remove ’(’
i++;
} else {
while (top != -1 && precedence(stack[top]) >= precedence(infix[i])) {
postfix[j++] = pop();
}
push(infix[i++]);
}
}
while (top != -1) {
postfix[j++] = pop();
}
postfix[j] = ’\0’;

// Step 3: Reverse postfix to get prefix


strcpy(prefix, strrev(postfix));
}

int main() {
char infix[MAX] = "(A+B)*C-D";
char prefix[MAX];
infixToPrefix(infix, prefix);
printf("Prefix: %s\n", prefix); // Output: "-*+ABCD"
return 0;
}
Example:
Infix: (A + B) * C - D

Reversed: D - C ) B + A (

Postfix: D C B A + * -

Prefix: - * + A B C D

Time Complexity: O(n) (n = expression length).


Key Takeaways
Linked List Deletion:
Handle head, middle, and tail cases separately.

Infix to Prefix:

Reverse → Postfix → Reverse with stack precedence checks.

21(a) Algorithm for Insert (Enqueue) and Delete (Dequeue) Operations on a Queue (5 Marks)
Queue Properties:
FIFO (First-In-First-Out) structure.

Operations:

Enqueue: Insert at the rear.

Dequeue: Delete from the front.

Algorithm for Enqueue:


Check if the queue is full (for fixed-size queues).

Create a new node with the given data.

If the queue is empty, set both front and rear to the new node.

Else, link rear->next to the new node and update rear = newNode.

Algorithm for Dequeue:


Check if the queue is empty (front == NULL).

Store front node in a temporary variable.

Update front to front->next.

If front becomes NULL, set rear = NULL (queue is now empty).

Free the temporary node.

Pseudocode (Linked List Implementation):


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

struct Node {
int data;
struct Node *next;
};

struct Node *front = NULL;


struct Node *rear = NULL;

void enqueue(int value) {


struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
newNode->data = value;
newNode->next = NULL;

if (rear == NULL) { // Queue is empty


front = rear = newNode;
} else {
rear->next = newNode;
rear = newNode;
}
}

void dequeue() {
if (front == NULL) {
printf("Queue is empty.\n");
return;
}
struct Node *temp = front;
front = front->next;
if (front == NULL) {
rear = NULL; // Reset rear if queue is now empty
}
free(temp);
}
Time Complexity:
Enqueue: O(1) (constant time).

Dequeue: O(1) (constant time).

21(b) Applications of Queues (5 Marks)


1. Operating Systems:
CPU Scheduling: Processes are scheduled in FIFO order (e.g., Round-Robin).

Print Spooling: Print jobs are queued to avoid collision.

2. Network Traffic Management:


Packet Buffering: Data packets are queued in routers for orderly transmission.

3. Breadth-First Search (BFS):


Graph Traversal: Nodes are explored level-by-level using a queue.

4. Real-World Systems:
Ticket Counters: Customers are served in arrival order.

Call Centers: Callers wait in a queue for the next available agent.

5. Data Buffering:
Streaming Services: Frames are buffered in queues for smooth playback.

Example Scenarios:
Application Queue Usage
CPU Scheduling Manages processes waiting for CPU time.
BFS Algorithm Tracks nodes to visit next in a graph.
Print Spooler Orders print jobs to avoid conflicts.
Key Points:
Queues ensure orderly processing in time-sensitive systems.
Used where FIFO service is critical (e.g., scheduling, buffering).

Key Takeaways
Queue Operations:

Enqueue adds to the rear, Dequeue removes from the front.

O(1) time for both operations in linked list implementation.

Applications:

OS, Networking, BFS, and real-world systems rely on queues for FIFO management.

22. B-Tree of Order 5: Insertion of 81, 7, 49, 61, and 30 (10 Marks)
B-Tree Properties (Order 5):
Maximum Keys per Node: 4 (since order m = 5 m-1 keys).

Minimum Keys per Node (Non-Root): 2 (m/2 - 1).

Children per Node: ≤ 5.

Initial B-Tree:
(Assume an empty tree or provide the initial structure if given.)

Step-by-Step Insertions:
Insert 81:

Action: Becomes the root.

text
[81]
Insert 7:

Action: Added to the root (sorted order).

text
[7, 81]
Insert 49:

Action: Added to the root (sorted order).

text
[7, 49, 81]
Insert 61:

Action: Added to the root (sorted order).

text
[7, 49, 61, 81]
Insert 30:

Issue: Root is full (4 keys).


Split Root:

Median key (49) moves up to a new root.

Left child: [7, 30]

Right child: [61, 81]

text
[49]
/ \
[7, 30] [61, 81]
Final B-Tree After Insertions:
text
[49]
/ \
[7, 30] [61, 81]
C Code for B-Tree Insertion (Simplified for Order 5):
c
#include <stdio.h>
#include <stdlib.h>
#define MAX_KEYS 4

struct BTreeNode {
int keys[MAX_KEYS];
struct BTreeNode *children[MAX_KEYS + 1];
int num_keys;
int is_leaf;
};

struct BTreeNode *root = NULL;

// Function to create a new B-Tree node


struct BTreeNode *createNode(int is_leaf) {
struct BTreeNode *newNode = (struct BTreeNode *)malloc(sizeof(struct BTreeNode));
newNode->num_keys = 0;
newNode->is_leaf = is_leaf;
for (int i = 0; i < MAX_KEYS + 1; i++) {
newNode->children[i] = NULL;
}
return newNode;
}

// Function to split a full child during insertion


void splitChild(struct BTreeNode *parent, int index) {
struct BTreeNode *child = parent->children[index];
struct BTreeNode *newNode = createNode(child->is_leaf);
newNode->num_keys = MAX_KEYS / 2;

// Copy the right half of the child to the new node


for (int i = 0; i < MAX_KEYS / 2; i++) {
newNode->keys[i] = child->keys[i + MAX_KEYS / 2 + 1];
}
if (!child->is_leaf) {
for (int i = 0; i < MAX_KEYS / 2 + 1; i++) {
newNode->children[i] = child->children[i + MAX_KEYS / 2 + 1];
}
}
child->num_keys = MAX_KEYS / 2;

// Shift parent’s children to make space


for (int i = parent->num_keys; i > index; i--) {
parent->children[i + 1] = parent->children[i];
}
parent->children[index + 1] = newNode;

// Shift parent’s keys and insert the median key


for (int i = parent->num_keys - 1; i >= index; i--) {
parent->keys[i + 1] = parent->keys[i];
}
parent->keys[index] = child->keys[MAX_KEYS / 2];
parent->num_keys++;
}

// Function to insert a key into a non-full node


void insertNonFull(struct BTreeNode *node, int key) {
int i = node->num_keys - 1;
if (node->is_leaf) {
while (i >= 0 && key < node->keys[i]) {
node->keys[i + 1] = node->keys[i];
i--;
}
node->keys[i + 1] = key;
node->num_keys++;
} else {
while (i >= 0 && key < node->keys[i]) {
i--;
}
i++;
if (node->children[i]->num_keys == MAX_KEYS) {
splitChild(node, i);
if (key > node->keys[i]) {
i++;
}
}
insertNonFull(node->children[i], key);
}
}

// Function to insert a key into the B-Tree


void insert(int key) {
if (root == NULL) {
root = createNode(1);
root->keys[0] = key;
root->num_keys = 1;
} else {
if (root->num_keys == MAX_KEYS) {
struct BTreeNode *newRoot = createNode(0);
newRoot->children[0] = root;
splitChild(newRoot, 0);
root = newRoot;
}
insertNonFull(root, key);
}
}

// Function to print the B-Tree (inorder)


void printBTree(struct BTreeNode *node) {
if (node != NULL) {
for (int i = 0; i < node->num_keys; i++) {
if (!node->is_leaf) {
printBTree(node->children[i]);
}
printf("%d ", node->keys[i]);
}
if (!node->is_leaf) {
printBTree(node->children[node->num_keys]);
}
}
}

int main() {
int keys[] = {81, 7, 49, 61, 30};
for (int i = 0; i < 5; i++) {
insert(keys[i]);
}
printf("B-Tree after insertions: ");
printBTree(root);
return 0;
}
Output:
text
B-Tree after insertions: 7 30 49 61 81
Key Points:
Splitting Nodes:

When a node exceeds 4 keys, it splits into two nodes, promoting the median key.

Balanced Structure:

All leaf nodes remain at the same level.

Time Complexity:

Insertion: O(log n) (due to balanced nature).

23(a) Insert and Delete Operations in a Red-Black Tree (5 Marks)


Red-Black Tree Properties:
Every node is Red or Black.

The root is always Black.

No two adjacent Red nodes (Red node cannot have a Red parent or child).
Every path from a node to its descendant NULL leaves has the same number of Black nodes.

Insertion Operation:
Steps:

Insert the new node as in a BST and color it Red.

Fix Violations (if any) by recolorings/rotations.

Example: Insert 10, 20, 30 into an empty tree.

Insert 10 (Root, colored Black).

text
10(B)
Insert 20 (Red, no violation).

text
10(B)
\
20(R)
Insert 30 (Violation: Two Reds in a row).

Recolor 20 to Black and rotate left at 10.

text
20(B)
/ \
10(R) 30(R)

text

---

#### **Deletion Operation**:


**Steps**:
1. **Delete** the node as in a BST.
2. **Fix Violations** (if any) using rotations/recolorings.

**Example**: Delete ‘10‘ from the tree above.


1. Remove ‘10‘ (replaced by ‘NULL‘).
text
20(B)
\
30(R)
text
2. **Recolor** ‘30‘ to Black to maintain Black height.
text
20(B)
\
30(B)
text

---
### **23(b) Depth-First Search (DFS) Algorithm in C (5 Marks)**

#### **Algorithm**:
1. **Start** at a node, mark it as visited.
2. **Recursively visit** all unvisited adjacent nodes.

#### **C Code**:


‘‘‘c
#include <stdio.h>
#include <stdlib.h>
#define MAX 100

int visited[MAX]; // Visited array

struct Node {
int vertex;
struct Node* next;
};

struct Graph {
int numVertices;
struct Node** adjLists;
};

// Create a node
struct Node* createNode(int v) {
struct Node* newNode = malloc(sizeof(struct Node));
newNode->vertex = v;
newNode->next = NULL;
return newNode;
}

// Create a graph
struct Graph* createGraph(int vertices) {
struct Graph* graph = malloc(sizeof(struct Graph));
graph->numVertices = vertices;
graph->adjLists = malloc(vertices * sizeof(struct Node*));
for (int i = 0; i < vertices; i++) {
graph->adjLists[i] = NULL;
visited[i] = 0; // Initialize as unvisited
}
return graph;
}

// Add edge
void addEdge(struct Graph* graph, int src, int dest) {
struct Node* newNode = createNode(dest);
newNode->next = graph->adjLists[src];
graph->adjLists[src] = newNode;
}

// DFS function
void DFS(struct Graph* graph, int vertex) {
visited[vertex] = 1;
printf("Visited %d\n", vertex);
struct Node* temp = graph->adjLists[vertex];
while (temp != NULL) {
int adjVertex = temp->vertex;
if (!visited[adjVertex]) {
DFS(graph, adjVertex);
}
temp = temp->next;
}
}

int main() {
struct Graph* graph = createGraph(4);
addEdge(graph, 0, 1);
addEdge(graph, 0, 2);
addEdge(graph, 1, 2);
addEdge(graph, 2, 3);

printf("DFS Traversal:\n");
DFS(graph, 0); // Start from vertex 0
return 0;
}
Output:
text
DFS Traversal:
Visited 0
Visited 2
Visited 3
Visited 1
Time Complexity:
O(V + E) (V = vertices, E = edges).

Key Takeaways
Red-Black Tree:

Insertion/Deletion may require rotations and recolorings to maintain balance.

DFS:

Uses recursion (or a stack) to explore deep paths first.

Applications: Maze solving, cycle detection.

You might also like