Implementing the queue using the linked list as the underlying data structure is much simpler than using arrays as the underlying data structure.

I will assume that you understand the fundamentals of the queue data structure.

In this post, we will be writing the queue using the linked list as the underlying data structure.

This tutorial exists not only to show users how to implement the queue using linked lists but to also demonstrate how important it is to master the fundamentals.

Having a solid grasp on the fundamentals not only gives you a greater arsenal of tools to work with. It also accelerates the speed at which you learn other topics, including more complex data structures.

If you don’t know what a linked list is, I strongly suggest that you come back after learning how the linked list works.

Queue Linked List Overview

If you know how to implement a linked list, then congratulations! You pretty much know how to implement the queue using linked lists. It is now just a matter of customizing the interface so that it behaves like a queue.

As you may already know, the queue has the following methods

enqueue() – add an item to the back of the queue.

– add an item to the back of the queue. dequeue() – remove the first item from the queue and return its value.

– remove the first item from the queue and return its value. front() – return the value of the first item without removing it from the queue.

Because the queue is a FIFO (first in, first out) data structure, we add items to the end of the queue and remove items from the front of the queue.

Enqueue()

As I mentioned before, the queue is a FIFO data structure. Therefore, the queue linked list implementation’s enqueue method adds data to the end of the list. That way, the elements will be removed from the queue in the order that they were inserted.

This involves the following steps.

Create a new node with the data to insert. Set the next node pointer of the new node to null . Because the tail is the last element in the queue, the next node pointer should point to null . Set the previous node pointer of the new node to the current tail . The current tail (in the next step) will be pushed down. In other words, it will become the second last element. Update tail pointer reference to point to the newly created node.

Dequeue()

In the dequeue operation, we will be removing the item at the front of the queue. In other words, the head of the linked list.

Get the next node after the head . Set it as the new head . Set the previous pointer of the new head to null . Remove the previous head .

When working with C++, make sure to use the delete keyword to remove the previous head. Otherwise, this will cause a memory leak in your program, which is something that you never want, under any circumstance.

Front()

As the name suggests, this method simply enables users to retrieve the first element in the queue without actually removing it. The rest of the methods such as size() and isEmpty () is quite self-explanatory.

I want to finish off by stating that giving meaningful names to methods is an important part of writing good code. It is something that we all need to be mindful of, regardless of where we are in our walk as developers.

It is a small part of writing clean, readable code that adds value to your team. Spending the extra time in making your code as easy to extend and modify will pay dividends in the form of reduced time spent updating the code in the future.

Source Code

Assuming that you read through my previous tutorial on the linked list and queue implementation using arrays, this should be a cakewalk.

The queue using linked list is pretty much a linked list with the restriction that users can only interact with the first element in the list and add items to the end of the linked list.

Once again, all the source code can be found on my GitHub profile. The source code is available in the following languages.

Thank you for reading and happy coding!

Java

JavaScript

C++ Queue Interface public interface Queue<T> { /** * Insert data into the Queue * */ void enqueue(T data); /** * Remove and return the element at the front of the queue * */ T dequeue(); /** * Get the size of the Queue * */ int size(); /** * Get data at the front of the Queue * @return T data * */ T front(); /** * Check if queue is empty * */ boolean isEmpty(); /** * Print items in the queue * */ void print(); } LinkedListQueue.Java Queue.js /** * @author Jay Lee * @see https://www.thecodingdelight.com * Queue implementation for my blog post. * * For this queue implementation, * function expressions will be private functions * E.g. * var privateFunction = function privateFunction() { * // I am a private function * }; * function declarations starting with a capital letter * will be Constructor functions. * E.g. * function Node() { * // Constructor function * } * */ (function () { "use strict"; var toString = Object.prototype.toString; var getType = function getType(item) { return toString.call(item); }; /** * Node is a wrapper around data * with pointers to the next and previous node * * @param data the data * @param {Node} nextNode pointer to the next node * */ function Node(data, nextNode) { if (!(this instanceof Node)) { return new Node(data, nextNode); } this.data = data; this.nextNode = nextNode || null; } Node.prototype.setNext = function setNext(node) { this.nextNode = node; return this; }; Node.prototype.getNext = function getNext() { return this.nextNode; }; /** * @this {Queue} * @param {Array} item the item to be added to the Queue * */ var checkDataType = function checkDataType(item) { var itemType = getType(item), queueDataType = this.dataType; if (itemType !== queueDataType) { throw new Error("Item " + item + " is of type: " + itemType + ". Queue only accepts data of type: " + queueDataType); } }; /** * add single data to the back of the Queue * * @this {Queue} * @param data * */ var addToBack = function addToBack(data) { if (this.head == null) { this.head = new Node(data); this.dataType = getType(data); } else { checkDataType.call(this, data); // Set the new head to added data // and next node to current head var newNode = new Node(data); if (this.tail == null) { this.tail = newNode; // Update references // Head --> Tail // Head <-- Tail this.head.setNext(this.tail); } else { var prevTail = this.tail; var newTail = new Node(data); // have previous tail point to new tail prevTail.setNext(newTail); this.tail = newTail; } } this.size++; }; /** * @constructor * */ function Queue() { // Ensure that we are creating a new // queue object if (!(this instanceof Queue)) { return new Queue(); } // initialize before adding any items this.size = 0; this.head = null; this.tail = null; // Default equality check is strict equality check. this.equals = function compareTo(a, b) { return a === b; }; } /** * @param item the item to add to the front of the queue. * @return {Queue} this * */ Queue.prototype.enqueue = function enqueue(item) { addToBack.call(this, item); return this; }; /** * Remove item at the front of the queue * @return {Queue} this * */ Queue.prototype.dequeue = function dequeue() { if (this.head != null) { var nextNode = this.head.getNext(); // if Item contains 2 or more elements if (nextNode == null) { this.tail = null; } // Remove current head this.head = null; this.head = nextNode; this.size--; } return this; }; // Expose Queue to the global scope if (window.Queue === undefined) { window.Queue = Queue; } else { throw new Error("Queue is already defined in this scope"); } })(); var Queue = Queue(); Queue.enqueue(1).enqueue(2).dequeue().dequeue(); Queue.enqueue(1).enqueue(2).enqueue(3); Queue.enqueue(100); console.log(Queue); LinkedListQueue.h #pragma once /** * \brief * \tparam T */ template <class T> class LinkedListQueue { /** * \brief Node of the underlying linked list */ typedef struct Node { T data; Node* next; } *nodePtr; private: nodePtr head; nodePtr tail; int size; /** * Private methods * ================== */ nodePtr CreateNode(T data); /** * Clean up dynamically allocated resources */ void EmptyQueue(); public: LinkedListQueue(); ~LinkedListQueue(); void Enqueue(const T data); T Dequeue(); int Size() const; bool IsEmpty() const; void Print() const; }; LinkedListQueue.cpp #include "stdafx.h" #include "LinkedListQueue.h" #include <ostream> #include <iostream> struct EmptyQueueException : public std::exception { const char * what() const throw () { return "C++ Exception: Queue is empty ... "; } }; template <class T> typename LinkedListQueue<T>::nodePtr LinkedListQueue<T>::CreateNode(T data) { nodePtr newNode = new Node; newNode->data = data; newNode->next = nullptr; return newNode; } template <class T> void LinkedListQueue<T>::EmptyQueue() { while (this->head != nullptr) { nodePtr current_node = this->head; std::cout << "Removing " << current_node->data << " ... " << std::endl; this->head = this->head->next; delete current_node; } this->tail = nullptr; this->size = 0; } template <class T> LinkedListQueue<T>::LinkedListQueue() { this->head = nullptr; this->tail = nullptr; this->size = 0; } template <class T> LinkedListQueue<T>::~LinkedListQueue() { std::cout << "Cleaning resources ... " << std::endl; this->EmptyQueue(); } /** * \brief * \param data The data to add to the queue. * Remember, it is added to the back of the queue. Wait in line! */ template <class T> void LinkedListQueue<T>::Enqueue(const T data) { nodePtr newNode = CreateNode(data); if (this->IsEmpty()) { this->head = newNode; } else { if (this->tail == nullptr) { this->tail = newNode; this->head->next = this->tail; } else { nodePtr prevTail = this->tail; this->tail = newNode; // Set previous tail's next node pointer to point at new tail prevTail->next = this->tail; } } ++this->size; } template <class T> T LinkedListQueue<T>::Dequeue() { T dataToReturn; if (this->IsEmpty()) { throw EmptyQueueException(); } else { dataToReturn = this->head->data; if (this->tail == nullptr) { delete this->head; } else { // Set next node as the new head. nodePtr prevHead = this->head; this->head = this->head->next; delete prevHead; } } --this->size; return dataToReturn; } template <class T> int LinkedListQueue<T>::Size() const { return this->size; } template <class T> bool LinkedListQueue<T>::IsEmpty() const { return this->head == nullptr; } template <class T> void LinkedListQueue<T>::Print() const { nodePtr current_node = this->head; int counter = 0; std::cout << "LinkedListQueue ["; // While current_node does not point to null while (counter < this->size) { std::cout << current_node->data << " --> "; current_node = current_node->next; ++counter; } std::cout << " ]" << std::endl; } // Template declaration template LinkedListQueue<int>::LinkedListQueue(); template LinkedListQueue<int>::~LinkedListQueue(); template void LinkedListQueue<int>::Enqueue(const int data); template void LinkedListQueue<int>::Print() const; template int LinkedListQueue<int>::Dequeue(); template int LinkedListQueue<int>::Size() const; template bool LinkedListQueue<int>::IsEmpty() const;

Jay