Cliff

The wind is slapping my ears. I hear nothing but the direction of air. My heavy panting cover the noise as I try to cope with the exhaustion of coming here. I stand near the edge, anxious of the…

Smartphone

独家优惠奖金 100% 高达 1 BTC + 180 免费旋转




Linked List

Data structures are everywhere. Every developer should know how they work under the hood, starting from the most common ones.

A data structure priorly describes how the data is organised, accessed, associated and processed.

Using data structures you can keep your data in memory and efficiently access them.

You should pick the data structure that is the most suitable for your purposes to minimize space in memory and access time.

Some algorithms are designed to run upon certain data structures.

The critical part of developing such data structures in Go is the lack of generics (this post has been written in January 2020). While other programming languages such as Java, C# etc. allow us to work with generics, Go has no way to do that, so we have to use the empty interface: interface{}.

Let’s start with the most popular class of data structures: linear data structures.

A Linear Data Structure arranges the data into a sequence and follows some sort of order.

The linear data structure is a single level data structure while non-linear data structures are the multilevel data structure.

The most popular Linear Data Structures are:

- Stack

- Array

- Linked List

- Queue

In this post, we will learn those Linear Data Structures and we will develop them using Go.

The structures we will develop are only for educational purpose, you should not use them in production, also because some of them are already implemented in Go’s standard library.

None of those structures is thread-safe. We will make them thread-safe in future posts (I wish).

A stack of cookies

Beyond cookies, a stack or LIFO (last in, first out) is an abstract data type that serves as a collection of elements, with two principal operations:

- push: adds an element to the collection. This method requires O(1);

- top: returns the last element that was added without removing it. Requires O(1);

- pop: removes the last element that was added. Requires O(1);

In a stack, both the operations of push and pop takes place at the same end that is top of the stack. It can be implemented by using both array and linked list.

You can think of a stack as a… stack. Yeah! Maybe the stack of plates is the best metaphor when thinking about this data structure.

In a stack of plates you can (and should at least if you don’t want to break all your plates) access only the plate on the top. So you can put a plate on the top of the stack (push), you can take a plate on the top of the stack and get rid of it (pop) or you can observe the plate on the top of the stack without removing it (top).

Now let’s develop our stack using Go!

Let’s start defining our structure:

The best way to implement a stack is by using an array and an int that indexes the top of the stack.

To simplify the stack’s operations we assume that when the index is -1 the stack is empty and when the stack is full we cannot insert other elements.

If you want to avoid the latter limitation, you should either control the size of the array dynamically or you should use another structure such as a linked list.

The following image shows the empty stack, i.e. the one with no elements and curInt equals to -1.

Empty stack

The function NewStack takes an integer as argument and returns a pointer to a stack having both array and top’s index initialized.

The method IsEmpty is quite simplistic: it returns true if curInd is less than 0, otherwise it returns false.

Now let’s dive into the core methods of the stack:

The method Top returns an interface but does not alter the stack. It first checks if the stack is empty, if so it panics, otherwise returns the element of the underlying array at the index specified by curInd.

The method Pop is similar to its relative Top but "deletes" the element at the top. Actually, this method stores the element at curInd in a temporary variable el, sets the location at curInd to nil (garbage collector will do the tough job) and decrements curInd, finally returns el.

The method Push does the opposite: it increments curInd and sets the element at location curInd of the stack array to be the element taken as the argument.

The following image shows a stack with a single element, so curInd is 0 (namely, the first location of the array):

The following image shows a stack with two elements, so curInd is 1:

An array

The array is a data structure used to store homogeneous elements at contiguous locations. The size of an array can be provided before storing data or the array can dynamically adapt its size to keep all the elements.

Operations on the array are:

Go already has the array data structure, so it does not make any sense to develop our array. However, the arrayof Go does not have the search method, so we'll develop it!

The method Search is very simplistic: it iterates over the elements of the array and, if this element is equal to the element el passed as the argument, returns its index. If the method does not find the element el returns -1.

A Linked List is a linear data structure (like arrays) where each element is a separate object. Each element, called node, of a list is comprising of two items: the data and a reference to the next node.

There are different kinds of Linked List, but we will explore only the most popular one: Single Linked List.

Operations on Linked Lists are:

The building blocks of our Linked List are Nodes. A node contains an element (el) and a reference to the next node (next) in the list. In that way we could iterate over our list starting from a node.

The LinkedList itself only contains a reference to a node which is the first of the list (head).

The following image explains the structure of a linked list:

Representation of a LinkedList in memory

Now let’s dive into its methods:

Get and Search are quite similar: we start from the head and move to the next node until we reach the right position or find the element we are looking for. A temporary variable called cur (by convention) initially points to head, then to its next, then to the next's next and so on.

The Add method requires a detailed explanation. We first create a new node n putting the element e into it. Then we have to discriminate between two cases:

The following images may clarify this concept:

Our Linked List before Add:

LinkedList before Add

Our Linked List after Add an element in position 1:

LinkedList after Add in position 1

The Delete method is quite similar to Add but does the opposite. The elimination of the variable from the memory is up to the garbage collector.

The following images may clarify this concept:

Our Linked List before Delete:

LinkedList before Delete

Our Linked List after Delete an element in position 1:

As you can see looking at the image, the element in position 1 is still there but is not reachable any more. Now it is up to the garbage collector to remove it from the memory.

The String method shows how to iterate over a list using a for loop to create a string that describes the current state of the list.

A queue or FIFO (first in, first out) is an abstract data type that serves as a collection of elements, with two principal operations:

It can be implemented by using both array and linked list. Our implementation will be based on a Linked List.

The implementation of theQueue is obviously similar to the one for LinkedList but we also store another pointer to the tail which is the side from which we insert new elements.

The Enqueue method first creates a new Node n, then, if q.tail == nil (the queue is empty) makes both q.head and q.tail to point to the new node, otherwise makes both q.tail and q.tail.next to point to the new node.

It is worth noting that q.tail is the latter inserted node, so its next previously pointed to nil.

The Dequeue method first checks if q.head == nil, so we have an empty queue and it must return nil, otherwise, it stores the q.head value to a temporary variable n, checks if its next pointer is nil (single element queue) (if so makes q.tail pointing to nil to have an empty queue). Finally, it returns the el value of n.

Different data structures bring different advantages. You should know when to pick one rather than another and this knowledge comes with experience. However, it is not hard to understand the benefits that come with those simple data structures:

I wish this post would help beginner developers using the correct data structure for their algorithms.

Add a comment

Related posts:

What is the Endocannabinoid System?

The benefits of medical marijuana and hemp are many and as every day passes, research deepens into how we can use this amazing plant to heal and help so many people around the world. How do the…

The Rise of Drones for Commercial Use

Drones are revolutionizing the industrial landscape. Data from Grand View Research shows that the commercial drone market was valued at $5.80 billion in 2018 and is projected to grow at a CAGR of…

Introduction to Nervos Network

During DevCon4 in Prague, Nervos Network and Celer Network co-hosted another successful edition of The Future of Layer 2 panel. This time, researchers and engineers from 15 projects gathered at…