§ Doubly Linked List
Time Complexity
Access
Since there’s no way to directly access the nth node, we need to traverse the list from the head. This causes the access operation to be linear, which is O(n).
The exception would be accessing the first and the last node. Since first node will always be head
, it will be constant, O(1). Same with the last node which is tail
.
Search
We need to compare each node one at a time to find the target value. This is linear, O(n).
Insert
Inserting at the head is constant because we have a head
node.
Inserting a node in between node is linear because we need to locate the position by traversing the list.
About inserting node at the end, unlike Singly Linked List which took linear time, doubly linked list can perform the action at constant time, O(1) if we have tail
. This is because we can access the previous node using tail.prev
.
Delete
The time complexity for the deletion is similar to the insertion.
Deleting node at the head and tail both can be done at constant time because of the prev
point. But for nodes in between, we still need to locate the position which causes linear time in average.
Implementation
class Node
attr_accessor :prev, :next
attr_reader :data
def initialize(data, _prev=nil, _next=nil)
@data = data
@prev = _prev
@next = _next
end
end
class DoublyLinkedList
attr_accessor :head, :tail
def initialize
@head = @tail = nil
@size = 0
end
def push_front(data)
new_node = Node.new(data)
if @head == nil
@head = @tail = new_node
else
new_node.next = @head
@head.prev = new_node
@head = new_node
end
@size += 1
end
def push_back(data)
new_node = Node.new(data)
if @tail == nil
@head = @tail = new_node
else
@tail.next = new_node
new_node.prev = @tail
@tail = new_node
end
@size += 1
end
def insert_at(data, pos)
raise "err: invalid position." if pos < 0 || pos >= @size
curr = @head
pos.times { curr = curr.next }
new_node = Node.new(data)
new_node.next = curr
new_node.prev = curr.prev
curr.prev.next = new_node
curr.prev = new_node
@size += 1
end
def pop_front
raise "err: list is empty" if @head == nil
@head = @head.next
@head.prev = nil
@size -= 1
end
def pop_back
raise "err: list is empty" if @tail == nil
@tail = @tail.prev
@tail.next = nil
@size -= 1
end
def delete_at(pos)
raise "err: invalid position" if pos < 0 || pos >= @size
curr = @head
if pos == 0
pop_front
elsif pos == @size - 1
pop_back
else
pos.times { curr = curr.next }
curr.prev.next = curr.next
curr.next.prev = curr.prev
@size -= 1
end
end
def first
@head
end
def last
@tail
end
def size
@size
end
def each
curr = @head
while curr
yield curr
curr = curr.next
end
end
def to_s
each { |node| print "#{node.data} "}
puts " (#{@size})"
end
end