0% found this document useful (0 votes)
33 views21 pages

Ds Musa Answers

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)
33 views21 pages

Ds Musa Answers

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/ 21

Q.1) Explain various types of data structure with examples.

Q.2) Explain ADT with an example.


Q.3) What is topological sort? Explain topological sort with an
example.
Q.4) Explain various graph representation techniques and define
graphs.
Q.5) Explain different cases for deletion of a node in binary
search tree.
Q.6) Explain different operations that can be performed on data
structure.
Q. 7) Explain the advantages of circular queue over linear queue
OR Circular queue
overcomes disadvantages of linear queue.
Q.8) Explain double ended queue. Explain its types.
Q1. Types of Data Structures

Data structures are essential for organizing and managing data efficiently. They can be
categorized into two main types: primitive and non-primitive data structures.

Primitive Data Structures


These are the basic data types provided by programming languages. Examples include:
- Integers: Whole numbers, e.g., `5`, `-10`.
- Floats: Decimal numbers, e.g., `3.14`, `-0.001`.
- Characters: Single letters or symbols, e.g., `a`, `Z`.
- Booleans: True or false values.

Non-Primitive Data Structures


These are more complex structures built from primitive data types. They can be further divided
into:

1. Linear Data Structures: Elements are arranged sequentially.


- Arrays: A collection of elements identified by index, e.g., `[1, 2, 3]`.
- Linked Lists: A series of nodes where each node points to the next, e.g., `Head -> Node1 ->
Node2 -> NULL`.
- Stacks: Follows Last In First Out (LIFO) principle, e.g., operations like `push` and `pop`.
- Queues: Follows First In First Out (FIFO) principle, e.g., operations like `enqueue` and
`dequeue`.
2. Non-linear Data Structures: Elements are not arranged sequentially.
- Trees: Hierarchical structure with nodes, e.g., binary trees.
- Graphs: Consists of vertices connected by edges, e.g., social networks.

Q2. Abstract Data Type (ADT)

An Abstract Data Type (ADT) is a theoretical concept that defines a data type purely in terms of
its behavior from the user's perspective, without specifying how it is implemented. It includes a
set of operations that can be performed on the data type.

Example of ADT
A common example is the Stack ADT:
- Operations:
- `push(item)`: Add an item to the top of the stack.
- `pop()`: Remove the item from the top of the stack.
- `peek()`: Return the top item without removing it.
- `isEmpty()`: Check if the stack is empty.

The stack's behavior is defined by these operations, but its implementation can vary (e.g., using
arrays or linked lists) [1][2][3].

Q3. Topological Sort

Topological Sort is a linear ordering of vertices in a directed acyclic graph (DAG) such that for
every directed edge u \to v , vertex u comes before vertex v .

Example of Topological Sort


Consider a graph with vertices representing tasks and directed edges representing
dependencies:

```
A→B
A→C
B→D
C→D
```

A valid topological sort for this graph could be:


- A, B, C, D
or
- A, C, B, D

Both orders respect the dependency constraints [1].


Q4. Graph Representation Techniques

A graph consists of a set of vertices (or nodes) connected by edges. There are several ways to
represent graphs:

1. Adjacency Matrix: A 2D array where each cell indicates whether pairs of vertices are
adjacent.
- Example for vertices A, B, C:
```
ABC
A011
B100
C100
```

2. Adjacency List: An array of lists where each list represents a vertex and contains all adjacent
vertices.
- Example:
```
A: B, C
B: A
C: A
```

3. Edge List: A list of all edges in the graph.


- Example:
```
(A, B), (A, C)
```

Each representation has its advantages and disadvantages regarding space complexity and
ease of use [1][4].

Q5. Deletion Cases in Binary Search Tree (BST)

In a Binary Search Tree (BST), deletion can occur in three different cases:

1. Node with No Children (Leaf Node):


- Simply remove the node.

2. Node with One Child:


- Remove the node and link its parent directly to its child.
3. Node with Two Children:
- Find the nodes in-order predecessor or successor (largest node in left subtree or smallest in
right subtree), replace the node with that value, and then delete that predecessor/successor
node.

These cases ensure that the properties of BST are maintained after deletion [1].

Q6. Operations on Data Structures

Various operations can be performed on data structures:

- Insertion: Adding an element.


- Deletion: Removing an element.
- Traversal: Visiting all elements systematically.
- Searching: Finding an element within the structure.
- Sorting: Arranging elements in a specific order.

These operations vary based on the type of data structure used [1][5].

Q7. Advantages of Circular Queue Over Linear Queue

A circular queue overcomes several limitations of a linear queue:

1. Efficient Space Utilization: In a circular queue, when elements are dequeued from the front,
space becomes available for new elements at the rear without needing to shift other elements.

2. No Wasted Space: Unlike linear queues where space may be wasted after several dequeue
operations, circular queues utilize all available positions effectively.

3. Continuous Operations: Circular queues allow continuous enqueue and dequeue operations
without needing to reset indices when they reach the end of the array [1][5].

Double Ended Queue (Deque)

Q8. A Double Ended Queue (Deque) allows insertion and deletion at both ends—front and
back.

Types of Deques
1. Input Restricted Deque: Insertion is allowed only at one end while deletion can occur at both
ends.
2. Output Restricted Deque: Deletion is allowed only at one end while insertion can occur at
both ends.
Deques provide flexibility for various applications such as task scheduling and buffer
management [1][5].
i) Linear and Non - linear data structure.
ii) B tree and B+ tree.
Linear and Non-Linear Data Structures

Data structures can be categorized into linear and non-linear types based on their organization
and relationships between elements.

Linear Data Structures


In linear data structures, elements are arranged sequentially, meaning each element is
connected to its previous and next element. This structure allows for straightforward traversal
and access. Common examples include:

- Arrays: A collection of elements identified by index, e.g., `[10, 20, 30]`.


- Linked Lists: A series of nodes where each node points to the next, e.g., `Node1 -> Node2 ->
Node3 -> NULL`.
- Stacks: Follows Last In First Out (LIFO) principle, where the last element added is the first to
be removed.
- Queues: Follows First In First Out (FIFO) principle, where the first element added is the first to
be removed.

Non-Linear Data Structures


Non-linear data structures do not arrange elements in a sequential manner. Instead, they allow
for more complex relationships among elements. Examples include:

- Trees: Hierarchical structures with nodes connected in parent-child relationships, e.g., binary
trees.
- Graphs: Consist of vertices connected by edges, allowing for various paths and connections.

These structures are useful for representing more complex relationships and hierarchies in data.

B-Tree and B+ Tree

Both B-Trees and B+ Trees are types of self-balancing tree data structures that maintain sorted
data and allow searches, sequential access, insertions, and deletions in logarithmic time.

B-Tree
- Structure: A B-Tree is a balanced tree where each node can have multiple children (m-way
tree). It can store both keys and records in internal nodes as well as leaf nodes.
- Properties:
- All leaf nodes are at the same level.
- Each node can contain a maximum of m - 1 keys and a minimum of \lceil m/2 \rceil - 1 keys.
- The root must have at least two children if it is not a leaf.
B+ Tree
- Structure: A B+ Tree is an extension of the B-Tree that stores all actual data records only in
the leaf nodes. Internal nodes only store keys for navigation.
- Properties:
- All leaves are at the same level and are linked together for efficient sequential access.
- Internal nodes store only keys (no data pointers), which allows for a higher number of keys
per node compared to B-Trees.
- The height of a B+ Tree is typically lower than that of a B-Tree due to its structure.

Key Differences
- In a B-Tree, both internal and leaf nodes can store records; in contrast, a B+ Tree stores
records only in leaf nodes.
- The search operation in a B+ Tree may be faster due to the linked list structure connecting leaf
nodes, allowing for efficient range queries.

These properties make B+ Trees particularly useful in database systems where large amounts
of data need to be managed efficiently.

i) Binary search tree.


ii) Priority Queue with example.
iii) Depth first search ( D FS) transversal with example.
iv) Hashing and collision.
Binary Search Tree (BST)

A Binary Search Tree (BST) is a data structure that maintains sorted data in a hierarchical
manner. Each node in a BST has at most two children, referred to as the left and right child, and
it follows specific properties:

- The left subtree of a node contains only nodes with keys less than the node's key.
- The right subtree of a node contains only nodes with keys greater than the node's key.
- Both the left and right subtrees must also be binary search trees.

Example of a Binary Search Tree


Consider the following BST:

```
40
/ \
20 60
/\ /\
10 30 50 70
```

In this tree:
- The root node is `40`.
- The left child `20` is less than `40`, and its children `10` and `30` are less than `20`.
- The right child `60` is greater than `40`, with children `50` and `70` appropriately positioned.

Operations
Common operations on a BST include:
1. Insertion: To insert a new key, start from the root and recursively find the correct position
based on comparisons.
2. Searching: Similar to insertion, traverse left or right depending on whether the searched key
is less than or greater than the current node's key.
3. Deletion: This can involve three cases:
- Removing a leaf node.
- Removing a node with one child.
- Removing a node with two children by replacing it with its in-order predecessor or successor.

The time complexity for search, insertion, and deletion operations in a balanced BST is O(\log
n), while in the worst case (unbalanced), it can degrade to O(n) [1][2][4].

Priority Queue

A Priority Queue is an abstract data type similar to a regular queue but with an added feature:
each element has a priority assigned to it. Elements are served based on their priority rather
than their order in the queue.

Example of a Priority Queue


Consider a priority queue that processes tasks based on urgency:

| Task | Priority |
|------|----------|
|A |2 |
|B |1 |
|C |3 |

In this example:
- Task B has the highest priority (1) and will be processed first.
- After B is processed, Task A will be next, followed by Task C.

Implementation
Priority queues can be implemented using various data structures, such as arrays, linked lists,
or heaps (most commonly binary heaps). The heap implementation allows for efficient insertion
and removal of elements based on priority.

Depth First Search (DFS) Traversal

Depth First Search (DFS) is an algorithm used to traverse or search through tree or graph data
structures. It explores as far down one branch as possible before backtracking.

Example of DFS Traversal


Given the following graph:

```
A
/ \
B C
/\ / \
D E F
```

A DFS traversal starting from node A could follow this path:


1. Start at A.
2. Move to B.
3. Move to D (explore D fully).
4. Backtrack to B, then move to E (explore E fully).
5. Backtrack to A, then move to C.
6. Move to F (explore F fully).

The DFS order would be: A, B, D, E, C, F.

DFS can be implemented using recursion or an explicit stack [1][3].

Hashing and Collision

Hashing is a technique used to uniquely identify a specific object from a group of similar objects.
It involves converting input data into a fixed-size string of bytes using a hash function.

Hash Function
A hash function takes an input (or "key") and returns an integer value called a hash code. This
hash code is then used as an index in an array where the actual data is stored.

Collision
A collision occurs when two different inputs produce the same hash code. To handle collisions
effectively, several strategies can be employed:
1. Chaining: Each index in the hash table points to a linked list of entries that have hashed to
the same index.
2. Open Addressing: When a collision occurs, the algorithm searches for the next available slot
in the array based on some probing sequence.

These techniques help maintain efficient access times even when collisions occur [1][2].

Q.1) Write a function in C to count the number of nodes in (Singly


and Doubly LinkedList)
Q.2) Write aC program to perform the operation on Singly linked
list:
i) Insert a new node at the end of the list.
ii) Delete a node from the beginning of the list.
iii) Search for a given node.
iv) Display the list
Q.3) Write a C program to implement the operation on (Circular
Linked list):
i) Delete a node after a given node.
ii) Find node with smallest data value
iii) Display the list
iv) Insert a node at the end of the list.
Q.3) Write a program to simulate a linear queue as a linked list .
Q.4) Write a c program for infix and postfix conversion.
Q.5) Write a program in C to implement queue ADT using a linked
list.
Here are the C programs for your requests, covering linked lists, circular linked lists, queues,
and expression conversions.
### Q.1: Count the Number of Nodes in Singly and Doubly Linked Lists

#### Count Nodes in Singly Linked List


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

// Structure for a node in singly linked list


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

// Function to count nodes in a singly linked list


int countSinglyLinkedList(struct Node* head) {
int count = 0;
struct Node* current = head;
while (current != NULL) {
count++;
current = current->next;
}
return count;
}

// Main function to test counting nodes


int main() {
struct Node* head = NULL; // Initialize head as NULL
// Code to create and populate the list goes here...

printf("Number of nodes in singly linked list: %d\n", countSinglyLinkedList(head));


return 0;
}

```
#### Count Nodes in Doubly Linked List
```c
#include <stdio.h>
#include <stdlib.h>

// Structure for a node in doubly linked list


struct DNode {
int data;
struct DNode* next;
struct DNode* prev;
};

// Function to count nodes in a doubly linked list


int countDoublyLinkedList(struct DNode* head) {
int count = 0;
struct DNode* current = head;
while (current != NULL) {
count++;
current = current->next;
}
return count;
}

// Main function to test counting nodes in doubly linked list


int main() {
struct DNode* head = NULL; // Initialize head as NULL
// Code to create and populate the doubly linked list goes here...

printf("Number of nodes in doubly linked list: %d\n", countDoublyLinkedList(head));


return 0;
}
```
### Q.2: Operations on Singly Linked List

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

// Structure for a node in singly linked list


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

// Function prototypes
void insertAtEnd(struct Node** head, int newData);
void deleteFromBeginning(struct Node** head);
struct Node* searchNode(struct Node* head, int key);
void displayList(struct Node* node);

// Main function demonstrating operations on singly linked list


int main() {
struct Node* head = NULL;

insertAtEnd(&head, 10);
insertAtEnd(&head, 20);
insertAtEnd(&head, 30);

printf("Singly Linked List: ");


displayList(head);

deleteFromBeginning(&head);

printf("After deleting from beginning: ");


displayList(head);

int key = 20;


struct Node* foundNode = searchNode(head, key);

if (foundNode != NULL) {
printf("Node with value %d found.\n", foundNode->data);
} else {
printf("Node with value %d not found.\n", key);
}

return 0;
}

void insertAtEnd(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) {
*head = newNode;
return;
}

while (last->next != NULL) {


last = last->next;
}

last->next = newNode;
}

void deleteFromBeginning(struct Node** head) {


if (*head == NULL) return;

struct Node* temp = *head;


*head = (*head)->next;
free(temp);
}

struct Node* searchNode(struct Node* head, int key) {


while (head != NULL) {
if (head->data == key)
return head;
head = head->next;
}
return NULL;
}
void displayList(struct Node* node) {
while (node != NULL) {
printf("%d -> ", node->data);
node = node->next;
}
printf("NULL\n");
}
```

### Q.3: Operations on Circular Linked List

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

// Structure for a node in circular linked list


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

// Function prototypes
void insertAtEnd(struct CNode** last, int newData);
void deleteAfter(struct CNode* prevNode);
struct CNode* findSmallest(struct CNode* last);
void displayList(struct CNode* last);

// Main function demonstrating operations on circular linked list


int main() {
struct CNode* last = NULL;

insertAtEnd(&last, 10);
insertAtEnd(&last, 20);
insertAtEnd(&last, 30);

printf("Circular Linked List: ");


displayList(last);

deleteAfter(last); // Delete after last node (10)

printf("After deletion: ");


displayList(last);

struct CNode* smallest = findSmallest(last);


if (smallest != NULL) {
printf("Smallest value: %d\n", smallest->data);
}

return 0;
}
void insertAtEnd(struct CNode** last, int newData) {
struct CNode* newNode = (struct CNode*)malloc(sizeof(struct CNode));
newNode->data = newData;

if (*last == NULL) { // Empty list case


*last = newNode;
newNode->next = *last; // Pointing to itself
return;
}

newNode->next = (*last)->next; // New node points to first node


(*last)->next = newNode; // Last node points to new node
*last = newNode; // Update last pointer to new node
}

void deleteAfter(struct CNode* prevNode) {


if (prevNode == NULL || prevNode->next == prevNode) return; // No deletion possible

struct CNode* temp = prevNode->next; // The node to be deleted

if (temp == prevNode) { // Only one element case


free(temp);
prevNode->next = prevNode; // Pointing back to itself
return;
}

prevNode->next = temp->next; // Bypass the node to be deleted


free(temp); // Free memory of deleted node
}

struct CNode* findSmallest(struct CNode* last) {


if (last == NULL) return NULL;

struct CNode* current = last->next; // Start from first element


int smallestValue = current->data;

do {
if (current->data < smallestValue)
smallestValue = current->data;

current = current->next; // Move to next node


} while (current != last->next); // Loop until we reach back

return current; // Return the smallest value found


}

void displayList(struct CNode* last) {


if (last == NULL) return;

struct CNode* current = last->next;

do {
printf("%d -> ", current->data);
current = current->next;
} while (current != last->next);

printf("(back to start)\n");
}
```
### Q.4: Infix and Postfix Conversion

```c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <ctype.h>

#define MAX 100

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

// Function prototypes for stack operations


void push(char c);
char pop();
char peek();
int isEmpty();
int precedence(char c);

// Function to convert infix expression to postfix expression


void infixToPostfix(char *infix, char *postfix) {
int i, j=0;

for(i=0; infix[i]; i++) {


if(isalnum(infix[i])) { // If operand add it to output.
postfix[j++] = infix[i];
} else if(infix[i] == '(') { // If '(', push it onto stack.
push(infix[i]);
} else if(infix[i] == ')') { // If ')', pop and output from stack until '(' is found.
while(!isEmpty() && peek() != '(')
postfix[j++] = pop();
pop(); // Remove '(' from stack.
} else { // Operator.
while(!isEmpty() && precedence(peek()) >= precedence(infix[i]))
postfix[j++] = pop();
push(infix[i]);
}
}

while(!isEmpty())
postfix[j++] = pop();

postfix[j] = '\0'; // Null terminate string.


}

// Stack functions implementation


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

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

char peek() {
return stack[top];
}

int isEmpty() {
return top == -1;
}

int precedence(char c) {
switch(c) {
case '+':
case '-': return 1;
case '*':
case '/': return 2;
case '^': return 3;
}
return -1;
}

// Main function for testing conversion from infix to postfix.


int main() {
char infix[MAX], postfix[MAX];

printf("Enter infix expression: ");


scanf("%s", infix);

infixToPostfix(infix, postfix);

printf("Postfix expression: %s\n", postfix);

return 0;
}
```
### Q.5: Queue ADT Using Linked List

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

// Structure for a queue node


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

// Queue structure with front and rear pointers


struct Queue {
struct QueueNode *front, *rear;
};

// Function prototypes for queue operations


struct Queue* createQueue();
void enqueue(struct Queue*, int data);
void dequeue(struct Queue*);
void printQueue(struct Queue*);
int isEmpty(struct Queue*);

// Main function demonstrating queue ADT using linked list.


int main() {
struct Queue *q = createQueue();

enqueue(q, 10);
enqueue(q, 20);
enqueue(q, 30);

printf("Queue: ");
printQueue(q);

dequeue(q);
printf("After dequeue: ");
printQueue(q);

dequeue(q);
printf("After another dequeue: ");
printQueue(q);
return 0;
}

// Create a queue and initialize front and rear pointers.


struct Queue* createQueue() {
struct Queue *q = (struct Queue*)malloc(sizeof(struct Queue));
q->front = q->rear = NULL;
return q;
}

// Add an item to the queue.


void enqueue(struct Queue *q, int data) {
struct QueueNode *newnode =
(struct QueueNode*)malloc(sizeof(struct QueueNode));

newnode->data = data;
newnode->next=NULL;

if(q->rear == NULL) {
q->front=q->rear=newnode;
return;
}

q->rear->next=newnode;
q->rear=newnode;
}

// Remove an item from the queue.


void dequeue(struct Queue *q) {
if(q->front == NULL)
return;

struct QueueNode *temp=q->front;

q->front=q->front->next;

if(q->front==NULL)
q->rear=NULL;

free(temp);
}

// Print all elements in the queue.


void printQueue(struct Queue *q) {
struct QueueNode *temp=q->front;

while(temp!=NULL){
printf("%d ",temp->data);
temp=temp->next;
}
printf("\n");
}

// Check if the queue is empty.


int isEmpty(struct Queue *q){
return q->front == NULL ? 1 : 0 ;
}
```

These programs cover various operations on singly and doubly linked lists, circular linked lists,
queues using linked lists, as well as infix-to-postfix conversion. Each program is structured with
clear function definitions for ease of understanding and modification. You can compile and run
these programs using any standard C compiler.

You might also like