DEV Community

Cover image for Unraveling the Chain: A Deep Dive into Linked Lists đź”—
Bibek Khanal
Bibek Khanal

Posted on

Unraveling the Chain: A Deep Dive into Linked Lists đź”—

Understanding Linked Lists in Computer Science

Linked lists are a fundamental data structure in computer science, forming the backbone of many complex systems and algorithms. Understanding them is crucial for any aspiring programmer or software engineer. This article will explore the different types of linked lists, their uses, historical significance, advantages, drawbacks, and why they remain incredibly relevant today.


What Exactly is a Linked List?

At its core, a linked list is a linear collection of data elements, called nodes. Unlike arrays, where elements are stored in contiguous memory locations, linked list nodes can be scattered anywhere in memory. Each node contains two key pieces of information:

  • Data: The actual value stored in the node (e.g., a number, a string, or a more complex object).
  • Pointer (or Link): A reference to the next node in the sequence. The last node typically points to null (or None), indicating the end of the list.

Think of it like a treasure hunt 🗺️: each clue (node) tells you where to find the next one, until you reach the final treasure (the end of the list).


Types of Linked Lists

There are several variations of linked lists, each with its own characteristics and use cases:

Singly Linked List

  • Structure: Each node has one pointer, pointing to the next node in the list. Traversal is unidirectional (forward only).
  • Simplicity: It’s the simplest form of a linked list.

Doubly Linked List

  • Structure: Each node has two pointers: one to the next node and one to the previous node.
  • Bidirectional Traversal: Allows for traversing the list both forwards and backwards, making operations like deletion more efficient as you don’t need to keep track of the previous node separately.

Circular Linked List

  • Structure: The last node’s next pointer points back to the first node instead of null, forming a circle.
  • Use Case: Useful for applications that require a continuous cycle, like a round-robin scheduler or managing a playlist that repeats. This can be implemented for both singly and doubly linked lists.

Doubly Circular Linked List

  • Structure: Combines the features of a doubly linked list and a circular linked list. The last node’s “next” pointer points to the first node, and the first node’s “previous” pointer points to the last node.
  • Flexible Traversal: Allows traversal in either direction, continuously.

Why Are Linked Lists Important? 🤔

Linked lists are a cornerstone of computer science education and practice for several reasons:

  • Dynamic Memory Allocation: They can grow or shrink in size during program execution, unlike arrays which typically have a fixed size. This makes them ideal for situations where the amount of data is unknown beforehand.
  • Efficient Insertions and Deletions: Adding or removing elements in the middle of a linked list is generally more efficient than in an array. For arrays, these operations often require shifting many elements. In a linked list, you only need to update a few pointers.
  • Foundation for Other Data Structures: Many more complex data structures, such as stacks, queues, hash tables (for collision resolution), and graphs, can be implemented using linked lists.
  • Understanding Pointers and Memory Management: Working with linked lists provides a solid understanding of how pointers and dynamic memory allocation work, which is crucial for low-level programming and system design.

Historical Significance 📜

The concept of linked lists was developed in the mid-1950s by Allen Newell, Cliff Shaw, and Herbert A. Simon at RAND Corporation while working on their Information Processing Language (IPL). IPL was one of the earliest programming languages and heavily utilized linked lists to manage its memory and data structures. This made linked lists one of the earliest dynamic data storage techniques. Their work laid the groundwork for many concepts in artificial intelligence and list processing languages like LISP.


Uses in Complex Systems and Algorithms ⚙️

Linked lists are not just academic concepts; they are actively used in various real-world applications and sophisticated algorithms:

  • Operating Systems

    • Memory Management: To keep track of free and allocated memory blocks.
    • File System Management: Directory structures can sometimes be represented using linked lists or tree-like structures based on them.
    • Process Management: Task schedulers often use linked lists (like circular queues) to manage processes waiting for CPU time.
  • Web Browsers

    • The “back” and “forward” navigation buttons often use a doubly linked list to store the history of visited pages.
  • Compilers and Interpreters

    • Symbol tables, which store information about identifiers (variables, functions, etc.), can be implemented using hash tables that employ linked lists for collision chaining.
  • Computer Graphics

    • To manage objects in a scene or vertices in a polygon mesh.
  • Other Data Structures

    • Stacks and Queues: Can be efficiently implemented using linked lists.
    • Graphs (Adjacency Lists): Used to represent sparse graphs, where each node (vertex) has a linked list of its neighbors.
  • Algorithms

    • Polynomial Representation: Representing polynomials where each node stores a coefficient and an exponent.
    • Radix Sort: Uses linked lists to group elements.
    • Big Number Arithmetic: Handling numbers larger than what standard data types can hold.

Advantages of Linked Lists Over Arrays 👍

  • Dynamic Size: As mentioned, they can easily grow and shrink. Arrays are static in size or require costly resizing operations.
  • Ease of Insertion/Deletion: Inserting or deleting elements in the middle of a linked list is generally O(1) (constant time) if you have a pointer to the node before the insertion/deletion point. For arrays, it’s O(n) (linear time) due to the need to shift elements.
  • Memory Efficiency (in some cases): While nodes in a linked list have an overhead for storing pointers, linked lists can be more memory efficient if the list is sparse or the maximum size is unknown, avoiding overallocation of memory that might occur with arrays.
  • No Wasted Memory (Contiguity not required): Since nodes are not stored contiguously, there’s no large chunk of memory that needs to be pre-allocated and potentially left unused.

Drawbacks of Linked Lists 👎

  • Memory Overhead: Each node in a linked list requires extra memory to store the pointer(s). For small data items, this overhead can be significant.
  • No Random Access: Elements in a linked list must be accessed sequentially starting from the first node. You cannot directly access the i-th element in O(1) time as you can with an array. Accessing an element takes O(n) time in the worst case.
  • Cache Performance: Because linked list nodes can be scattered in memory, they can lead to poor cache locality. Arrays, being contiguous, often benefit from better cache performance.
  • Reverse Traversal (for Singly Linked Lists): Traversing a singly linked list in reverse is cumbersome and typically requires recursion or an auxiliary stack, adding complexity or space. Doubly linked lists solve this but at the cost of an extra pointer per node.

Why Should YOU Know About Linked Lists? 🧑‍💻

Understanding linked lists is fundamental for several reasons:

  • Problem-Solving Skills: They teach you to think about data relationships and memory management at a lower level.
  • Technical Interviews: Questions about linked lists are very common in software engineering interviews as they test a candidate’s understanding of basic data structures, algorithms, and pointer manipulation.
  • Foundation for Advanced Concepts: They are the foundation for more advanced data structures and algorithms. Knowing them well will make learning these more complex topics easier.
  • Efficient System Design: A grasp of linked lists helps in making informed decisions about which data structure is most appropriate for a given problem, leading to more efficient and scalable system designs.

Python Implementation: Doubly Circular Linked List 🧑‍💻

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

  def __str__(self):
    return str(self.data)

class NodeHandler:
  def __init__(self):
    self.head = None
    self.tail=None
    self.count=0

  def _isempty(self):
    """checking if the list is empty or not"""
    return self.head is None

  def __len__(self):
    """Returns the node count of the list"""
    return self.count

  def append(self, data):
    """ Appends the node to the end of the list """
    newnode = Node(data)
    if self._isempty():
      self.head = newnode
      self.tail = newnode
      newnode.next = newnode
      newnode.prev = newnode
      self.count += 1
      print(f"appended data - {data}")
      return

    newnode.next = self.head
    newnode.prev = self.tail
    self.tail.next = newnode
    self.tail = newnode
    self.head.prev = newnode
    self.count += 1
    print(f"appended data - {data}")
    return

  def prepend(self, data):
    """Prepends the node to the end of the list"""
    newnode = Node(data)
    if self._isempty():
      self.head = newnode
      self.tail = newnode
      newnode.next = newnode
      newnode.prev = newnode
      self.count += 1
      print(f"prepended data - {data}")
      return

    newnode.next=self.head
    newnode.prev = self.head.prev
    self.head.prev = newnode
    self.head = newnode
    self.count += 1
    print(f"prepended data - {data}")
    return

  def traverse_forward(self):
    """Traverse the list from head to tail"""
    current = self.head
    for _ in range(self.count):
      print(current)
      current = current.next
    return

  def traverse_backward(self):
    """Traverse the list from tail to head"""
    current = self.tail
    for _ in range(self.count):
      print(current)
      current = current.prev
    return

  def search(self, key):
    """Searches the list for a key 
    if found returns True else False"""
    current = self.head
    for _ in range(self.count):
      if current.data == key:
        return True
      current = current.next
    return False

  def delete(self, key):
    """Deletes the node with the given key from the list"""
    current = self.head
    for _ in range(self.count):
      if current.data == key:
        # if current is the head
        if current == self.head:
          nextnode = current.next
          nextnode.prev = self.tail
          self.head = nextnode
          del(current)
          self.count -= 1

        # if current is the tail
        elif current == self.tail:
          prevnode = current.prev
          prevnode.next = self.head
          self.tail = prevnode
          del(current)
          self.count -= 1

        else:
          prevnode = current.prev
          nextnode = current.next
          prevnode.next = nextnode
          nextnode.prev = prevnode
          del(current)
          self.count -= 1
        print(f"key {key} deleted!")
        return
      current = current.next
    print(f"key {key} not found!")
    return

nodehandler = NodeHandler()
nodehandler.append(1)
nodehandler.append(2)
nodehandler.append(3)

#output:
#appended data - 1
#appended data - 2
#appended data - 3

nodehandler.prepend(0)
nodehandler.prepend(-1)
nodehandler.prepend(-2)

#output:
#prepended data - 0
#prepended data - -1
#prepended data - -2

nodehandler.traverse_forward()
#output:
#-2 
#-1 
#0 
#1 
#2 
#3

nodehandler.traverse_backward()
#output:
#3 
#2 
#1 
#0 
#-1 
#-2

nodehandler.search(1)
#output:
#True

nodehandler.delete(-2)
nodehandler.traverse_forward()
#output:
#key -2 deleted!
#-1
#0
#1
#2
#3
Enter fullscreen mode Exit fullscreen mode

Conclusion

Linked lists, in their various forms, are a versatile and powerful data structure. While they have their drawbacks, their strengths in dynamic memory management and efficient insertion/deletion operations make them indispensable in a programmer’s toolkit. From the early days of computing to modern complex systems, linked lists continue to play a vital role, underscoring their enduring importance in the world of computer science. Mastering them is a significant step towards becoming a more proficient and knowledgeable software developer.

Top comments (0)