Hello there! Today, we will explore Linked Lists, a core data structure crucial for organized data management and establishing relationships between data.
We will mark some essential milestones: an introduction to Linked Lists, their real-world applications, their implementation in Ruby
, and the different operations you can perform on them.
By the end of this lesson, you will be well-equipped to implement and operate Linked Lists using Ruby
. Let's get started!
A Linked List is a linear data structure similar to arrays. But, unlike arrays, they are not stored in contiguous memory locations. Each element in a Linked List is part of a node
. A node
comprises data and a reference (or link) to the next node
in the sequence. This structure facilitates efficient insertions and deletions.
The head
is also an essential concept in Linked Lists. It is the first node
in the list and serves as a reference to the entire list. The head
is nil
if the Linked List is empty.
Singly linked lists come up quite often in coding interviews. Interviewers from tech giants, start-ups, and just about every company testing your coding abilities will pose challenges based on this concept.
Here's another interesting point: While singly linked lists might not be extensively used in real-world applications, they form the foundational knowledge for understanding doubly linked lists, which are indeed quite common. A singly linked list contains nodes with a single link pointing to the next node, whereas a doubly linked list has nodes with links to both the next and the previous nodes.
To begin implementing Linked Lists, we first need to understand the structure of a node
, the building block of a Linked List. In Ruby
, we'll create a class to serve as a blueprint for a node
.
A Node
class mainly consists of data
(the data you want to store) and next_node
(the reference to the next node
). In our case, we'll create a Node
class to store integer data, with an initialize
method to set up the node.
Ruby1class Node 2 attr_accessor :data, :next_node 3 4 def initialize(data) 5 @data = data 6 @next_node = nil 7 end 8end
Fantastic! You now know how to set up a node
in a Linked List.
In this section, we'll learn how to add a new node at the end of our Linked List. We'll implement an append
method in our LinkedList
class for this.
Ruby1class Node 2 attr_accessor :data, :next_node 3 4 def initialize(data) 5 @data = data 6 @next_node = nil 7 end 8end 9 10class LinkedList 11 def initialize 12 @head = nil 13 end 14 15 def append(data) 16 node = Node.new(data) 17 18 if @head.nil? 19 @head = node 20 else 21 last = @head 22 last = last.next_node while last.next_node 23 last.next_node = node 24 end 25 end 26end
The code checks if @head
is nil
, which would be the case for an empty list. If that's true, @head
is set to the new node, meaning this new node is the first and only node in the list. If the linked list is not empty (@head
is not nil
), the code enters a loop to navigate to the end of the list. The new node is then appended after the last node in the list.
Now, what if we want to add a new node at the beginning of our list? We'll write a method add_first
to achieve this operation.
Ruby1def add_first(data) 2 node = Node.new(data) 3 4 if @head 5 node.next_node = @head 6 end 7 @head = node 8end
It simply reassigns the @head
.
Removing a node from a Linked List is also an essential functionality. We will add a delete
method to our LinkedList
class to remove a node with a particular data value.
Ruby1def delete(data) 2 return if @head.nil? 3 4 if @head.data == data 5 @head = @head.next_node 6 return 7 end 8 9 current = @head 10 while current.next_node 11 if current.next_node.data == data 12 current.next_node = current.next_node.next_node 13 return 14 end 15 current = current.next_node 16 end 17end
We traverse the list like in the append
method, searching for a node with specific data. If found, it is removed from the list by retargeting the previous node to the node after the target.
While understanding the implementation of Linked Lists is great, it's equally crucial to comprehend the performance of our operations. This understanding generally comes through complexity analysis, examining the time (number of operations) and space (memory used) needed for an operation.
Here's a summary of the performance of particular operations in Linked Lists:
- Accessing an element: It has
O(n)
time complexity because, in the worst-case scenario, we'd have to traverse through the entire list. - Inserting or deleting a node: It's
O(1)
if we're adding or removing from the front of the list. However, if it's at the end or in the middle, it would beO(n)
because we'd have to traverse the list to find the position.
Great job sticking with it throughout this intriguing journey, from understanding the concept of Linked Lists to implementing them in Ruby
, exploring critical operations, and understanding their performance!
Up ahead, we have lined up some practice sessions. These sessions will provide you with real-time experience implementing and manipulating Linked Lists using Ruby
.
