Download as pdf or txt
Download as pdf or txt
You are on page 1of 61

Linked List

Objective:
• To understand linked list as a linear, dynamic data
structure
• Compare and contrast arrays and linked lists
• To understand the mechanism through which the
nodes in linked list are accessed
• Describe operations defined for a linked list
• To understand applications of linked lists
Introduction
• Array is a static data structure -> cannot be
extended or reduced to fit the data set during
execution
• Expensive to implement new insertions and
deletions
• Linked Lists addresses some of the limitations of
arrays
• A linked list is a linear data structure where each
element is a separate object.
Introduction
• A linked list is a linear data structure which can
change (grow or shrink in size) during execution
(dynamic)
– Two successive elements are connected by link (self
referential structure)
struct node
– Last element points to NULL
{
– It does not waste memory space. int data;
struct node *next;
}
Points:
• To keep track of a linked list:
– Pointer to the first element of the list (called start, head, etc.) is
essential header linked list

• Head is not a separate node, but the reference to the first node
– If the list is empty then the head is a null reference
• Linear relationship Each element except the first has a unique
predecessor, and each element except the last has a unique
successor
• Every node typically consists of two fields
– data (the information contained in the node)
– next (pointer to the next node)
Operations on a singly linked list
• Insert
– At the beginning of the list
– In the end of the list
– In the middle of the list
• Delete
– From the beginning of the list
– From the end of the list
– From the middle of the list
• Traverse
Writing Methods
• When trying to code methods for Linked Lists draw
pictures!
– If you don't draw pictures of what you are trying to do it
is very easy to make mistakes!
Insertion at the beginning of the list

Objective is to insert a new node at the beginning


Possible Steps:
• Create a new node -> Set the node with data Values

• Connect the pointers (head & next of new node)


Insertion at the beginning of the list
The algorithm:
INSERT_BEGINNING_SLL(head, value)
.Create a new node ‘new’
I. new->data = value
II. new->next = NULL
.new->next = head
.head = new
•Time complexity – O(1)
•What if the list is empty i.e head = NULL?
✔ The above algorithm will cover the case
Insertion in the end of the list

Objective is to insert a new node in the end

• How to reach the end of the list?


– Hint:- the next field of last node points to NULL
Reaching the end of the list
• Assuming a pointer tmp is set to head, following
loop will set tmp to last node after termination
tmp = head
while(tmp->next != NULL)
tmp = tmp->next
Insertion in the end of the list
Steps:
• Create a new node -> Set the node with data Values

• Iterate through the list to reach the end

• Create new link (next of tmp to new node)


Insertion in the end of the list
The algorithm:
INSERT_END_SLL(head, value)
.Create a new node ‘new’
I. new->data = value
II. new->next = NULL
.tmp = head
.while(tmp->next != NULL)
. tmp = tmp->next
.tmp->next = new
•Time complexity – O(n) [n -> # of nodes]
•Time complexity may be reduced to O(1) by maintaining another pointer tail to point to
last node
•Why another pointer tmp? head is already there!
Insertion in the middle of the list

Objective is to insert a new node somewhere in the


middle (after a specific node having a specific
value)
• How to reach the specific node?
– data field of specific node contains the specific value
• Assuming a pointer tmp is set to head, following
loop will set tmp to specific node after termination
tmp = head
while(tmp->data != val)
How to insert after a specific node? How to change the pointers?
Wrong approach:
tmp->next = new; //link to the node with val = 142 lost
forever
new->next = ??;
Right approach:
new->next = tmp->next;
tmp->next = new;
Insertion in the middle of the list
Steps:
• Create a new node -> Set the node with data Values

• Iterate through the list to reach the specific node

• Create new link (next of tmp to new node)


Insertion in the middle of the list
The algorithm:
INSERT_MIDDLE_SLL(head, value, sp_val)
.Create a new node ‘new’
I. new->data = value
II. new->next = NULL
.tmp = head
.while(tmp->data != sp_val)
. tmp = tmp->next
.new->next = tmp->next
.tmp->next = new
•Time complexity – O(n) [n -> # of nodes]
Deletion from the front of the list
Objective is to delete the first node of the list
List in hand:

Alter head: head = head->next

List after first node deleted:

tmp_node Deleted node


Algorithm to delete from the front
Data_Type DELETE_FRONT_SLL(head)
.if (head==NULL)
. print “Empty List”
. return
4. tmp_val = head->data
5. tmp_node = head
6. head = head->next
7. free(tmp_node)
8. return(tmp_val)
•Time complexity – O(1)
Deletion of the last node of the list
Objective is to delete the last node of the list
List in hand:

This is to be done:

End of the list can be reached by running a loop as before


But…

• tmp does NOT hold any reference to its previous node


• Pointer (next field) of the previous node needs to be set to
NULL
• Requires another pointer to refer to previous node of tmp

Following loop will find previous to the last node after termination

tmp = head
while(tmp->next != NULL)
prev = tmp
tmp = tmp->next
Algorithm to delete from the end
Data_Type DELETE_END_LL(head)
.head = tmp
.while(tmp->next != NULL)
. prev = tmp
. tmp = tmp->next
.prev->next = NULL
.val = tmp->data
.free(tmp)
.return(val)
•Time complexity – O(n); n – number of nodes
Deletion of a node from the middle of the list
List in hand:

Steps:
1. Move to the node to be deleted (with a specific value, 65 in the
current example), keeping track of previous pointer:

2. Set prev->next = tmp->next then free tmp


Algorithm to delete from the middle
Data_Type DELETE_MIDDLE_SLL(head, val)
.tmp = head
.while(tmp->data != val)
. prev = tmp
. tmp = tmp->next
.prev->next = tmp->next
.val_ret = tmp->data
.free(tmp)
.return(val_ret)
•Time complexity – O(n) - n – number of nodes
Traversal of linked list
• Means -> visiting each node of the list once -> perform
some task during visit of each node
• For example – count number of odd numbers in the
following list

• How it can be done?


• Steps –
– Set a counter count to zero
– Starting from the head, visit every node till node->next
becomes NULL (traversal of the LIST)
– During visit, if (node->data)%2 != 0, increment count
The algorithm
int ODD_COUNT(head)
1. count = 0
2. tmp = head
3. while(tmp->next != NULL)
. if((tmp->data)%2 != 0)
. count = count + 1
. tmp = tmp->next
.rerturn count

Time complexity – O(n)


n – number of nodes
Application of Linked List
• As stand alone data structure, linked list has hardly
any importance
• Almost inevitable when they are used to implement
other data structures – stack, queue, tree, graph…
• Why??
– Due to dynamic nature
• How to implement stack and queue using linked
list? Think logically…
– Hint -> Try to relate the operations of linked list and
operations of stack, queue
Stack using linked list
• Stack = LIFO
– Insert and delete operations on stacks are performed on
only one end – the top
• To implement a stack using linked list
– Create a linked list (Last-In-Last-Out list – linked stack)
– Allow only insert at the beginning and delete from the
beginning operations on the said list
– Implement other operations such as display( ) as per
requirement
• Time complexity – O(1) [at par…]
• Imp: What if insertion and deletion are done in the end?
– Traversal would cost O(n) time => push( ), pop( ) would
cost O(n) in turn
Queue using linked list
• Queue = FIFO
– Insert in the rear end, delete from the front
• To implement a queue using linked list
– Create a linked list (First-In-First-Out list – linked queue)
– Allow only insert in the end and delete from the
beginning operations on the said list
– Implement other operations such as display( ) as per
requirement
• Time complexity – delete - O(1) [at par…]; insert O(n)
[costlier…]
• Time complexity of insert can be kept to O(1) by
maintaining another pointer end [extra overhead though]
Polynomial Representation and manipulation
Using Linked Lists
To represent a polynomial using LL, each node should contain three
fields (typically)

Coefficient Exponent Link to next node

A polynomial 5x12 + 8x3 – 3x + 2 can be represented using LL as


follows:

head 5 12 8 3 -3 1 2 0
Addition of two polynomials using LLs
(5x2 + 4x + 2) + (5x + 5) = ?

Steps:
1. Start with the highest power of each polynomial (LLs)
2. In case no item having same exponent found, simply append the
term to the resultant list, and continue with the process
3. Wherever exponents are matched, add the corresponding
coefficients and store the term in the resultant list
4. If one list gets exhausted earlier and other list still contains some
lower order terms, append the remaining terms to the resultant list
Array versus Linked Lists
• Arrays are suitable for:
– Inserting/deleting an element at the end.
– Randomly accessing any element.
– Searching the list for a particular value.
• Linked lists are suitable for:
– Inserting an element.
– Deleting an element.
– Applications where sequential access is required.
– In situations where the number of elements cannot be
predicted beforehand.
Types of Lists
• Depending on the way in which the links are used to
maintain adjacency, several different types of linked
lists are possible.
– Linear singly-linked list (or simply linear list)
• One we have discussed so far.

head

A B C
Circular linked list
• The pointer from the last element in the list points back to
the first element.
• There is no node pointing to NULL

• Advantage
– Useful in implementation of circular queue
– Entire list can be traversed from any node of the list
• Disadvantage:
– Reversing of circular list is a complex task
– May end up in an infinite loop if proper care is not taken
Doubly linked list (DLL)
• Pointers exist between adjacent nodes in both directions
(next & prev)
• Usually two pointers are used to keep track of the list, head
and tail

• Advantage:
– The list can be traversed either forward or backward =>
operations such as delete a node and reverse LL becomes easy
• Disadvantage:
– Every node of DLL require extra space for a ‘prev’ pointer; incurs
maintenance overhead
All the operations performed on SLL can be performed on DLL as well
Insert at the beginning of a DLL

INSERT_BEGINING_DLL(head, val)
1. Create a new node NewNode
2. NewNode->prev = NULL
3. NewNode->data = val
4. NewNode->next = head
5. head->prev = NewNode
6. head = NewNode
# Time complexity is O(1)
Insert a node into existing DLL @ position pos

Figure: 1

Figure: 2

Figure: 3
Figure: 4

But… in which order??


Figure: 4

One possible ordering

Figure: 5

• Change the pointers of the NewNode before changing other pointers


Figure: 6
Pseudocode for insert at position pos
INSERT_AFTER_DLL(head, pos, val)
//insert new node with value ‘val’ after position ‘pos’
1. tmpH = head
2. i = 1
3. while((i < POS) && (tmpH != NULL))
4. tmp = tmpH Worst case
5. tmpH = tmpH->next time
6. i = i + 1 complexity of
6. if ((tmpH != NULL) && (i == POS)) the algorithm
(a) Create a New Node is O(n),
(b) NewNode → data = val considering
(c) NewNode → next = tmp → next ‘n’ nodes are
(d) NewNode → prev = tmp there
(e) (tmp → next) → prev = NewNode
(f ) tmp → next = NewNode
7. else
(a) Display “Position NOT found”
Insert in the END of a DLL

INSERT_END_DLL(head, val)
1. Create a new node NewNode Worst case
2. NewNode->prev = NULL time
3. NewNode->data = val complexity of
4. NewNode->next = NULL the algorithm
5. tmp = head is O(n),
considering
6. while(tmp->next != NULL)
‘n’ nodes are
7. tmp = tmp->next there
8. tmp->next = NewNode
9. NewNode->prev = tmp
Delete from the beginning

Type DEL_BEGINNING_DLL(head)
//Delete from beginning
1. tmp = head
2. val = tmp->data
3. head = tmp->next
. free(tmp)
. Return val

# Time complexity is O(1)


Pseudocode to delete a node @ position pos

Type DEL_AFTER_DLL(head, pos)//Delete at position ‘pos’


1. tmpH = head
2. i = 1
3. while((i < POS) && (tmpH != NULL))
Worst case
4. tmp = tmpH
time
5. tmpH = tmpH->next
6. i = i + 1
complexity of
7. if ((tmpH != NULL) && (i == POS)) the algorithm
(a) val = (tmp->next)->data is O(n),
(b) tmp->next = (tmp->next)->next considering
(c) (tmp->next)->prev = tmp ‘n’ nodes are
(d) return val there
8. else
(a) Display “Position NOT found”
Delete from the END

Type DEL_END_DLL(head)//Delete Last node


1. tmp = head
2. while(tmp->next != NULL)
4. prev = tmp
5. tmp = tmp->next
6.val = tmp->data
7.prev->next = NULL
8.free(tmp)
9.return val

# Time complexity is O(n); n – number of nodes


Points:
• Linked list is a linear collection of self-referential objects, called nodes,
connected by links

– linear: for every node in the list, there is one and only one node
that precedes it (except for possibly the first node, which may have
no predecessor,) and there is one and only one node that succeeds
it, (except for possibly the last node, which may have no successor)

– self-referential: a node that has the ability to refer to another node


of the same type, or even to refer to itself

– dynamic: can grow or shrink as necessary

– non-contiguous: the logical sequence of items in the structure is


decoupled from any physical ordering in memory
Question answers:
Q. What does the following function do for a given Linked List with first node
as head?

void fun(struct node* h)


{
if(h == NULL)
return;
fun(h->next);
printf("%d ", h->data);
}
(A) Prints all nodes of linked lists
(B) Prints all nodes of linked list in reverse order
(C) Prints alternate nodes of Linked List
(D) Prints alternate nodes in reverse order
Discussion:
Example:
Algorithm:
void fun(struct node* h)
{
if(h == NULL)
return;
fun(h->next); Recursive calls to fun with h->next
printf("%d ", h->data);
}

• h becomes NULL in fourth call to fun(), then


1. returns to the previous call to fun(h->next)
2. Executes next statement i.e printf("%d ", h->data);
3. First printf will print 142
• statememts 1,2,3 will be executed two more times to print 17 and 48
• Thus, the list is printed in reverse order – option: (B)
Q. Which of the following points is/are true about
Linked List data structure when it is compared with
array
(A) Arrays have better cache locality that can make
them better in terms of performance (cache hit/miss)
(B) It is easy to insert and delete elements in LL
(C) Random access is not allowed in a typical
implementation of Linked Lists
(D) The size of array has to be pre-decided, linked lists
can change their size any time.
(E) All of the above
Answer: E
Q. What is the output of following function for start pointing to first node of
following linked list?
1->2->3->4->5->6
void fun(struct node* start)
{
if(start == NULL)
return;
printf("%d ", start->data);

if(start->next != NULL )
fun(start->next->next);
printf("%d ", start->data);
}
(A) 1 4 6 6 4 1
(B) 1 3 5 1 3 5
(C) 1 2 3 5
(D) 1 3 5 5 3 1
Discussion:
void fun(struct node* start)
{
1. if(start == NULL)
2. return;
3. printf("%d ", start->data);

4. if(start->next != NULL )
5. fun(start->next->next);
6. printf("%d ", start->data);
}
• fun() prints alternate nodes of the given LL
– first from head to end, statements 3, 4, 5 till 1 is satisfied
– then from end to head, 1 satisfied, stmt. 5 runs till beginning
– If Linked List has even number of nodes, skips the last node
• For 1->2->3->4->5->6; 1, 3, 5 and 5, 3, 1 will be printed (D)
Q. In the worst case, the number of comparisons
needed to search a singly linked list of length n for a
given element is (GATE CS 2002)

(A) log2 n
(B) n/2
(C) (log2 n – 1)
(D) n

Answer: (D)
Q. Suppose each set is represented as a linked list
with elements in arbitrary order. Which of the
operations among union, intersection, membership,
cardinality will be the slowest?

A. union only
B. intersection, membership
C. membership, cardinality
D. union, intersection
Discussion:
• Membership and Cardinality require a single scan to
a LL; resulting in linear time complexity - O(n)
• Assume two LLs, LL1 (cardinality n1) and LL2
(cardinality n2). For union of LL1 and LL2, one need
to ensure no duplicate elements is present – needs
O(n1×n2) operations for each element we need to
check if that element exists in other set
• Same argument is applicable for intersection

Option D. is the correct answer


Questions:
Q1. A circularly linked list is used to represent a Queue. A
single variable p is used to access the Queue. To which node
should p point such that both the operations enQueue and
deQueue can be performed in constant time? (GATE 2004)

(A) rear node


(B) front node
(C) not possible with a single pointer
(D) node next to front
Q.2 What are the time complexities of finding 8th
element from beginning and 8th element from end in
a singly linked list?
Let n be the number of nodes in linked list, you may
assume that n > 8.
(A) O(1) and O(n) (B) O(1) and O(1)
(C) O(n) and O(1) (D) O(n) and O(n)
Q.3 In a doubly linked list, the number of pointers
affected for an insertion operation will be
(A) 4 (B) 1 (C) 2
(D) 4 or 2 depending on position of insertion
Q.4 Let P be a singly linked list. Let Q be the pointer to
an intermediate node x in the list. What is the
worst-case time complexity of the best known
algorithm to delete the node x from the list?
(GATE-IT-2004)
)O(n) (B) O(log2 n) (C) O(log n) (D) O(1)

Q.5 The concatenation of two lists is to be performed


in O(1) time. Which of the following implementations
of a list should be used? (GATE CS 1997)
(A) singly linked list
(B) doubly linked list
(C) circular doubly linked list
Answers:
Q.1 – (A) rear; Q.2 - (A) O(1) and O(n)
Q.3 - (D) 4 or 2 depending on position of insertion
– Insertion at the middle node will affect 4 pointers whereas at the
head or tail will affect only 2 pointers
Q.4 - (D) O(1)
– Explanation: algorithm:-
Q→data=Q→next→data ; // Copy the value of next
node into Q.
Q→next=Q→next→next;
del=Q→next; // take another pointer variable
pointing to next node of Q.
free(del);
Q.5 - (C) circular doubly linked list – with singly and doubly linked list, this is
doable by maintaining another pointer, END (overhead). With option C, it can
be done by only changing two pointers (of the last nodes of both the lists).
Pointer to a pointer (A short Note)
The declaration of a pointer-to-pointer looks like
int **ipp;
where the two asterisks indicate that two levels of
pointers are involved.
Let,
int i = 5, j = 6; k = 7;
int *ip1 = &i, *ip2 = &j;
Now we can set
ipp = &ip1;
and ipp points to ip1 which points to i. *ipp is ip1,
and **ipp is i, or 5.

To change the value of the pointer passed to a


function as the function argument, require pointer
to a pointer
Questions
please…

You might also like