Before we get into doubly linked lists, let’s discuss some basics.
First, let’s discuss what an array is. An array, is a collection of elements of the same data type that are present in adjacent memory locations. We can also say that elements in an array are contiguous.
A list a collection of elements of different data types that are not contiguous.
Note: The elements can be in different memory locations, but the references for these elements are stored in contiguous memory locations.
Think about an array as a collection of all the buildings on a given street or a collection of all fish in a fish tank. Whereas, a list can be thought about as a collection containing the Taj Mahal, Pacific Ocean, Bill Gates, and a football.
A linked list is a collection of nodes. Since linked lists can be noncontiguous, we use nodes to store the value of elements and create links between them so that they are in sequential order. Each node consists of pointers that link to the next node/next element in the list.
In singly-linked lists, we only use the next pointer in each node, but in doubly linked lists we also add a previous pointer that links each element to its previous element. This helps us to traverse in both directions.
Now, let’s create a doubly linked list in Python.
First, we will create a node class, then we will use it to build a doubly linked list class.
# Creating a node classclass Node:def __init__(self, data):self.data = data #adding an element to the nodeself.next = None # Initally this node will not be linked with any other nodeself.prev = None # It will not be linked in either directionclass DoublyLinkedList:def __init__(self):self.head = None # Initally there are no elements in the list
Doubly linked lists use the head
variable to refer to the first element and the tail
variable that refers to the last element of the list. With this in mind, we are going to implement basic linked list methods, like:
push
pop
peek
insert
# Creating a node classclass Node:def __init__(self, data):self.data = data #adding an element to the nodeself.next = None # Initally this node will not be linked with any other nodeself.prev = None # It will not be linked in either direction# Creating a doubly linked list classclass DoublyLinkedList:def __init__(self):self.head = None # Initally there are no elements in the listself.tail = Nonedef push_front(self, new_data): # Adding an element before the first elementnew_node = Node(new_data) # creating a new node with the desired valuenew_node.next = self.head # newly created node's next pointer will refer to the old headif self.head != None: # Checks whether list is empty or notself.head.prev = new_node # old head's previous pointer will refer to newly created nodeself.head = new_node # new node becomes the new headnew_node.prev = Noneelse: # If the list is empty, make new node both head and tailself.head = new_nodeself.tail = new_nodenew_node.prev = None # There's only one element so both pointers refer to nulldef push_back(self, new_data): # Adding an element after the last elementnew_node = Node(new_data)new_node.prev = self.tailif self.tail == None: # checks whether the list is empty, if so make both head and tail as new nodeself.head = new_nodeself.tail = new_nodenew_node.next = None # the first element's previous pointer has to refer to nullelse: # If list is not empty, change pointers accordinglyself.tail.next = new_nodenew_node.next = Noneself.tail = new_node # Make new node the new taildef peek_front(self): # returns first elementif self.head == None: # checks whether list is empty or notprint("List is empty")else:return self.head.datadef peek_back(self): # returns last elementif self.tail == None: # checks whether list is empty or notprint("List is empty")else:return self.tail.datadef pop_front(self): # removes and returns the first elementif self.head == None:print("List is empty")else:temp = self.headtemp.next.prev = None # remove previous pointer referring to old headself.head = temp.next # make second element the new headtemp.next = None # remove next pointer referring to new headreturn temp.datadef pop_back(self): # removes and returns the last elementif self.tail == None:print("List is empty")else:temp = self.tailtemp.prev.next = None # removes next pointer referring to old tailself.tail = temp.prev # make second to last element the new tailtemp.prev = None # remove previous pointer referring to new tailreturn temp.datadef insert_after(self, temp_node, new_data): # Inserting a new node after a given nodeif temp_node == None:print("Given node is empty")if temp_node != None:new_node = Node(new_data)new_node.next = temp_node.nexttemp_node.next = new_nodenew_node.prev = temp_nodeif new_node.next != None:new_node.next.prev = new_nodeif temp_node == self.tail: # checks whether new node is being added to the last elementself.tail = new_node # makes new node the new taildef insert_before(self, temp_node, new_data): # Inserting a new node before a given nodeif temp_node == None:print("Given node is empty")if temp_node != None:new_node.prev = temp_node.prevtemp_node.prev = new_nodenew_node.next = temp_nodeif new_node.prev != None:new_node.prev.next = new_nodeif temp_node == self.head: # checks whether new node is being added before the first elementself.head = new_node # makes new node the new head