← Back to the index page

Circular doubly linked list

Circular doubly linked list is a data structure that is a combination of both circular singly linked list and doubly linked list. It stores 2 pointers, one is pointing to the next node, the other to the previous node and the first pointer points to the last and correspondingly last to the first. This approach makes a continual circular loop which can be traversed in both directions.

Figure 01: Circular doubly linked list example

Doubly linked list class

Implementation of the insertion and deletion algorithms are very similar to a linear doubly linked list, to check the description proceed to corresponding article.

Class implementation with OOP principles and annotations.

CircularDoublyinkedList.lua

---@class Node
---@field value any
---@field prev Node | nil
---@field next Node | nil
local Node = {}
Node.__index = Node

---@param value any
---@param prev? Node | nil
---@param next? Node | nil
---@return Node
function Node:new(value, prev, next)
    return setmetatable({
        value = value,
        prev = prev,
        next = next,
    }, self)
end

---@class CircularDoublyLinkedList
---@field private _size number
---@field private _tail Node
local CircularDoublyLinkedList = {}
CircularDoublyLinkedList.__index = CircularDoublyLinkedList

---@return CircularDoublyLinkedList
function CircularDoublyLinkedList:new()
    local t = {
        _size = 0,
        _tail = nil,
    }
    return setmetatable(t, self)
end

---@private
---@param value any
---@return Node
function CircularDoublyLinkedList:_addToEmpty(value)
    local node = Node:new(value)
    self._tail = node
    node.next = self._tail
    node.prev = self._tail
    return self._tail
end

---@return Node | nil
function CircularDoublyLinkedList:tail()
    return self._tail
end

---@return Node | nil
function CircularDoublyLinkedList:head()
    return self._tail and self._tail.next
end

---@return number
function CircularDoublyLinkedList:size()
    return self._size
end

---@return boolean
function CircularDoublyLinkedList:isEmpty()
    return self._size == 0
end

---Inserts new node at the end of the list.
---@param value any
---@return Node
function CircularDoublyLinkedList:append(value)
    local node = Node:new(value)
    if self:isEmpty() then
        node = self:_addToEmpty(value)
        self._size = self._size + 1
    else
        node = self:insertAfter(value, self._tail)
        self._tail = node
    end
    return node
end

---Inserts new node at the front of the list.
---@param value any
---@return Node
function CircularDoublyLinkedList:prepend(value)
    local node = Node:new(value)
    if self:isEmpty() then
        node = self:_addToEmpty(value)
        self._size = self._size + 1
    else
        node = self:insertAfter(value, self._tail)
    end
    return node
end

---Inserts a new node with value after given node. If after node is nil,
---then one node inserted and pointing to itself.
---@param value any
---@param after Node | nil
---@return Node
function CircularDoublyLinkedList:insertAfter(value, after)
    local node = Node:new(value)
    if after == nil then
        node = self:_addToEmpty(value)
    else
        node.next = after.next
        node.prev = after
        after.next.prev = node
        after.next = node
    end
    self._size = self._size + 1
    return node
end

---Inserts a new node with value before given node. If before node is nil,
---then one node inserted and pointing to itself.
---@param value any
---@param before Node | nil
---@return Node
function CircularDoublyLinkedList:insertBefore(value, before)
    local node = Node:new(value)
    if before == nil then
        node = self:_addToEmpty(value)
        self._size = self._size + 1
    else
        node = self:insertAfter(value, before.prev)
    end
    return node
end

---Removes and returns a node after given node. If given node not found nil is
---returned.
---@return any
function CircularDoublyLinkedList:removeNode(node)
    if node and node.next == node then
        self._tail = nil
    else
        node.next.prev = node.prev
        node.prev.next = node.next
        if node == self._tail then
            self._tail = node.prev
        end
        self._size = self._size - 1
    end
    return node
end

---Removes and returns a node after found node with gven value. If given
---node not found nil is
---returned.
---@return any
function CircularDoublyLinkedList:removeByValue(value)
    local node = self:findByValue(value)
    if node then
        self:removeNode(node)
    end
    return node
end

---Checks if the list contins a give value.
---@param value any
---@return boolean
function CircularDoublyLinkedList:contains(value)
    return self:findByValue(value) ~= nil
end

---Finds the first occurrence of the value. Returns nil if not found.
---@param value any
---@return Node | nil
function CircularDoublyLinkedList:findByValue(value)
    local node = self._tail
    if not node then
        return nil
    end
    repeat
        if node.value == value then
            return node
        end
        node = node.next
    until node == self._tail
    return nil
end

---Traversal of the list in forwards direction.
---@param fn fun(node: Node)
function CircularDoublyLinkedList:traverseForwards(fn)
    local node = self._tail and self._tail.next
    if not node then
        return
    end
    repeat
        fn(node)
        node = node.next
    until node == self._tail.next
end

---Traversal of the list in backwards direction.
---@param fn fun(node: Node)
function CircularDoublyLinkedList:traverseBackwards(fn)
    local node = self._tail
    if not node then
        return
    end
    repeat
        fn(node)
        node = node.prev
    until node == self._tail
end

---@param sep? string
---@return string
function CircularDoublyLinkedList:toString(sep)
    sep = sep or " <=> "
    local t = {}
    self:traverseForwards(function(node)
        t[#t + 1] = tostring(node.value)
    end)
    return table.concat(t, sep)
end

return CircularDoublyLinkedList

Usage of CircularDoublyLinkedList class

local cdll = CircularDoublyLinkedList:new()
cdll:insertAfter("A")
local foundA = cdll:findByValue("A")
cdll:prepend("B")
cdll:append("C")
cdll:insertBefore("D", foundA)
cdll:insertAfter("E", foundA)
cdll:removeNode(foundA)
print(cdll:toString(), cdll:size()) --> "B <=> D <=> E <=> C"   4

References

Feedback

For feedback, please check the contacts section. Before writing, please specify where you came from and who you are. Sometimes spammers go insane. Thank you in advance for your understanding.

← Back to the index page