May 08, 2020 ( last updated : May 09, 2020 )
		
		
			Linked List
		
			Data Structure and Algorithm
		
			Python
		
    
https://github.com/cao-weiwei/
In this post, let’s go through linked list in a brif way. I’ll introduce basic operations of linked list in Python.🤓
Below is a simple depiction of a single linked list:

Every linked list consists of nodes, we call the first node as ‘head’ and the last as ‘tail’, and for each node it has two components:
Sometimes, we may need a dummy head in the linked list that can help us with intrusion or deletion the first node.
O(n) operations for insertion/deletion of value at the array. Due to the shifting of the elements, the time complexity is O(n).O(n) operation given that you have access to the head node of the linked list.Single linked lists contain two kinds of classes:
Node class, Every node is going to consist of data and next.LinkedList class, except constructor, it initially contains print_list() methos to show each node’s data.class Node:
    def __init__(self, data):
        self.data = data
        self.next = None
class LinkedList:
    def __init__(self):
        self.head = None
    
    def print_list(self):
        """
        it will print out the data of each node from head to tail.
        """
        cur_node = self.head
        while cur_node:
            print(cur_node.data)
            cur_node = cur_node.next
         
Now we’ll insert elements in a linked list by different ways:
append, the append method will insert an element at the end of the linked list.
For append method, we should consider two kinds of situations:
def append(self, data):
  """
  the append method will insert an element at the end of the linked list. 
  """
  new_node = Node(data)	# create a node that will be inserted
  
  if not self.head:  # the linked list is empty
    self.head = new_node
    return
  
  last_node = self.head  # the linked list is not empty
  while last_node.next:
    last_node = last_node.next
    last_node.next = new_node
prepend, the prepend method will insert an element at the beginning of the linked list.
def prepend(self, data):
  """
  it will insert an element at the beginning of the linked list.
  """
  new_node = Node(data)
  new_node.next = self.head
  self.head = new_node
insert_after_node, this method will insert an element after a given node.
If we want to insert an element at somewhere(except head and tail) of the linked list, then this method will work. First, let’s break down the steps of solving this kind of problem:
def insert_after_node(self, pre_node, data):
  """
  it will insert an element after a given node.
  """
  if not pre_node:
    print("Previous node is None")
    return
  
  new_node = Node(data)
  new_node.next = pre_node.next
  pre_node.next = new_node
To delete a node in the linked list, first is to find the node to be deleted by traversing the linked list first. Then, delete that node and update the rest of pointers. However, to solve this problem, we need to handle two cases:
def delete_node(self, data):
  """
  this method will delete the given data node if existed in the linked list
  """
  cur_node = self.head
  if cur_node and cur_node.data == data:  # deal with the case of head
    self.head = cur_node.next
    cur_node = None
    return
  else:
    pre_node = None    # other cases
    while cur_node and cur_node.data != data:
      pre_node = cur_node
      cur_node = cur_node.next
            
    if not cur_node: return
    pre_node.next = cur_node.next
    cur_node = None
Now, let’s delete a node based on position rather than value. Again, we’ll consider the above twos cases
def delete_node_at_pos(self, pos):
  """
  it will delete a node based on given position. 
  """
  cur_node = self.head
  if cur_node and pos == 0: # deal with the case of head
    self.head = cur_node.next
    cur_node = None
  count = 0 # other cases
  pre_node = None
  while cur_node and count != pos:
    pre_node = cur_node
    cur_node = cur_node.next
    count += 1
  
  if not cur_node: return
  pre_node.next = cur_node.next
  cur_node = None
There are two ways to calculate the number of nodes in a linked list, which are iterative and recursive manners.
def len_iterative(self):
  """
  return the number of nodes in a linked list, using iterative method
  """
  count = 0;
  cur_node = self.head
  while cur_node:
    count += 1
    cur_node = cur_node.next
  
  return count
        
Before we start the recursive solution, let’s look the following diagram that could help us understand the method more specific.

For any recursive method, we need a base case. In this class, the base case is whether or not we’ve encountered the end of the linked list which means the None pointer.
0, otherwise;1.def len_recursive(self, node):
  """
  return the number of nodes in a linked list, using recursive method
  """
  if not node: return 0 # base case
  return 1 + self.len_recursive(node.next)
Now, assume we have two keys corresponding to the data element in the nodes, we would like to swap the two nodes in a linked list, what should we do?
One way to solve this is by iterating the linked list and keeping track of certain pieces of information that are going to be helpful.
There are two cases that we’ll have to cater for:
def swap_nodes(self, data_1, data_2):
    """
    it will swap two nodes if they're existed in the linked list
    """
    if data_1 == data_2: return    # two data are duplicate.
    
    pre_1, cur_1 = None, self.head  # looking for the data_1 and its previus node
    while cur_1 and cur_1.data != data_1:
        pre_1 = cur_1
        cur_1 = cur_1.next
        
    pre_2, cur_2 = None, self.head  # looking for the data_2 and its previuos node
    while cur_2 and cur_2.data != data_2:
        pre_2 = cur_2
        cur_2 = cur_2.next
        
    if not cur_1 or not cur_2: return  #cur_1 or cur_2 is none, means one of the data is no existed in linked list
    
    if pre_1:
        pre_1.next = cur_2
    else:    # data_1 is the head node
        self.head = cur_2
    if pre_2:
        pre_2.next = cur1
    else:    # data_2 is the head node
        self.head = cur_1
        
    cur_2.next, cur_1.next = cur_1.next, cur_2.next    # swap the two nodes.
    
In this part, let’s talk about how to reverse a singly linked list in an iterative way and a recursive way. Before getting started, let’s know how to reverse a linked list first.

The key idea is that we’re reversing the orientation of the arrows. For example, node A is initially pointing to node B but after we flip them, node B points to node A. The same is the case for other nodes.
def reverse_itr(self):
  """
  reverse the linked list, using iterative method
  """
  pre, cur = None, self.head
  while cur:	# three pointers for reversing
    nxt = cur.next;
    cur.next = prev
    prev = cur
    cur = nxt
  # new head after reversing
  self.head = prev    
The crux of recursive solution is as follows:
def reverse_rec(self):
  """
  it will reverse the linked list, using recursive method
  """
  def _reverse(pre, cur):
    if cur:
      _reverse(cur, cur.next)
      cur.next = pre
    else:
      self.head = pre
  _reverse_recursive(pre=None, cur=self.head)    # setting the head, which is the pre
We can use a hash-table or dictionary in Python to remove duplicates from a linked list.
def remove_deuplicates(self):
  """
  it will remove duplicate data held by nodes, using a dictionary.
  """
  dup_data = dict()
  cur = self.head
  pre = None
  
  while cur:
    if cur.data in dup_data: # if the data has existed in the dictionary, then removing it
      pre.next = cur.next
      cur = None
    else:
      dup_data[cur.data] = 1 # else, put the data in the dictionary
      pre = cur
    
    cur = pre.next
Now let’s dive into the solutions:
Let’s look into the implementation:
def print_nth_from_last(self, n):
    """
    to find the data of n-th node from the last, using length of the linked list
    """
    total_len = self.len_iterative()
    
    cur = self.head 
    while cur:    # countdown using total_len decrement
        if total_len == n:
            return cur.data
        total -= 1
        cur = cur.next
    if not cur:
        print(str(n) + "is greater than the length of the linked list.")
        return
p will point to the head node.q will point n nodes beyond head node.About two pointers pattern, the slow pointer will at the middle of the linked list, if the fast node is none or fast node’s next node is none. This because, in even length fast node will be the (length // 2 + 1), in odd length the fast node will be the excat middle one.
Here, this pattern can be used to look for the nth-node from the last node. The idea behind this solution is that the gap between p and q is the n, when p or q.next is None
def print_nth_from_last(self, n):
    """
    to find out the data of n-th node from the last, using two pointer pattern
    """
    p = q = self.head
    
    count = 0
    while q:    # q points to the n-th node from the head
        count += 1    # count += 1, because q have pointed 1 node already.
        if count >= n: break
        q = q.next
    
    if not q: print(str(n) + "is greater than the length of the linked list.")
    
    while p and q.next:  #gap between p and q is n, when p or q.next is None, the n-th node from last is found
        p = p.next
        q = q.next
        
    return p.data
Both the solutions have been made part of the LinkedList class. We can call the solution of the choice by passing in 1 or 2 as method to print_nth_from_last(self, n, method).
Originally published May 08, 2020
    
 Latest update May 09, 2020
Related posts :