I didn't like having the roof calculation done inside the solve  
method.  So here is my revised algorithm, including Dominik's  
suggestions and a new roof class method for Integer.

#!/usr/bin/env ruby

class Integer
   attr_reader :parent

   @@roof = 1.0/0.0

   def self.roof(*args)
       @@roof = args.max * 2 + 2 || @@roof
   end

   def odd?
     self % 2 != 0
   end

   # optimized to remove consecutive double/halves and remove  
adjacent values greater than a maximum
   def adjacency_list
     list = []
     list << self * 2 unless @parent == self * 2  or self * 2 > @@roof
     list << self / 2 unless self.odd? or @parent == self / 2
     list << self + 2 unless self + 2 > @@roof
     list
   end

   def visit!(parent = nil)
     @parent = parent
   end

   def path_from(start)
     if self == start
       [start]
     else
       if @parent == nil
         raise "no path from #{start} to #{self} exists"
       else
         @parent.path_from(start) << self
       end
     end
   end
end

def solve(start, target)
   return [start] if start == target
   Integer.roof(start, target)
   start.visit!
   queue = [start]
   queue.each do |vertex|
     vertex.adjacency_list.each do |child|
       unless child.parent
         child.visit!(vertex)
         return target.path_from(start) if target == child
         queue.push(child)
       end
     end
   end
end

# Run this code only when the file is the main program
if $0 == __FILE__
   # Parse arguments (authored by James Edward Gray II)
   unless ARGV.size == 2 and ARGV.all? { |n| n =~ /\A[1-9]\d*\Z/ }
     puts "Usage:  #{File.basename($0)} START_NUMBER FINISH_NUMBER"
     puts "  Both number arguments must be positive integers."
     exit
   end
   start, finish = ARGV.map { |n| Integer(n) }

   p solve(start, finish)
end

~ ryan ~