0% found this document useful (0 votes)
18 views9 pages

Data Structures Important Questions

Uploaded by

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

Data Structures Important Questions

Uploaded by

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

Equivalence Class and Linked Lists

An equivalence class is a subset of elements from a larger set, where all elements in the subset are related to
each other by an equivalence relation. In computer science, equivalence classes can be used to group objects
that share common properties or attributes. When managing equivalence classes, linked lists can be a practical
data structure for representing and managing these groups.

Equivalence Class Application Using Linked Lists

1. Representation: Each equivalence class can be represented as a linked list, where each node in the list
corresponds to an element of the class.
o The linked list makes it easy to group elements, traverse through them, and dynamically add or
remove elements based on changes in the relationship.
2. Union-Find Data Structure (Disjoint Set): A common application of equivalence classes is in the
Union-Find or Disjoint Set data structure, which is used to determine whether two elements are in the
same set (equivalence class).
o Here, each set (or class) is often represented using a linked list, where each node points to the
next element in the set, and each set is identified by a unique representative element.
o Linked lists make it easy to merge two sets, as merging involves linking one set to another by
adjusting the pointers.

Example:

Consider a situation where people are grouped based on whether they are friends (friendship is an equivalence
relation). If Alice, Bob, and Carol are friends, they can be represented as nodes in a linked list. If a new person,
Dave, becomes friends with Carol, Dave can be added to the linked list easily by linking Dave's node to Carol's.

In summary, linked lists are useful for equivalence class management due to their dynamic nature and efficient
element grouping.

Sparse Matrix and Linked Lists

A sparse matrix is a matrix where the majority of its elements are zero. Storing a sparse matrix in a traditional
2D array wastes a lot of memory because we are storing many zero values. Instead, we can use linked lists to
store only the non-zero elements, significantly saving memory space.

Sparse Matrix Representation Using Linked Lists

In this method, linked lists are used to store only the non-zero elements, along with their row and column
indices. There are several ways to represent a sparse matrix using linked lists, but the most common is a list of
linked lists or a triplet representation.

1. Linked List of Linked Lists (Row-wise or Column-wise)

 Each row (or column) of the sparse matrix is represented as a linked list, and each non-zero element in
that row (or column) is stored as a node in the list. Each node contains:
o The value of the non-zero element.
o The column index (if row-wise) or the row index (if column-wise) to locate the element.
o A pointer to the next element in the same row (or column).
2. Triplet Representation

 Each non-zero element is stored as a node containing three pieces of information:


o The row index of the element.
o The column index of the element.
o The value of the element.
 These nodes can then be stored in a single linked list or organized by row or column using multiple
linked lists.

Example:

Consider a 4x4 sparse matrix:

0 0 3 0
0 4 0 0
0 0 0 5
1 0 0 0

Using a row-wise linked list of linked lists:

 Row 1: (2, 3)
 Row 2: (1, 4)
 Row 3: (3, 5)
 Row 4: (0, 1)

Each row would be represented as a linked list where each node contains the column index and the non-zero
value.

Advantages of Using Linked Lists in Sparse Matrices

1. Memory Efficiency: Linked lists allow us to store only non-zero elements, saving significant memory
when dealing with sparse data.
2. Dynamic Size: The size of the linked list automatically adjusts as elements are added or removed, which
is useful when the matrix structure is subject to change.
3. Efficient Operations: Searching for non-zero elements, inserting new non-zero values, or deleting
existing values can be efficiently handled by linked lists.

Sparse Matrix in Real-World Applications

 Graph Representation: Sparse matrices are often used to represent adjacency matrices for graphs,
where each node has very few connections compared to the total number of nodes.
 Machine Learning: In areas like natural language processing and recommendation systems, sparse
matrices are used to represent high-dimensional data, such as word frequencies or user-item interactions.
MERGE TWO LISTS INTO A SORTED LIST

Node* mergeSortedLists(Node* head1, Node* head2) {


// Create a dummy node to serve as the start of the merged list
Node dummy;
Node* tail = &dummy;
dummy.next = NULL;

// While both lists have nodes remaining


while (head1 != NULL && head2 != NULL) {
if (head1->data <= head2->data) {
tail->next = head1;
head1 = head1->next;
} else {
tail->next = head2;
head2 = head2->next;
}
tail = tail->next;
}

// Attach the remaining nodes


if (head1 != NULL) {
tail->next = head1;
} else {
tail->next = head2;
}

// The merged list is next to the dummy node


return dummy.next;
}
1. Inorder Traversal (Left, Root, Right):

In an inorder traversal, the left subtree is visited first, followed by the root, and then the right subtree. The non-
recursive version uses a stack to simulate recursion.

Algorithm:

1. Initialize an empty stack.


2. Set the current node to the root.
3. Push all left nodes into the stack until you reach a NULL.
4. Pop from the stack, visit the node, and move to the right subtree.
5. Repeat the process until both the stack is empty and the current node is NULL.

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

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

struct Node *createNode(int data) {


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

void inorder(struct Node *root) {


struct Node *stack[100];
int top = -1;
struct Node *current = root;

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


// Reach the leftmost node
while (current != NULL) {
stack[++top] = current;
current = current->left;
}

// Current must be NULL at this point


current = stack[top--];
printf("%d ", current->data);

// Visit the right subtree


current = current->right;
}
}

int main() {
struct Node *root = createNode(50);
root->left = createNode(30);
root->right = createNode(70);
root->left->left = createNode(20);
root->left->right = createNode(40);
root->right->left = createNode(60);
root->right->right = createNode(80);

printf("Inorder traversal: ");


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

return 0;
}
2. Preorder Traversal (Root, Left, Right):

In preorder traversal, the root is visited first, followed by the left subtree, and then the right subtree. A stack is
used to simulate recursion.

Algorithm:

1. Initialize an empty stack and push the root node.


2. While the stack is not empty:
o Pop the node from the stack and visit it.
o Push its right child (if exists), then its left child (if exists) to the stack.
o Repeat until the stack is empty.

void preorder(struct Node *root) {


if (root == NULL) return;

struct Node *stack[100];


int top = -1;
stack[++top] = root;

while (top != -1) {


struct Node *current = stack[top--];
printf("%d ", current->data);

if (current->right != NULL) stack[++top] = current->right;


if (current->left != NULL) stack[++top] = current->left;
}
}

Postorder Traversal (Left, Right, Root):

Postorder traversal visits the left subtree first, followed by the right subtree, and finally the root. Non-recursive
postorder traversal can be tricky and usually involves two stacks.

Algorithm (Using Two Stacks):

1. Initialize two empty stacks.


2. Push the root to the first stack.
3. While the first stack is not empty:
o Pop the node from the first stack and push it to the second stack.
o Push the left and right children of the popped node to the first stack.
4. Once done, pop from the second stack to print nodes in postorder.

void postorder(struct Node *root) {


if (root == NULL) return;

struct Node *stack1[100], *stack2[100];


int top1 = -1, top2 = -1;

// Push the root to the first stack


stack1[++top1] = root;

// Process all nodes


while (top1 != -1) {
struct Node *current = stack1[top1--];
stack2[++top2] = current;

if (current->left != NULL) stack1[++top1] = current->left;


if (current->right != NULL) stack1[++top1] = current->right;
}

// Pop all nodes from the second stack


while (top2 != -1) {
struct Node *node = stack2[top2--];
printf("%d ", node->data);
}
}

Level Order Traversal (Breadth-First Traversal):

Level order traversal visits nodes level by level from left to right. It uses a queue instead of a stack.

Algorithm:

1. Initialize an empty queue and enqueue the root node.


2. While the queue is not empty:
o Dequeue a node and visit it.
o Enqueue its left child (if exists) and right child (if exists).
o Repeat until the queue is empty.

#include <stdio.h>
#define MAX 100

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

struct Node *createNode(int data) {


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

void levelOrder(struct Node *root) {


if (root == NULL) return;

struct Node *queue[MAX];


int front = 0, rear = 0;

queue[rear++] = root;

while (front < rear) {


struct Node *current = queue[front++];

printf("%d ", current->data);


if (current->left != NULL) queue[rear++] = current->left;
if (current->right != NULL) queue[rear++] = current->right;
}
}

int main() {
struct Node *root = createNode(50);
root->left = createNode(30);
root->right = createNode(70);
root->left->left = createNode(20);
root->left->right = createNode(40);
root->right->left = createNode(60);
root->right->right = createNode(80);

printf("Level order traversal: ");


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

return 0;
}

State any 2 conditions that need to be satisfied for implementing recursion using stacks

 Base Condition: There must be a condition to stop the recursion, called the base condition, where the
function no longer makes recursive calls. Without this, the recursion would continue indefinitely, causing a
stack overflow.

 Recursive Relation: The recursive function must be designed in a way that each recursive call reduces the
problem size, and its state must be saved on the stack until the recursion unravels back to the base condition.
Each recursive call must operate on a smaller subproblem to eventually reach the base case.

You might also like