Ds Musa Answers
Ds Musa Answers
Data structures are essential for organizing and managing data efficiently. They can be
categorized into two main types: primitive and non-primitive data structures.
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].
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 .
```
A→B
A→C
B→D
C→D
```
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
```
Each representation has its advantages and disadvantages regarding space complexity and
ease of use [1][4].
In a Binary Search Tree (BST), deletion can occur in three different cases:
These cases ensure that the properties of BST are maintained after deletion [1].
These operations vary based on the type of data structure used [1][5].
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].
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.
- 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.
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.
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.
```
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.
| 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) 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.
```
A
/ \
B C
/\ / \
D E F
```
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].
```
#### Count Nodes in Doubly Linked List
```c
#include <stdio.h>
#include <stdlib.h>
```c
#include <stdio.h>
#include <stdlib.h>
// 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);
insertAtEnd(&head, 10);
insertAtEnd(&head, 20);
insertAtEnd(&head, 30);
deleteFromBeginning(&head);
if (foundNode != NULL) {
printf("Node with value %d found.\n", foundNode->data);
} else {
printf("Node with value %d not found.\n", key);
}
return 0;
}
newNode->data = newData;
newNode->next = NULL;
if (*head == NULL) {
*head = newNode;
return;
}
last->next = newNode;
}
```c
#include <stdio.h>
#include <stdlib.h>
// 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);
insertAtEnd(&last, 10);
insertAtEnd(&last, 20);
insertAtEnd(&last, 30);
return 0;
}
void insertAtEnd(struct CNode** last, int newData) {
struct CNode* newNode = (struct CNode*)malloc(sizeof(struct CNode));
newNode->data = newData;
do {
if (current->data < smallestValue)
smallestValue = current->data;
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>
char stack[MAX];
int top = -1;
while(!isEmpty())
postfix[j++] = pop();
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;
}
infixToPostfix(infix, postfix);
return 0;
}
```
### Q.5: Queue ADT Using Linked List
```c
#include <stdio.h>
#include <stdlib.h>
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;
}
newnode->data = data;
newnode->next=NULL;
if(q->rear == NULL) {
q->front=q->rear=newnode;
return;
}
q->rear->next=newnode;
q->rear=newnode;
}
q->front=q->front->next;
if(q->front==NULL)
q->rear=NULL;
free(temp);
}
while(temp!=NULL){
printf("%d ",temp->data);
temp=temp->next;
}
printf("\n");
}
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.