Dsa Cia1 Notes U1 and U2
Dsa Cia1 Notes U1 and U2
Compare its efficiency with that of an array-based list for dynamic data handling
(or)Explain the basic structure of a singly linked list and list the fundamental
operations it supports
A singly linked list is a data structure used to store a sequence of elements, where each element
points to the next element in the sequence. It consists of nodes, each containing two parts:
1. Data: The value or data stored in the node.
2. Next: A reference (or pointer) to the next node in the list.
The list starts with a head node (the first node), and ends with a node whose next reference is
null (indicating the end of the list). There’s no direct way to access nodes other than by
traversing the list from the head.
Fundamental Operations
1. Insertion:
o At the head: Add a new node before the current head and update the head
reference.
o At the tail: Traverse the list to the end and add the new node.
o At a specific position: Traverse to the desired position and adjust pointers to
insert the new node.
2. Deletion:
o From the head: Update the head reference to the next node.
o From the tail: Traverse to the second-to-last node and set its next to null.
o At a specific position: Traverse to the position before the node to be deleted,
adjust pointers to bypass the node being deleted.
3. Traversal: Iterate through the list starting from the head to access or print the data in
each node.
4. Search: Traverse the list to find a node containing a specific value.
5. Update: Traverse to the node that needs updating and change its data value.
Efficiency Comparison with Array-Based List
Array-Based List
Structure: Arrays are contiguous blocks of memory. Elements are indexed and can be
accessed directly.
Size: Fixed size or dynamically resized (e.g., using a dynamic array or array list).
Efficiency Comparison
1. Insertion and Deletion:
o Singly Linked List:
At Head: O(1) – Insertions or deletions at the head are very efficient.
At Tail: O(n) – Requires traversal of the entire list unless a tail pointer is
maintained.
At Specific Position: O(n) – Requires traversal to the position.
o Array-Based List:
At End: O(1) – Efficient if resizing is not needed; otherwise, amortized
O(1) due to dynamic resizing.
At Specific Position: O(n) – Requires shifting elements if insertion or
deletion is not at the end.
2. Access:
o Singly Linked List: O(n) – Must traverse the list from the head to reach a
specific position.
o Array-Based List: O(1) – Direct access via index.
3. Memory Usage:
o Singly Linked List: O(n) extra space for pointers; no wasted space, but overhead
due to pointer storage.
o Array-Based List: May have wasted space if the array is not fully utilized;
resizing can also incur overhead.
4. Resizing:
o Singly Linked List: No resizing needed; dynamically adjusts with each insertion
or deletion.
o Array-Based List: May require resizing and copying elements to a new array
when capacity is exceeded.
2.Explain how the insertion of a node in a doubly linked list affects the
adjacent nodes in both directions.Provide a step-by-step example. or
Illustrate how the deletion of a node from a doubly linked list affects the
previous and next pointers of adjacent nodes. Provide an example to illustrate
A doubly linked list is a type of linked list in which each node consists of 3 components:
*prev - address of the previous node
data - data item
*next - address of next node
Representation of Doubly Linked List
Let's see how we can represent a doubly linked list on an algorithm/code.
Suppose we have a doubly linked list:
Ne
wly created doubly linked list
struct node {
int data;
struct node *next;
struct node *prev;
}
Each struct node has a data item, a pointer to the previous struct node, and a
pointer to the next struct node.
Now we will create a simple doubly linked list with three items to understand
how this works.
/* Initialize nodes */
struct node *head;
struct node *one = NULL;
struct node *two = NULL;
struct node *three = NULL;
/* Allocate memory */
one = malloc(sizeof(struct node));
two = malloc(sizeof(struct node));
three = malloc(sizeof(struct node));
/* Assign data values */
one->data = 1;
two->data = 2;
three->data = 3;
/* Connect nodes */
one->next = two;
one->prev = NULL;
two->next = three;
two->prev = one;
three->next = NULL;
three->prev = two;
In the above code, one , two , and three are the nodes with data items 1, 2,
and 3 respectively.
For node one: next stores the address of two and prev stores null (there is
no node before it)
For node two: next stores the address of three and prev stores the address
of one
For node three: next stores null (there is no node after it) and prev stores
the address of two .
Note: In the case of the head node, prev points to null , and in the case of the
tail pointer, next points to null. Here, one is a head node and three is a tail
node.
Insertion on a Doubly Linked List
Pushing a node to a doubly-linked list is similar to pushing a node to a linked
list, but extra work is required to handle the pointer to the previous node.
Ori
ginal doubly linked list
Re
organize the pointers (changes are denoted by purple arrows)
Reo
rganize the pointers
// point next of newNode to the first node of the doubly linked list
newNode->next = (*head);
// point previous of the first node (now first node is the second
node) to newNode
if ((*head) != NULL)
(*head)->prev = newNode;
Re
organize the pointers
3. Set the prev pointer of new node and the next node
assign the value of prev of next node to the prev of newNode
Re
organize the pointers
The final doubly linked list is after this insertion is:
Fin
al list
New node
2. Set prev and next pointers of new node and the previous node
If the linked list is empty, make the newNode as the head node. Otherwise,
traverse to the end of the doubly linked list and
Reorganize the
pointers
// if the linked list is not empty, traverse to the end of the linked
list
while (temp->next != NULL)
temp = temp->next;
Ori
ginal doubly linked list
Finally, free the memory of del_node . And, the linked will look like this
Final list
if (*head == del_node)
*head = del_node->next;
if (del_node->prev != NULL)
del_node->prev->next = del_node->next;
free(del);
Re
organize the pointers
Finally, we will free the memory of del_node . And, the final doubly linked list
looks like this.
Fina
l list
if (del_node->next != NULL)
del_node->next->prev = del_node->prev;
if (del_node->prev != NULL)
del_node->prev->next = del_node->next;
Final list
if (del_node->prev != NULL)
del_node->prev->next = del_node->next;
7. Classify the structure and operations of the Stack ADT, and compare its
efficiency with other data structures in terms of time and space complexity
or
8. Illustrate a detailed implementation of the Stack ADT using arrays. Include an
evaluation of the advantages and limitations of this approach
.
Stack Data Structure is a linear data structure that follows LIFO
(Last In First Out) Principle , so the last element inserted is the
first to be popped out. In this article, we will cover all the basics of
Stack, Operations on Stack, its implementation, advantages,
disadvantages which will help you solve all the problems based on
Stack.
The Last In, First Out (LIFO) principle is a key concept in stack data structures. In a stack, the
most recently added element (the "last in") is the first one to be removed (the "first out"). This
principle ensures that elements are managed in a way that the most recent operations take
precedence over older ones. Let's delve into how the stack operations—push, pop, and peek—
support this principle.
1. Push Operation
Definition: The push operation adds an element to the top of the stack.
LIFO Principle:
Support: When an element is pushed onto the stack, it becomes the new top element. Since the
stack only allows access to the top element, this means that the most recent element (the one
just pushed) is the first one that will be removed during subsequent operations.
Implementation: The push operation involves placing the new element on top of the current
top of the stack and updating the top pointer/reference to this new element.
Example:
// Stack before push
// Top -> [A] -> [B] -> NULL
push(stack, 'C');
// Enqueue 1
Enqueue(1)
Front -> [1] -> NULL <- Rear
// Enqueue 2
Enqueue(2)
Front -> [1] -> [2] -> NULL <- Rear
In this example, elements 1 and 2 are added to the queue in that order, with 1 at the front and 2 at
the rear.
2. Dequeue Operation
Definition: The dequeue operation removes the element from the front of the queue.
Role in FIFO:
Support: The dequeue operation removes the element at the front of the queue, which is the
oldest element that was added. This ensures that elements are processed in the exact order in
which they were enqueued.
Implementation: Involves removing the element from the front of the queue and updating the
front pointer/reference to the next element in the queue. If the queue becomes empty after the
operation, the rear pointer/reference is also updated.
Example:
// Initial Queue
Front -> [1] -> [2] -> NULL <- Rear
// Dequeue
Dequeue()
Front -> [2] -> NULL <- Rear
// Removed element: 1
In this example, element 1, which was the first element added, is removed first, adhering to the
FIFO principle.
3. Peek Operation
Definition: The peek operation returns the element at the front of the queue without removing it.
Role in FIFO:
Support: The peek operation allows access to the element that will be dequeued next, without
modifying the queue. This operation provides a way to view the oldest element (the front of the
queue) while preserving the FIFO order.
Implementation: Involves accessing the element at the front pointer/reference without
changing the queue's structure.
Example:
// Initial Queue
Front -> [2] -> NULL <- Rear
// Peek
Peek()
Element at the front: 2