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 Go, 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 Go. Let's get started!
A linked list is a linear data structure similar to arrays. However, 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 not set if the linked list is empty. Linked lists come up quite often in coding challenges.
There are two popular types of linked lists: singly linked lists and doubly linked lists. 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 Go, we'll define a struct
to serve as a blueprint for a node.
A Node struct
mainly consists of a data
(the data you want to store) field and a next
field (the reference to the next node). In our case, we'll create a Node struct
to store integer data, with a constructor-like function to initialize the node.
Go1package main 2 3type Node struct { 4 Data int 5 Next *Node 6} 7 8func NewNode(data int) *Node { 9 return &Node{Data: data, Next: nil} 10}
In Go, the Next
field of the Node
struct is of type *Node
, which is a pointer to another Node
. Pointers allow us to create references to other nodes, enabling the dynamic linking that forms the basis of linked lists. Without this, nodes would be isolated entities without connections to subsequent elements in the list.
Fantastic! You now know how to create 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 struct
for this.
Go1package main 2 3type LinkedList struct { 4 Head *Node 5} 6 7func (list *LinkedList) Append(data int) { 8 node := NewNode(data) 9 10 if list.Head == nil { 11 list.Head = node 12 } else { 13 last := list.Head 14 for last.Next != nil { 15 last = last.Next // find the node at the end 16 } 17 last.Next = node // set the new node as the last 18 } 19}
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 for
loop to navigate to the end of the list. The new node is then appended after the last node in the list. For example, if our list initially contained one node with a value of 1
, calling Append(2)
would result in a list with 2 nodes: 1 -> 2 -> nil
.
Now, what if we want to add a new node at the beginning of our list? We'll write a method AddFirst
to achieve this operation. It simply reassigns the Head
.
Go1package main 2 3func (list *LinkedList) AddFirst(data int) { 4 node := NewNode(data) 5 6 if list.Head != nil { 7 node.Next = list.Head // set head to the added value 8 } 9 list.Head = node 10}
Removing a node from a linked list is also an essential functionality. We will add a Delete
method to our LinkedList struct
to remove a node with a particular data value.
Go1package main 2 3func (list *LinkedList) Delete(data int) { 4 if list.Head == nil { 5 return 6 } 7 8 if list.Head.Data == data { 9 list.Head = list.Head.Next 10 return 11 } 12 13 current := list.Head 14 for current.Next != nil { 15 if current.Next.Data == data { 16 current.Next = current.Next.Next 17 return 18 } 19 current = current.Next 20 } 21}
We traverse the list like in the Append
method, searching for a node with specific data. If Head
is nil
, our list is empty and return right away. We check Head
first and then move to checking other nodes in the list. If found, it is removed from the list by retargeting the previous node to the node after the target. We should also note that this only removes the first instance of this particular data within the list. In other words, the Delete
method removes at most one node from the LinkedList
.
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 time complexity because, in the worst-case scenario, we'd have to traverse through the entire list.
- Inserting or deleting a node: It's 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 be 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 Go, 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 Go.