0% found this document useful (0 votes)
5 views36 pages

Data Structures Final Revision Notes - pt 2

The document provides an overview of various data structures including priority queues, double-ended queues (deques), multiple queues, and linked lists. It explains their definitions, functionalities, common operations, implementation strategies, and real-world applications. Additionally, it includes code snippets for implementing these structures in C, along with Q&A sections addressing common queries related to each data structure.

Uploaded by

thangenabil44
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)
5 views36 pages

Data Structures Final Revision Notes - pt 2

The document provides an overview of various data structures including priority queues, double-ended queues (deques), multiple queues, and linked lists. It explains their definitions, functionalities, common operations, implementation strategies, and real-world applications. Additionally, it includes code snippets for implementing these structures in C, along with Q&A sections addressing common queries related to each data structure.

Uploaded by

thangenabil44
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/ 36

📚 DATA STRUCTURES - FINAL REVISION NOTES

🔄 Priority Queue
Definition: A priority queue is a queue where elements have priorities and are served based on their
priority rather than arrival time.

How it works:

Higher priority elements are dequeued before lower priority ones

Elements with the same priority follow FIFO (First-In-First-Out)

Real-life example: Hospital emergency room - patients with critical conditions are treated before
those with minor issues, regardless of arrival time.

Implementation options:

1. Array-based: Use an array with special insertion logic to maintain priority order

2. Linked list-based: Insert elements in order of priority


3. Heap-based: Most efficient for large data sets (uses a binary heap)

Common operations:

Enqueue: Add an element with a priority


Dequeue: Remove the highest priority element

Peek: View the highest priority element without removing it

🤔 Q&A: Priority Queue


1. Q: What makes a priority queue different from a regular queue?
A: A regular queue follows FIFO (First-In-First-Out), while a priority queue serves elements based
on their priority value, regardless of their arrival order.
2. Q: What are some real-world applications of priority queues?
A: Hospital emergency rooms, process scheduling in operating systems, print job management,
event-driven simulation, and network traffic management.
3. Q: What are the advantages of using a priority queue?
A: They allow handling urgent tasks first, provide efficient access to the highest/lowest priority
element, and are useful for scheduling and resource allocation.

4. Q: What are common ways to implement a priority queue?


A: Array-based implementation, linked list-based implementation, and heap-based
implementation (most efficient for large datasets).

🔄 Double Ended Queue (Deque)


Definition: A queue that allows insertion and deletion at both ends.

Key features:

Elements can be added or removed from either the front or the rear
More flexible than standard queues and stacks

Can be used as both a queue and a stack

Common operations:

insertFront: Add element at the front

insertRear: Add element at the rear

deleteFront: Remove element from the front

deleteRear: Remove element from the rear


getFront: Get the front element without removing

getRear: Get the rear element without removing

Implementation (Array-based circular deque):


c
#include <stdio.h>

#define MAX 5 // Maximum size of deque

// Global variables
int deque[MAX]; // Array to store deque elements
int front = -1; // Index of front element
int rear = -1; // Index of rear element

// Function to check if deque is full


int isFull() {
if ((front == 0 && rear == MAX - 1) || (front == rear + 1)) {
return 1; // Return true (deque is full)
} else {
return 0; // Return false (deque is not full)
}
}

// Function to check if deque is empty


int isEmpty() {
if (front == -1) { // If front is -1
return 1; // Return true (deque is empty)
} else {
return 0; // Return false (deque is not empty)
}
}

// Function to insert at the front of deque


void insertFront(int item) {
if (isFull()) { // Check if deque is full
printf("Deque Overflow\n"); // Print error message
} else {
if (front == -1) { // If deque was empty
front = rear = 0; // Set front and rear to first position
} else if (front == 0) { // If front is at the beginning
front = MAX - 1; // Set front to the end (circular)
} else {
front--; // Decrement front
}
deque[front] = item; // Place the item at front position
printf("%d inserted at front\n", item);
}
}

// Function to insert at the rear of deque


void insertRear(int item) {
if (isFull()) { // Check if deque is full
printf("Deque Overflow\n"); // Print error message
} else {
if (front == -1) { // If deque was empty
front = rear = 0; // Set front and rear to first position
} else if (rear == MAX - 1) { // If rear is at the end
rear = 0; // Set rear to the beginning (circular)
} else {
rear++; // Increment rear
}
deque[rear] = item; // Place the item at rear position
printf("%d inserted at rear\n", item);
}
}

// Function to delete from the front of deque


int deleteFront() {
if (isEmpty()) { // Check if deque is empty
printf("Deque Underflow\n"); // Print error message
return -1; // Return error value
} else {
int item = deque[front]; // Get the front item
if (front == rear) { // If the last item was deleted
front = rear = -1; // Reset front and rear
} else if (front == MAX - 1) { // If front is at the end
front = 0; // Set front to the beginning (circular)
} else {
front++; // Increment front
}
return item; // Return the item
}
}

// Function to delete from the rear of deque


int deleteRear() {
if (isEmpty()) { // Check if deque is empty
printf("Deque Underflow\n"); // Print error message
return -1; // Return error value
} else {
int item = deque[rear]; // Get the rear item
if (front == rear) { // If the last item was deleted
front = rear = -1; // Reset front and rear
} else if (rear == 0) { // If rear is at the beginning
rear = MAX - 1; // Set rear to the end (circular)
} else {
rear--; // Decrement rear
}
return item; // Return the item
}
}
// Function to get the front element without removing it
int getFront() {
if (isEmpty()) { // Check if deque is empty
printf("Deque is empty\n"); // Print error message
return -1; // Return error value
} else {
return deque[front]; // Return the front item
}
}

// Function to get the rear element without removing it


int getRear() {
if (isEmpty()) { // Check if deque is empty
printf("Deque is empty\n"); // Print error message
return -1; // Return error value
} else {
return deque[rear]; // Return the rear item
}
}

Algorithm Explanation:

1. Setting up the deque:


Create an array of fixed size to store elements
Use two pointers: front (for front element) and rear (for rear element)

Initially, both pointers are set to -1 (indicating empty deque)

2. insertFront(item):
If deque is full, show error

If deque is empty, set both front and rear to 0


If front is at position 0, move it to the end of array (circular behavior)

Otherwise, decrement front

Place item at front position

3. insertRear(item):
If deque is full, show error
If deque is empty, set both front and rear to 0

If rear is at the end, move it to position 0 (circular behavior)

Otherwise, increment rear

Place item at rear position

4. deleteFront():
If deque is empty, show error

Get the item at front position

If only one item exists, reset front and rear to -1

If front is at the end, move it to 0 (circular behavior)

Otherwise, increment front

Return the item

5. deleteRear():
If deque is empty, show error

Get the item at rear position

If only one item exists, reset front and rear to -1

If rear is at position 0, move it to the end (circular behavior)

Otherwise, decrement rear

Return the item


🤔 Q&A: Double Ended Queue
1. Q: What is a deque and how is it different from a queue?
A: A deque (double-ended queue) allows insertion and deletion at both ends, while a regular
queue only allows insertion at the rear and deletion from the front.

2. Q: What are the benefits of using a circular implementation for a deque?


A: A circular implementation prevents wastage of space and allows better utilization of the fixed-
size array by reusing spaces that become available after deletion.
3. Q: What are some applications of deques?
A: Work stealing algorithms, undo operations in applications, browser history navigation,
palindrome checking, and implementing both stacks and queues.

4. Q: How would a deque implementation differ if using a linked list instead of an array?
A: In a linked list implementation, we wouldn't need to worry about circular behavior as there's no
fixed size. It would also eliminate overflow conditions, but might use more memory due to
pointers.

🔄 Multiple Queues
Definition: Implementing multiple queues in a single array.

Key strategies:

1. Fixed division: Divide the array into separate sections for each queue
2. Dynamic division: Allow queues to grow and shrink as needed

3. Separate pointers: Maintain separate front and rear pointers for each queue

Implementation approach:

Allocate a fixed portion of the array to each queue


Use separate front and rear pointers for each queue
Handle overflow/underflow conditions for each queue separately

Advantages:

Efficient memory utilization

Reduced overhead of multiple array declarations

Easier management of multiple queues

🤔 Q&A: Multiple Queues


1. Q: Why would we implement multiple queues in a single array?
A: To save memory, avoid multiple array declarations, and efficiently manage space when we
need multiple queues of different sizes.

2. Q: What are the challenges in implementing multiple queues in one array?


A: Managing overflow conditions, determining optimal size allocation for each queue, and
handling cases where one queue fills up while others are relatively empty.

3. Q: How can we implement multiple queues with dynamic size allocation?


A: By maintaining a free list that tracks available spaces in the array and allowing queues to use
spaces from this free list as needed.

4. Q: What is the difference between fixed and dynamic division in multiple queue
implementation?
A: Fixed division allocates a predetermined amount of array space to each queue that cannot
change, while dynamic division allows queues to grow and shrink based on their requirements.

🔄 Linked List Concepts


Definition: A linked list is a dynamic data structure where elements are stored in nodes that point to
the next node, creating a sequence.
Linked List vs. Array:

Feature Array Linked List

Memory allocation Static (fixed at compile time) Dynamic (can grow/shrink)

Element storage Contiguous memory Non-contiguous memory

Access time O(1) (direct access) O(n) (sequential access)

Insertion/Deletion Costly (requires shifting) Efficient (just update pointers)

Memory usage May waste memory if not fully used No wastage (allocate as needed)

Memory overhead Low Higher (extra pointer storage)


 

Types of Linked Lists:

1. Singly Linked List:


Each node points to the next node

Last node points to NULL

Only forward traversal is possible

2. Doubly Linked List:


Each node points to both next and previous nodes

Allows traversal in both directions

Requires more memory (extra pointer)

3. Circular Linked List:


Last node points back to first node instead of NULL

No NULL pointers

Can be singly or doubly circular

Basic Operations:
Insertion: Add a node at the beginning, end, or specific position

Deletion: Remove a node from the list

Traversal: Visit each node in the list

Searching: Find a node with specific data


Updating: Change data of a specific node

🔄 Singly Linked List


Structure definition:

// Structure for a node


struct Node {
int data; // Data of the node
struct Node* next; // Pointer to the next node
};

Implementation:
c
#include <stdio.h>
#include <stdlib.h> // For malloc

// Structure for a node


struct Node {
int data; // Data of the node
struct Node* next; // Pointer to the next node
};

// Global variable - pointer to the first node


struct Node* head = NULL;

// Function to create a new node


struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // Allocate memo
newNode->data = data; // Set the data
newNode->next = NULL; // Set next pointer to NULL
return newNode; // Return the new node
}

// Function to insert node at the beginning


void insertAtBeginning(int data) {
struct Node* newNode = createNode(data); // Create a new node
if (head == NULL) { // If list is empty
head = newNode; // Set head to newNode
} else {
newNode->next = head; // Link newNode to head
head = newNode; // Update head to newNode
}
printf("%d inserted at the beginning\n", data);
}
// Function to insert node at the end
void insertAtEnd(int data) {
struct Node* newNode = createNode(data); // Create a new node
if (head == NULL) { // If list is empty
head = newNode; // Set head to newNode
} else {
struct Node* temp = head; // Start from head
// Traverse to the last node
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode; // Link the last node to newNode
}
printf("%d inserted at the end\n", data);
}

// Function to insert node after a specific position


void insertAfterPos(int pos, int data) {
if (pos < 1) { // Invalid position
printf("Invalid position\n");
return;
}

struct Node* newNode = createNode(data); // Create a new node

if (pos == 1) { // Insert after the first node


if (head == NULL) { // If list is empty
head = newNode; // Set head to newNode
} else {
newNode->next = head->next; // Link newNode to head's next
head->next = newNode; // Link head to newNode
}
} else {
struct Node* temp = head; // Start from head
int i = 1; // Current position

// Traverse to the position


while (i < pos && temp != NULL) {
temp = temp->next;
i++;
}

if (temp == NULL) { // Position out of range


printf("Position out of range\n");
free(newNode); // Free allocated memory
return;
}

newNode->next = temp->next; // Link newNode to temp's next


temp->next = newNode; // Link temp to newNode
}

printf("%d inserted after position %d\n", data, pos);


}

// Function to delete a node with specific data


void deleteNode(int data) {
if (head == NULL) { // If list is empty
printf("List is empty\n");
return;
}

struct Node* temp = head; // Current node


struct Node* prev = NULL; // Previous node
// If head node itself holds the data to be deleted
if (temp != NULL && temp->data == data) {
head = temp->next; // Change head
free(temp); // Free old head
printf("%d deleted\n", data);
return;
}

// Search for the data to be deleted, keep track of the previous node
while (temp != NULL && temp->data != data) {
prev = temp;
temp = temp->next;
}

// If data was not present in linked list


if (temp == NULL) {
printf("%d not found\n", data);
return;
}

// Unlink the node from linked list


prev->next = temp->next;
free(temp); // Free memory
printf("%d deleted\n", data);
}

// Function to update a node


void updateNode(int oldData, int newData) {
if (head == NULL) { // If list is empty
printf("List is empty\n");
return;
}

struct Node* temp = head; // Start from head

// Search for the node to be updated


while (temp != NULL && temp->data != oldData) {
temp = temp->next;
}

// If data was not present in linked list


if (temp == NULL) {
printf("%d not found\n", oldData);
return;
}

// Update the data


temp->data = newData;
printf("%d updated to %d\n", oldData, newData);
}

// Function to display the linked list


void display() {
if (head == NULL) { // If list is empty
printf("List is empty\n");
return;
}

struct Node* temp = head; // Start from head


printf("Linked List: ");

// Traverse and print each node


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

printf("NULL\n");
}

Algorithm Explanation:

1. Create Node:
Allocate memory for a new node

Set the data value

Initialize the next pointer to NULL

Return the pointer to the new node

2. Insert at Beginning:
Create a new node

If list is empty, set head to new node

Otherwise, make new node point to current head

Update head to point to new node

3. Insert at End:
Create a new node

If list is empty, set head to new node

Otherwise, traverse to the last node

Make the last node point to the new node

4. Insert After Position:


Create a new node
If position is 1 (insert after first node):
If list is empty, set head to new node
Otherwise, link new node between head and head's next

Otherwise, traverse to the specified position

Insert new node between the position node and its next node

5. Delete Node:
If list is empty, show error
If the node to delete is the head:
Update head to next node
Free the old head

Otherwise, traverse to find the node to delete

Update the previous node's next pointer to skip the node to delete
Free the deleted node

6. Update Node:
Traverse to find the node with the old data

Change its data to the new data

7. Display:
Traverse the list from head to tail

Print each node's data followed by an arrow


Print NULL at the end

🤔 Q&A: Singly Linked List


1. Q: What are the main advantages of a singly linked list over an array?
A: Dynamic size (can grow/shrink at runtime), efficient insertion and deletion operations (no need
to shift elements), and no wastage of memory.
2. Q: What are the limitations of a singly linked list?
A: Sequential access only (no direct access to elements), extra memory for pointer storage, and no
backward traversal.
3. Q: When would you choose an array over a linked list?
A: When you need random access to elements, when the size is fixed and known in advance, or
when memory efficiency is critical.

4. Q: What is the time complexity of searching in a singly linked list?


A: O(n) in the worst case, as you may need to traverse the entire list to find an element.
5. Q: How does memory allocation differ between an array and a linked list?
A: Arrays use contiguous memory blocks allocated at compile time, while linked lists use
dynamically allocated non-contiguous memory blocks created at runtime.

🔄 Doubly Linked List


Structure definition:

// Structure for a node


struct Node {
int data; // Data of the node
struct Node* next; // Pointer to the next node
struct Node* prev; // Pointer to the previous node
};

Implementation:
c
#include <stdio.h>
#include <stdlib.h> // For malloc

// Structure for a node


struct Node {
int data; // Data of the node
struct Node* next; // Pointer to the next node
struct Node* prev; // Pointer to the previous node
};

// Global variable - pointer to the first node


struct Node* head = NULL;

// Function to create a new node


struct Node* createNode(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // Allocate memo
newNode->data = data; // Set the data
newNode->next = NULL; // Set next pointer to NULL
newNode->prev = NULL; // Set prev pointer to NULL
return newNode; // Return the new node
}

// Function to insert node at the beginning


void insertAtBeginning(int data) {
struct Node* newNode = createNode(data); // Create a new node
if (head == NULL) { // If list is empty
head = newNode; // Set head to newNode
} else {
newNode->next = head; // Link newNode to head
head->prev = newNode; // Link head to newNode
head = newNode; // Update head to newNode
}
printf("%d inserted at the beginning\n", data);
}

// Function to insert node at the end


void insertAtEnd(int data) {
struct Node* newNode = createNode(data); // Create a new node
if (head == NULL) { // If list is empty
head = newNode; // Set head to newNode
} else {
struct Node* temp = head; // Start from head
// Traverse to the last node
while (temp->next != NULL) {
temp = temp->next;
}
temp->next = newNode; // Link the last node to newNode
newNode->prev = temp; // Link newNode to the last node
}
printf("%d inserted at the end\n", data);
}

// Function to delete a node with specific data


void deleteNode(int data) {
if (head == NULL) { // If list is empty
printf("List is empty\n");
return;
}

struct Node* temp = head; // Start from head

// If head node itself holds the data to be deleted


if (temp != NULL && temp->data == data) {
head = temp->next; // Change head
if (head != NULL) { // If the list is not empty after deletion
head->prev = NULL; // Set prev of new head to NULL
}
free(temp); // Free old head
printf("%d deleted\n", data);
return;
}

// Search for the data to be deleted


while (temp != NULL && temp->data != data) {
temp = temp->next;
}

// If data was not present in linked list


if (temp == NULL) {
printf("%d not found\n", data);
return;
}

// If node to be deleted is not the last node


if (temp->next != NULL) {
temp->next->prev = temp->prev; // Link next node to previous node
}

// If node to be deleted is not the first node


if (temp->prev != NULL) {
temp->prev->next = temp->next; // Link previous node to next node
}

free(temp); // Free memory


printf("%d deleted\n", data);
}
// Function to display the linked list forward
void displayForward() {
if (head == NULL) { // If list is empty
printf("List is empty\n");
return;
}

struct Node* temp = head; // Start from head


printf("Linked List (Forward): ");

// Traverse and print each node


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

printf("NULL\n");
}

// Function to display the linked list backward


void displayBackward() {
if (head == NULL) { // If list is empty
printf("List is empty\n");
return;
}

struct Node* temp = head; // Start from head

// Traverse to the last node


while (temp->next != NULL) {
temp = temp->next;
}

printf("Linked List (Backward): ");

// Traverse backward and print each node


while (temp != NULL) {
printf("%d <-> ", temp->data);
temp = temp->prev;
}

printf("NULL\n");
}

Algorithm Explanation:

1. Create Node:
Allocate memory for a new node
Set the data value

Initialize both next and prev pointers to NULL


Return the pointer to the new node

2. Insert at Beginning:
Create a new node
If list is empty, set head to new node

Otherwise:
Make new node's next point to current head
Make current head's prev point to new node

Update head to point to new node


3. Insert at End:
Create a new node

If list is empty, set head to new node


Otherwise:
Traverse to the last node
Make the last node's next point to new node

Make new node's prev point to the last node

4. Delete Node:
If list is empty, show error

If the node to delete is the head:


Update head to next node
Update new head's prev to NULL

Free the old head

Otherwise:
Traverse to find the node to delete
Update previous node's next pointer to skip the node to delete

Update next node's prev pointer to skip the node to delete

Free the deleted node

5. Display Forward:
Traverse the list from head to tail

Print each node's data with bidirectional arrows

Print NULL at the end

6. Display Backward:
Traverse to the last node
Traverse backward using prev pointers

Print each node's data with bidirectional arrows


Print NULL at the end

🤔 Q&A: Doubly Linked List


1. Q: What are the advantages of a doubly linked list over a singly linked list?
A: Bidirectional traversal, efficient deletion operations (no need to track previous node), and easier
implementation of certain algorithms like LRU cache.

2. Q: What are the disadvantages of a doubly linked list?


A: Higher memory usage (extra pointer for each node), more complex implementation, and more
pointer operations during insertion and deletion.
3. Q: In what scenarios would you prefer a doubly linked list over a singly linked list?
A: When you need bidirectional traversal, when you need quick access to both previous and next
elements, or when implementing data structures like browsers' back/forward navigation.
4. Q: How is node deletion different in a doubly linked list compared to a singly linked list?
A: In a doubly linked list, you don't need to keep track of the previous node during traversal as
each node has a prev pointer. This makes deletion simpler as you can directly access the previous
node from the node to be deleted.

5. Q: What is the space complexity of a doubly linked list compared to a singly linked list?
A: Doubly linked list has a higher space complexity as it requires an extra pointer (prev) for each
node.

🔄 Circular Linked List


Definition: A circular linked list is a linked list where the last node points back to the first node,
forming a circle.
Key features:

No NULL pointers

Can be singly circular (last node points to first) or doubly circular (last node's next points to first,
first node's prev points to last)

Useful for applications where you need to cycle through all elements repeatedly

Applications:

Round-robin scheduling

Circular buffer implementation


Multiplayer games (managing turns)

Implementing circular queues efficiently

🤔 Q&A: Circular Linked List


1. Q: What is the main difference between a circular linked list and a regular linked list?
A: In a circular linked list, the last node points back to the first node instead of pointing to NULL,
creating a circular structure.
2. Q: What are the advantages of using a circular linked list?
A: Easy implementation of circular operations, no need for special handling of the end of the list,
and efficient for applications that require repeated cycling through elements.

3. Q: In what scenarios would you prefer a circular linked list?


A: Round-robin scheduling, implementing circular queues, multiplayer games where turns rotate
among players, and applications requiring continuous cycling through elements.

4. Q: How do you detect if a linked list is circular?


A: You can use Floyd's Cycle-Finding Algorithm (also known as the "tortoise and hare" algorithm)
where two pointers move at different speeds through the list. If they meet, the list is circular.
🔄 Stack Using Linked List
Implementation:
c
#include <stdio.h>
#include <stdlib.h> // For malloc

// Structure for a node


struct Node {
int data; // Data of the node
struct Node* next; // Pointer to the next node
};

// Global variable - pointer to the top node


struct Node* top = NULL;

// Function to check if stack is empty


int isEmpty() {
if (top == NULL) { // If top is NULL
return 1; // Return true (stack is empty)
} else {
return 0; // Return false (stack is not empty)
}
}

// Function to push an element onto the stack


void push(int data) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node)); // Allocate memo
if (newNode == NULL) { // If memory allocation failed
printf("Stack Overflow\n");
return;
}

newNode->data = data; // Set the data


newNode->next = top; // Link newNode to current top
top = newNode; // Update top to newNode
printf("%d pushed to stack\n", data);
}

// Function to pop an element from the stack


int pop() {
if (isEmpty()) { // Check if stack is empty
printf("Stack Underflow\n"); // Print error message
return -1; // Return error value
} else {
struct Node* temp = top; // Store current top
int data = temp->data; // Get the data
top = top->next; // Update top to next node
free(temp); // Free the popped node
return data; // Return the data
}
}

// Function to peek at the top element without removing it


int peek() {
if (isEmpty()) { // Check if stack is empty
printf("Stack is empty\n"); // Print error message
return -1; // Return error value
} else {
return top->data; // Return the top data
}
}

// Function to display the stack


void display() {
if (isEmpty()) { // Check if stack is empty
printf("Stack is empty\n"); // Print error message
return;
}

struct Node* temp = top; // Start from top


printf("Stack: ");

// Traverse and print each node


while (temp != NULL) {
printf("%

You might also like