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)