Lecture
29 Singly Linked List I
1. Introduction
Singly
Linked Lists are a type of data structure. In a singly linked list,
each node in the list stores the contents and a pointer or reference to
the next node in the list. It does not store any pointer or reference
to the previous node.
In a linked list he elements are not stored at contiguous memory
locations. The elements in a linked list are linked using pointers as
shown in the below image:
Why
Linked List?
Arrays can be used to store linear data of similar types, but arrays
have following limitations.
1) The size of the arrays is fixed: So we must know the upper limit on
the number of elements in advance. Also, generally, the allocated
memory is equal to the upper limit irrespective of the usage.
2) Inserting a new element in an array of elements is expensive,
because room has to be created for the new elements and to create room
existing elements have to shifted.
Advantages over arrays
1) Dynamic size
2) Ease of insertion/deletion
Drawbacks:
1) Random access is not allowed. We have to access elements
sequentially starting from the first node. So we cannot do binary
search with linked lists efficiently with its default implementation.
2) Extra memory space for a pointer is required with each element of
the list.
3) Not cache friendly. Since array elements are contiguous locations,
there is locality of reference which is not there in case of linked
lists.
Representation: A linked list is represented by a pointer to the first
node of the linked list. The
first node is called head. If the linked list is empty, then value of
head is NULL. Each node in a list consists of at least two parts: 1)
data 2) Pointer (Or Reference) to the next node.
A
singly linked list is
nothing but several Nodes to be linked together by pointers. So we may
build two Classes, one is Class
Node, the other one is Class
linkedList:
The Node class can be implemented as follows:
And
the linkedList class can be modelled as follows in the beginning
(before you have anythin to be inserted/appended/prepended to it):
Keep in mind that the
'self.head' pointer must always point to the first Node!
2. Singly Linked List
Append, Prepend, and Insertion
2.1 Append
The 'Append' method takes the following arguments:
def append(self, data):
'data' is the data to be appended to the end of the list.
The 'Append' operation is one of the Methods in Class linkedList. If
you want to append a data to the list, you must wrap the data into a
Node object first:
newNode=Node(data).
Now you can start the append operation. There are different situations
when you append data to a list:
1)
If the linked list is empty when you append this data, it will be very
simple. Just assigne the newNode to the Head pointer and terminate the
append operation.
if self.head==None:
self.head=newNode
return
2)
If the list is not empty, you need to move the head pointer to the last
Node and assign the newNode to the last Node's 'next' pointer. However,
the rules tell us the head pointer must point to the first Node in the
linked list, so let's make a duplicate of the 'head pointer' and just
move the duplicate to the last Node:
else:
lastNode=self.head # make a duplicate of the head pointer
while lastNode.next != None:
# If the pointer is still not reaching the real 'last Node' yet, just
keep moving it forward.
lastNode=lastNode.next
lastNode.next=newNode
#
Once lastNode pointer reaches the real 'last Node', connect the
lastNode.next pointer to the newNode to complete the 'Append'
operation.
The entire 'Append' operation in Python is as follows. The 'Append'
method belongs to Class
linkedList.
2.2 Prepend
The
'Prepend' method takes the following arguments:
def prepend(self, data):
'data' is the data to be appended to the end of the list.
The 'Prepend'
operation is similar to the 'Append' operation but needs some extra
concern on the head pointer.
1) Of course, if you want to prepend a data in the front of the list,
you need to wrap it up into a Node object first:
newNode=Node(data)
2) If the list is an empty one before this 'Prepend' operation, then
just set this new node as the 'Head Node':
if self.head == None:
self.head=newNode
return
3)
If it is not empty, connect the old 'self.head' pointer to the 'tail'
of the new node, and then set up the new node as the address for the
new head pointer.
else:
newNode.next=self.head
self.head=newNode
2.3 Insert after Node
The
'insertAfterNode' method takes the following arguments:
def
insertAfterNode(self, prevNode, data):
'data' is the data to be appended to the end of the list. 'prevNode' is
the node before the inserted node.
'Append'
and 'Prepend' operations add a Node to the front/end of the list. To
insert a Node into the list, you need the 'insert after Node' method in
your Class linkedList:
The schematic of this operation is as follows:
Since this method is 'insert a node after a node' so the previous node
is given as one of the arguments in of the method:
def
insertAfterNode(self, prevNode, data):
newNode=Node(data)
newNode.next=prevNode.next
prevNode.next=newNode
You can tell that this 'insert' method only works for the insertion to
the position close to the head node. For example, to insert at the
second place will be:
lst=linkedList()
lst.insertAfterNode(lst.head.next, 'E')
To insert to the third place, it looks like this:
lst=linkedList()
lst.insertAfterNode(lst.head.next.next, 'E')
It is not very
practical to isert something to the far-right side of the list.
2.4 Print out the Entire
list
This 'print' method belongs to the linkedList Class.
One of the drawbacks of using a linked list is the hardship of
accessing to the items in the list.
We
can use a pointer to move from the front of the linked list to the last
node of the linked list. We know the head pointer of the linked list so
we know where to start. We also know the last node's 'lastNode.next'
pointer points to 'None'. So we know where to start and where to stop.
We just need to print out the 'self.data' attribute of every Node in
this process.
def printList(self):
curNode=self.head # duplicate it so you are not losing 'self.head'.
while curNode!=None:
print(curNode.data)
curNode=curNode.next
2.5 Delete a Specific
Node in a Linked List
The goal of this method is to search from the left to the right side of
the list and find the first node that has the specific data. After this
node is deleted, the code execution will be terminated. If there are
more nodes afterwards have the same data inside, this method won't
delete them.
There are two cases when you delete a node in a linked list. The Node
may be the head node or one of the rest of the nodes.
For Case 1, after remove the head node, the head pointer should point
to the second node and the headNode.next pointer should point to None.
For Case 2, the node to be removed should has its '.next' pointer point
to None. The previous node's '.next' pointer should point to the
current nodes's '.next' pointer in order to connect the previous node
and the next node.
def deleteNode(self, key):
curNode=self.head # duplicate the head pointer
first
if curNode != None and curNode.data==key: # the
head node is the one to be deleted
self.head=curNode.next
curNode=None
return
else:
prev=None
while curNode != None and
curNode.data != key:
prev=curNode
curNode=curNode.next
if curNode==None: # why it is not curNode.next == None????
If the original linkedlist is empty, then there is no curNode.next at
all. If the scan pointer 'curNode' is already pointing to the 'None'
after the last node, then there is no None.next as well.
print('The data is not found in the list')
return
else:
prev.next=curNode.next
curNode=None # Why it is not curNode.next = None??? When you don't need this node anymore, just point the entire Node to None....
Tasks
1. Follow the instruction on this page, create the Node Class and the
linkedList Class. Instantiate a linked list object from the linkedList
Class and test all the
methods designed in the class.
(Send the link of the GitHub repositor to the homework email)