Skip to content

Implementing Circular Linked Lists in Python: A How-To Guide

Updated: at 02:01 AM

A circular linked list is a fundamental data structure used extensively in computer science. Unlike a regular singly linked list that has a null reference at the end, a circular linked list connects the last node back to the first node to form a continuous loop. This structure lends itself well to problems that involve traversal or rotation through elements.

In this comprehensive guide, we will explore how to implement a circular linked list in Python. We will cover the key aspects of building this data structure from scratch including:

Proper understanding of pointers and references in Python is required to implement linked lists efficiently. So let’s get started!

Table of Contents

Open Table of Contents

Prerequisites

To follow along with the code examples, you should have:

We will be using Python 3.x in the code snippets. Feel free to code along in an IDE like PyCharm or Jupyter Notebook.

Node Class

The building block of a linked list is a node. Let’s define a Node class in Python to represent each element in the list:

class Node:
    def __init__(self, data):
        self.data = data
        self.next = None

A node contains two fields:

We initialize next to None indicating the lack of connection upon creation.

Circular Linked List Class

Now let’s utilize the Node class to build our circular linked list data structure:

class CircularLinkedList:
    def __init__(self):
        self.head = None

The CircularLinkedList class contains a single head field pointing to the first node of the list. We initialize it to None indicating an empty list.

Next, we will write methods to insert, delete and print elements in the circular linked list.

Inserting Elements

There are two ways to insert elements in a circular linked list:

  1. Insert at Head
  2. Insert at Tail

Let’s define both methods:

Insert at Head

This method inserts the given node at the beginning of the list.

def insertHead(self, data):
    newNode = Node(data)
    if self.head is None:
        self.head = newNode
        newNode.next = self.head
    else:
        current = self.head
        while current.next != self.head:
            current = current.next
        current.next = newNode
        newNode.next = self.head
        self.head = newNode

The steps are:

  1. Create a new Node with the given data
  2. Check if list is empty, if so directly assign it as head
  3. Otherwise, traverse to end of list and insert new node before head
  4. Update head to new node

Insert at Tail

This method inserts a new node at the end of the circular linked list:

def insertTail(self, data):
    newNode = Node(data)
    if self.head is None:
        self.head = newNode
        newNode.next = self.head
    else:
        current = self.head
        while current.next != self.head:
            current = current.next
        current.next = newNode
        newNode.next = self.head

The logic is very similar to insert at head:

  1. Create new node
  2. Check for empty list then directly insert
  3. Traverse to end, insert new node, and connect to head

With these two methods, we can insert elements at the front or back of the circular linked list.

Deleting Elements

For removing elements from a circular linked list, there are also two approaches:

  1. Delete from Head
  2. Delete from Tail

Let’s implement them:

Delete from Head

This method removes the first node of the list:

def deleteHead(self):
    if self.head is None:
        return None
    current = self.head
    while current.next != self.head:
        current = current.next
    toDelete = self.head
    current.next = self.head.next
    self.head = self.head.next
    return toDelete.data

The logic is:

  1. Check for empty list
  2. Traverse to end and save last node
  3. Point last node to second node, removing head
  4. Update head and return old head’s data

Delete from Tail

This method removes the last node:

def deleteTail(self):
    if self.head is None:
        return None
    current = self.head
    while current.next.next != self.head:
        current = current.next
    toDelete = current.next
    current.next = self.head
    return toDelete.data

The steps are:

  1. Check for empty list
  2. Traverse to second last node
  3. Point second last node to head, removing last node
  4. Return data of removed node

Together, these two methods allow removing elements from either end of the circular linked list.

Traversing the List

Since a circular linked list loops back, we need to be careful when traversing it. We cannot stop when current.next becomes None as in a normal linked list.

Let’s write a printList method to print the circular linked list:

def printList(self):
    if self.head is None:
        return
    current = self.head
    print(current.data, end=" ")
    while current.next != self.head:
        current = current.next
        print(current.data, end=" ")
    print()

It loops until the next pointer again points back to the head, indicating the end of traversal.

We can also create a generator to yield the node values during traversal:

def traverse(self):
    current = self.head
    while current:
        yield current.data
        current = current.next
        if current == self.head:
            break

This allows iterating over the circular linked list using a for loop.

Putting It All Together

Let’s test our circular linked list implementation with a simple example:

cll = CircularLinkedList()
cll.insertHead(5)
cll.insertHead(10)
cll.insertTail(7)

print([node for node in cll.traverse()]) # [10, 5, 7]

cll.deleteHead()
cll.deleteTail()

print([node for node in cll.traverse()]) # [5]

This shows basic usage of the insert, delete and traverse methods we implemented. The circular list retains its structure and looped references even after deletions.

Some key points:

The circular linked list enables efficient insertions and deletions from both ends while maintaining constant access to the first node. This makes it effective for problems involving circular buffers or rotations.

Time and Space Complexity

Let’s analyze the time and space complexity of operations on our circular linked list implementation:

Time Complexity

Space Complexity

Circular linked lists provide constant time insertions and deletions giving them an edge over arrays in certain use cases. The compromise is slow access and search due to linear traversal.

Applications

Some useful applications of circular linked lists include:

Game development, operating system processes scheduling, multimedia editing software are some domains that can benefit from circular linked lists.

Example Applications

Circular linked lists are commonly used in game development for seamless looping of sprites or motion. For example, a spaceship orbiting a planet can be modelled as nodes in a circular list with connections to enable smooth traversal along the orbit path. As new nodes are added or removed, the ship progresses along the orbit.

Operating systems also leverage circular linked lists to maintain processes queued for execution on the CPU. The OS scheduler loops through the circular list, running each process in turn for its allocated time slice. New processes are added to the list and existing ones removed as they terminate. The circular structure allows smooth, indefinite cycling through the queued processes.

In audio editing software, circular linked lists help enable seamless playback looping. Audio samples can be stored as nodes and connected in a circle. Playback simply traverses this circular list repeatedly, providing continuous uninterrupted playback. Inserting or deleting samples is also efficient, making editing seamless.

Hope these real-world examples help illustrate the power and flexibility of circular linked lists for problems involving cyclic continuity! Let me know if you have any other questions.

Conclusion

In this guide, we learned how to implement a circular linked list data structure in Python from scratch. The key takeaways include:

With the insert, delete and traversal methods implemented, you can now incorporate circular linked lists into your Python programs for seamless circular buffer and queueing operations. They open up applications in domains like multimedia, gaming and OS scheduling.

Hopefully this step-by-step explanation helps demystify this fundamental data structure! Circular linked lists strike an effective balance between arrays and normal linked lists providing efficient inserts/deletes while maintaining access to the first node. Add them to your Python coding toolbox today.