In Ruby, a linked list is a data structure that consists of a sequence of nodes. Each node in the list contains a data element and a reference (or pointer) to the next node in the sequence. The first node is called the head of the list, and the last node is called the tail

The LinkedList class is a doubly linked list, which means that each node has a reference to both the next and the previous node in the sequence. This allows for efficient traversal in both directions.

Ruby has a built-in LinkedList class in the enumerator library that can be used to create and manipulate linked lists. Here’s an example of how to create a linked list using the built-in LinkedList class:

require 'enumerator'

list = LinkedList.new
list << 1
list << 2
list << 3

This creates a linked list with three nodes containing the values 1, 2, and 3, respectively. We can add nodes to the list using the << operator.

Here are some of the basic operations we can perform on a linked list using the LinkedList class:

  • Insertion at beginning: list.push(0)
  • Deletion at beginning: list.shift
  • Traversal: list.each {|node| puts node.value}
  • Searching for a node: list.find {|node| node.value == 2}
  • Insertion at a specific position: list.insert(2, 2.5)
  • Deletion at a specific position: list.delete_at(2)
  • Reversing a linked list: list.reverse
  • Finding the middle node: we can implement our own method as shown in the previous answer.

We will see some of the operations after implementing linked dlists in Ruby.

Here’s an example of a Node class that we can use to create our linked list:

class Node
  attr_accessor :data, :next

  def initialize(data)
    @data = data
    @next = nil
  end
end

With this Node class, we can create a linked list by chaining together nodes. For example, here’s how we can create a linked list with three nodes:

node1 = Node.new(1)
node2 = Node.new(2)
node3 = Node.new(3)

node1.next = node2
node2.next = node3

head = node1 # The head of the linked list is the first node

Now that we have our Node class and our linked list, let’s go through each of the operations you’ve asked for.

Creation

As shown above, a linked list is created by creating node objects and linking them together by setting the next pointer of each node to the next node in the sequence. The head of the list is set to the first node in the sequence.

See also  Introduction to doubly linked list

Insertion at beginning

To insert a new node at the beginning of the list, we need to create a new node object with the data we want to insert, set its next pointer to the current head of the list, and then set the head of the list to the new node. Here’s the code:

def insert_at_beginning(head, data)
  new_node = Node.new(data)
  new_node.next = head
  head = new_node
  return head
end

Deletion at beginning

To delete the first node in the list, we simply set the head of the list to the second node in the sequence. The first node is then no longer referenced and will eventually be garbage collected. Here’s the code:

def delete_at_beginning(head)
  head = head.next
  return head
end

Traversal

To traverse the linked list, we can start at the head of the list and follow the next pointer of each node until we reach the end of the list (i.e., the next pointer of the last node is nil). Here’s the code:

def traverse(head)
  current_node = head
  while current_node != nil
    puts current_node.data
    current_node = current_node.next
  end
end

Searching for a node

To search for a node with a specific value in the list, we can start at the head of the list and follow the next pointer of each node until we find a node with the value we’re looking for or we reach the end of the list. Here’s the code:

def search(head, value)
  current_node = head
  while current_node != nil
    if current_node.data == value
      return current_node
    end
    current_node = current_node.next
  end
  return nil # Node not found
end

Insertion at a specific position

To insert a new node at a specific position in the list, we need to find the node that currently occupies that position, create a new node object with the data we want to insert, set its next pointer to the node that was previously at that position, and then set the next pointer

of the previous code:

of the previous node to point to the new node. Here’s the code:

def insert_at_position(head, data, position)
  if position == 0
    return insert_at_beginning(head, data)
  end

  current_node = head
  (position - 1).times do
    if current_node == nil
      return head # position out of range
    end
    current_node = current_node.next
  end

  if current_node == nil
    return head # position out of range
  end

  new_node = Node.new(data)
  new_node.next = current_node.next
  current_node.next = new_node

  return head
end

Deletion at a specific position

To delete a node at a specific position in the list, we need to find the node that currently occupies that position, set the next pointer of the previous node to the node that follows the node we want to delete, and then delete the node. Here’s the code:

def delete_at_position(head, position)
  if position == 0
    return delete_at_beginning(head)
  end

  current_node = head
  (position - 1).times do
    if current_node == nil || current_node.next == nil
      return head # position out of range
    end
    current_node = current_node.next
  end

  if current_node == nil || current_node.next == nil
    return head # position out of range
  end

  current_node.next = current_node.next.next

  return head
end

Reversing a linked list

To reverse the linked list, we need to iterate through the list and flip the next pointers of each node so that they point in the opposite direction. We also need to keep track of the previous node so that we can update its next pointer. Finally, we set the head of the list to the last node we visited (i.e., the previous node when we reached the end of the list). Here’s the code:

def reverse(head)
  current_node = head
  previous_node = nil

  while current_node != nil
    next_node = current_node.next
    current_node.next = previous_node
    previous_node = current_node
    current_node = next_node
  end

  head = previous_node

  return head
end

Splitting a linked list

To split the linked list in two, we need to find the middle node and set its next pointer to nil. The first half of the list is the original list up to the middle node, and the second half of the list is the nodes after the middle node. Here’s the code:

def split(head)
  if head == nil || head.next == nil
    return [head, nil]
  end

  slow_pointer = head
  fast_pointer = head.next

  while fast_pointer != nil
    fast_pointer = fast_pointer.next
    if fast_pointer != nil
      slow_pointer = slow_pointer.next
      fast_pointer = fast_pointer.next
    end
  end

  second_half_head = slow_pointer.next
  slow_pointer.next = nil

  return [head, second_half_head]
end

Joining two linked lists

To join two linked lists, we need to find the last node of the first list and set its next pointer to the head of the second list. Here’s the code:

require 'enumerator'

# create first list
list1 = LinkedList.new
list1 << 1
list1 << 2
list1 << 3

# create second list
list2 = LinkedList.new
list2 << 4
list2 << 5
list2 << 6

# join the two lists
if list1.tail
  list1.tail.next = list2.head
else
  list1.head = list2.head
end

# print the joined list
list1.each {|node| puts node.value}

Finding the middle node

To find the middle node of the linked list, we can use the slow-pointer/fast-pointer approach that we used in the split method. We start with two pointers: a slow pointer that moves one node at a time, and a fast pointer that moves two nodes at a time. When the fast pointer reaches the end of the list, the slow pointer will be at the middle node. Here’s the code:

def find_middle_node(head)
  if head == nil
    return nil
  end

  slow_pointer = head
  fast_pointer = head

  while fast_pointer != nil
    fast_pointer = fast_pointer.next
    if fast_pointer != nil
      slow_pointer = slow_pointer.next
      fast_pointer = fast_pointer.next
    end
  end

  return slow_pointer
end

Time and Space complexity

Time and space complexity of each operation on a linked list:

  • Creation: O(1) time complexity, O(n) space complexity (for n nodes in the list)
  • Insertion at beginning: O(1) time complexity, O(1) space complexity
  • Deletion at beginning: O(1) time complexity, O(1) space complexity
  • Traversal: O(n) time complexity, O(1) space complexity
  • Searching for a node: O(n) time complexity, O(1) space complexity
  • Insertion at a specific position: O(n) time complexity in the worst case (when inserting at the end of the list), O(1) time complexity in best case (when inserting at the beginning), O(1) space complexity
  • Deletion at a specific position: O(n) time complexity in the worst case (when deleting the last node), O(1) time complexity in best case (when deleting the first node), O(1) space complexity
  • Reversing a linked list: O(n) time complexity, O(1) space complexity
  • Splitting a linked list: O(n) time complexity, O(1) space complexity
  • Joining two linked lists: O(n) time complexity (where n is the length of the first list), O(1) space complexity
  • Finding the middle node: O(n) time complexity, O(1) space complexity
See also  Remove duplicates from a linked list

These are just general time and space complexity estimates, and actual performance may vary depending on factors such as the size of the input data and the implementation details. Additionally, these estimates assume that the linked list is implemented using a basic node structure as shown in the previous answers; using more complex node structures or adding additional features to the linked list may affect the time and space complexity of the operations.