Data Structures. Mod_3 .Docx
Data Structures. Mod_3 .Docx
A linked list in C is a data structure that consists of a sequence of nodes, where each node
contains data and a pointer to the next node in the sequence. This structure allows for dynamic
memory allocation and efficient insertion and deletion of elements.
● Memory Allocation:
○ Arrays: Have a fixed size and allocate memory in a contiguous block. The size
must be defined at compile time.
○ Linked Lists: Can grow and shrink dynamically, as nodes are allocated
individually using functions like malloc().
● Access Time:
○ Arrays: Allow for constant time access (O(1)) to elements using an index.
○ Linked Lists: Require linear time (O(n)) to access elements, as you must
traverse from the head node to the desired node.
● Insertion/Deletion:
○ Linked Lists: Can grow and shrink in size dynamically, allowing for efficient use
of memory.
○ Arrays: Have a fixed size, which can lead to wasted memory or overflow if the
size is exceeded.
2. Efficient Insertions and Deletions:
○ Linked Lists: Inserting or deleting nodes can be done in constant time (O(1)) if
the pointer to the node is known, as it only requires updating pointers.
○ Arrays: Inserting or deleting elements often requires shifting other elements,
leading to linear time complexity (O(n)).
3. No Memory Wastage:
○ Linked Lists: Allocate memory for each node as needed, minimizing wasted
space.
○ Arrays: May allocate more memory than necessary, especially if the maximum
size is set too high.
1. Fixed Size: Arrays require a predetermined size, which can lead to overflow or
underutilization of memory.
2. Costly Insertions/Deletions: Arrays require shifting elements during insertions and
deletions, which can be inefficient for large datasets.
3. Memory Contiguity: Arrays require contiguous memory allocation, which can be
problematic in fragmented memory environments. Linked lists can utilize scattered
memory locations.
Representation of Linked Lists in Memory:
● Nodes: Each node in a linked list is represented in memory as a separate block. Each
node contains:
● Pointers: A pointer is a variable that stores the memory address of another variable. In
the context of linked lists:
○ Each node contains a pointer that points to the next node in the list.
○ This allows for the traversal of the list by following the pointers from one node to
the next.
● Entry Point: The head pointer is crucial as it serves as the entry point to the linked list. It
points to the first node in the list.
● Traversal: To access or traverse the linked list, you start from the head pointer and
follow the next pointers until you reach the end (where the next pointer is NULL).
● List Operations: Operations such as insertion, deletion, and searching often rely on the
head pointer to locate the nodes.
● Structure:
○ Doubly Linked List: Each node contains three components:
■ Data: The value stored in the node.
■ Next Pointer: A pointer to the next node.
■ Previous Pointer: A pointer to the previous node.
● Traversal: Can be traversed in both directions (forward and backward).
● Memory Usage: Requires more memory per node due to the additional pointer.
● Insertion/Deletion: More flexible for operations, as you can easily access both the
previous and next nodes.
● Definition: In a circular linked list, the last node points back to the first node instead of
pointing to NULL. This creates a circular structure.
● Types:
○ Circular Singly Linked List: Each node points to the next node, and the last
node points back to the head.
○ Circular Doubly Linked List: Each node has pointers to both the next and
previous nodes, with the last node pointing back to the head and the head
pointing to the last node.
● Functionality:
○ Allows for continuous traversal of the list without reaching a null reference.
○ Useful for applications that require a circular iteration, such as round-robin
scheduling.
The process of insertion can vary based on where you want to insert the node (beginning, end,
or middle).
1. Identify the Node: Traverse the list to find the node to delete.
2. Adjust Pointers: Update the previous node's next pointer to skip the node to be deleted.
3. Free Memory: Deallocate the memory of the node.
23.void deleteNode(struct Node** head, int key) {
24. struct Node* temp = *head, *prev = NULL;
25.
26. if (temp != NULL && temp->data == key) {
27. *head = temp->next; // Change head
28. free(temp); // Free old head
29. return;
30. }
31.
32. while (temp != NULL && temp->data != key) {
33. prev = temp;
34. temp = temp->next;
35. }
36.
37. if (temp == NULL) return; // Key not found
38.
39. prev->next = temp->next; // Unlink the node
40. free(temp); // Free memory
41.}
1. Search for the Node: Traverse the list to find the node with the specified value.
2. Modify the Data: Update the data field of the node.
57.void updateNode(struct Node* head, int oldData, int newData) {
58. struct Node* current = head;
59.
60. while (current != NULL) {
61. if (current->data == oldData) {
62. current->data = newData; // Update the data
63. return;
64. }
65. current = current->next;
66. }
67.}
○ The top of the stack corresponds to the head of the linked list.
○ Each node contains the data and a pointer to the next node.
● Operations:
○ Push: To add an element, create a new node, set its next pointer to the current
head, and update the head to the new node.
○ Pop: To remove an element, check if the stack is empty. If not, store the head
node, update the head to the next node, and free the stored node.
1. struct Node {
2. int data;
3. struct Node* next;
4. };
5.
6. void push(struct Node** top, int newData) {
7. struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
8. newNode->data = newData;
9. newNode->next = *top;
10. *top = newNode;
11.}
12.
13.int pop(struct Node** top) {
14. if (*top == NULL) return -1; // Stack is empty
15. struct Node* temp = *top;
16. int poppedData = temp->data;
17. *top = (*top)->next;
18. free(temp);
19. return poppedData;
20.}
○ The front of the queue corresponds to the head of the linked list.
○ The rear of the queue corresponds to the tail of the linked list.
● Operations:
○ Enqueue: To add an element, create a new node. If the queue is empty, set both
front and rear to the new node. Otherwise, set the current rear's next pointer to
the new node and update the rear pointer.
○ Dequeue: To remove an element, check if the queue is empty. If not, store the
front node, update the front to the next node, and free the stored node.
21.struct Node {
22. int data;
23. struct Node* next;
24.};
25.
26.void enqueue(struct Node** front, struct Node** rear, int newData) {
27. struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
28. newNode->data = newData;
29. newNode->next = NULL;
30.
31. if (*rear == NULL) {
32. *front = *rear = newNode; // Queue is empty
33. return;
34. }
35.
36. (*rear)->next = newNode;
37. *rear = newNode;
38.}
39.
40.int dequeue(struct Node** front) {
41. if (*front == NULL) return -1; // Queue is empty
42. struct Node* temp = *front;
43. int dequeuedData = temp->data;
44. *front = (*front)->next;
45. free(temp);
46. return dequeuedData;
47.}
● Dynamic Size: Linked lists can grow and shrink in size dynamically, allowing for efficient
memory usage without a predefined limit.
● No Wasted Space: Unlike arrays, linked lists do not require a fixed size, so there is no
wasted space when the stack or queue is not full.
● Efficient Insertions/Deletions: Both stacks and queues can perform push/pop or
enqueue/dequeue operations in constant time (O(1)) without the need for shifting
elements, as would be necessary in an array-based implementation.
1. Insertion:
○ At the beginning
○ At the end
○ At a specific position
2. Deletion:
7. Finding the Length: Counting the number of nodes in the list.
To reverse a single linked list, you can use an iterative approach. The idea is to change the next
pointers of the nodes so that they point to the previous node instead of the next one.
Algorithm:
1. Initialize three pointers: prev (set to NULL), current (set to head), and next (to store
the next node).
2. Traverse the list:
○ Store the next node: next = current->next
○ Reverse the current node's pointer: current->next = prev
○ Move the prev and current pointers one step forward: prev = current,
current = next
3. After the loop, set the head to prev.
Code Example:
Algorithm:
1. Initialize two pointers: slow and fast, both pointing to the head of the list.
2. Move slow one step at a time and fast two steps at a time.
3. When fast reaches the end of the list, slow will be at the middle.
Code Example:
1. Insertion:
○ At the beginning
○ At the end
○ At a specific position
2. Deletion:
7. Finding the Length: Counting the number of nodes in the list.
● Forward Traversal:
To delete a node, you need to adjust the pointers of the neighboring nodes.
Algorithm:
1. If the list is empty, return.
2. If the node to be deleted is the head:
○ Update the head to the next node.
○ Set the new head's previous pointer to NULL.
3. If the node to be deleted is the tail:
○ Update the tail to the previous node.
○ Set the new tail's next pointer to NULL.
4. If the node is in the middle:
○ Adjust the previous node's next pointer to the next node.
○ Adjust the next node's previous pointer to the previous node.
5. Free the memory of the deleted node.
Code Example:
1. Circular Structure: Unlike traditional linked lists, the last node in a circular linked list
points back to the first node, forming a circle. This means there is no NULL at the end.
2. No End: You can traverse the list indefinitely without reaching a NULL pointer, making it
suitable for applications that require continuous looping through the list.
3. Single or Double Circular: Circular linked lists can be singly linked (each node points to
the next) or doubly linked (each node points to both the next and previous nodes).
4. Efficient Traversal: You can easily traverse the list from any node, making it useful for
applications like round-robin scheduling.
To insert a new node, you can choose to insert it at the beginning, end, or a specific position.
Here’s how to insert at the end:
Algorithm:
Code Example:
To detect a loop, you can use the Floyd’s Cycle-Finding Algorithm (also known as the
Tortoise and Hare algorithm).
Algorithm:
1. Initialize two pointers, slow and fast. Set both to the head of the list.
2. Move slow one step at a time and fast two steps at a time.
3. If there is a loop, the fast pointer will eventually meet the slow pointer.
4. If fast reaches NULL, there is no loop.
Code Example: