14 July 2013

Algorithmathon

The tail of a Single Linked List (SLL) can be found by working your way along the chain until the next node reference/pointer is null. What do you do if instead the last node points to another node such as the head? When a SLL tail (final node) points back to a previous node it is considered to be cyclic. If it does have a cycle how do we determine this? One way is to use The Tortoise and the Hare Algorithm.

Note: I have seen this condition referred to as cyclic and looped.

To solve our problem we will start with the SLL Base which can be found here. If you haven’t already read them, rules for the Algorithmathon can be found here.

Lets add new RSpec tests for “is_cyclic”. The new tests will check for SSLs that end in nil, a tail that points to the head and a tail that points to the middle. The last two of which are cyclic.

single_linked_list_spec.rb (excerpt)
it "should return true when 'is_cyclic' is called: last to first" do
  first = SingleLinkedListElement.new("first")
  last = SingleLinkedListElement.new("last")
  last.next_node=first
  @list.add(first)
  add_elements(@list)
  @list.add(last)
  @list.is_cyclic().should be_true
end

it "should return false when 'is_cyclic' is called: nil terminated" do
  add_elements(@list)
  @list.is_cyclic().should be_false
end

it "should return true when 'is_cyclic' is called: last to middle" do
  middle = SingleLinkedListElement.new("middle")
  last = SingleLinkedListElement.new("last")
  last.next_node=middle
  add_elements(@list)
  @list.add(middle)
  add_elements(@list)
  @list.add(last)
  @list.is_cyclic().should be_true
end

We can solve this problem by by using two object references. The tortoise will advance one link at a time and the hare will advance two links at a time. If there is a cycle then eventually the hare and the tortoise references will point to the same location. This is accomplished in the code below.

single_linked_list.rb
require 'single_linked_list_element'

class SingleLinkedList
  attr_reader :head

  def add(element)
    if @head.nil?
      @head = element
    else
      last_element=last()
      last_element.next_node=element
    end
  end

  def last()
    last_by_recursion(@head)
  end        

  def is_cyclic()
    if has_nil?(@head)
      false
    else
      @tortoise=@head
      @hare=@head.next_node.next_node
      is_cyclic_recursion()
    end
  end        

  private
    def last_by_recursion(element)
      unless element.nil?
        unless element.next_node.nil?
          last_by_recursion(element.next_node)
        else
          element
        end
      end
    end

    def is_cyclic_recursion()
      if @tortoise.eql?(@hare)
        true
      else
        if has_nil?(@tortoise) || has_nil?(@hare)
          false
        else
          @tortoise=@tortoise.next_node
          @hare=@hare.next_node.next_node
          is_cyclic_recursion()          
        end
      end

    end

    def has_nil?(element)
      element.nil? || element.next_node.nil? || element.next_node.next_node.nil?
    end


end

This code can be summed up in four simple rules.

  1. Tortoise advances one link at a time.
  2. Hare advances two links at a time.
  3. If you find a nil return false.
  4. If the hare and tortoise are in the same place return true.

There is more that one way to solve this problem and Ostermiller.org provides a good discussion of the solution options.


Less Is More ~ Older posts are available in the archive.