Data Structure - Single Linked List in Python

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.🤓

Single Linked List

1. Strcuture

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.

2. Difference with Array

Insertion/ Deletion

Accessing Elements

3. Implementation

1). General classes

Single linked lists contain two kinds of classes:

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
         

2). Insertion

Now we’ll insert elements in a linked list by different ways:

3). Deletion

3-1). Deletion by Value

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
3-2). Deletion by position

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

4). Length

There are two ways to calculate the number of nodes in a linked list, which are iterative and recursive manners.

4-1). Iterative solution
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
        
4-2). Recursive solution

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.

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)

5). Node Swap

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.
    

6). Reverse

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.

6-1). Iterative implementation

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    
6-2). Recursive Implementation

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

7). Remove Duplicates

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

8). Nth-from-last Nodes

Now let’s dive into the solutions:

  1. ragualer way, counting the length, break down this solution in two simple steps:
    • Calculate the length of the linked list.
    • Count down from the total length until n is reached.

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
  1. two pinters pattern, when fast pointer or next node of fast pointer is None, the slow pointer will reach the n
    • 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 :