Ruby Binary Heap insertion and finding dilemma

408 Views Asked by At

I have an interesting problem when building my Heap (challenge without using an array), and wondering if anyone can help. Where I am at so far, I can insert a number of built nodes and my Heap structure builds correctly via my output. However, when I go to #find a particular node, I receive nil because it appears my nodes aren't matching with the outputted tree. Keeping it as condensed as I can, here is what I have:

Node constructor & HeapTree #insert method

class Node
  attr_accessor :title
  attr_accessor :rating
  attr_accessor :parent
  attr_accessor :left
  attr_accessor :right

  def initialize(title, rating)
    @title = title
    @rating = rating
    @parent = nil
    @left = nil
    @right = nil
  end
end

class HeapSearchTree

  def initialize
    @root = nil
    @heapsize = 1
  end

  def insert(node)
    return nil if node.nil?

    if @root.nil?
      @root = node
    else
      current = @root
      @heapsize += 1 #every insert increases heapsize; used for balancing heap
      until current.left.nil? || current.right.nil?
        if @heapsize % 2 == 0
          current = current.left
        else
          current = current.right
        end
      end

      #after figuring out to go left or right, find the first nil spot
      if current.left.nil? && current.right.nil?
        current.left = node
        node.parent = current
      elsif current.left.nil? && !current.right.nil?
        current.left = node
        node.parent = current
      elsif !current.left.nil? && current.right.nil?
        current.right = node
        node.parent = current
      end

      #heapify by swapping titles and ratings because if I swap parent node for higher node it doesnt stick.
      while node.rating >= node.parent.rating
        if node.parent.rating <= node.parent.left.rating
          temp_title = node.parent.title
          temp_rating = node.parent.rating

          node.parent.title = node.parent.left.title
          node.parent.rating = node.parent.left.rating
          node.parent.left.title = temp_title
          node.parent.left.rating = temp_rating
        elsif node.parent.rating <= node.parent.right.rating
          temp_title = node.parent.title
          temp_rating = node.parent.rating

          node.parent.title = node.parent.right.title
          node.parent.rating = node.parent.right.rating
          node.parent.right.title = temp_title
          node.parent.right.rating = temp_rating
        end
      end
    end
  end
  def find(root=@root, movie_title)
    if root.title == movie_title
      puts "END OF RECURSION"
      puts "movie_title entered: #{movie_title}"
      puts "root.title: #{root.title}"
      return root
    else
      loop = 0
      left = find(root.left, title) if root.left
      right = find(root.right, title) if root.right
      left || right
      loop += 1
      puts loop
    end
  end

Picture of Problem

You will notice by inserting martian, the tree rearranges properly. However, when I tree.find(matrix.title) it passes in as martian.title and I get nil as a return.

enter image description here

I've been puzzled with this for awhile and cannot find anything via the web to help me. I am swapping titles and ratings with node.parent if node.parent's rating is less then passed in node. The node ID is not changing, just the information. Looking for a solution to make this work. Most will show a built array, but I do not want to use an array for learning purposes. Thanks

note

I've found my program breaks from the bubbling up from while loop in #insert. I'm now trying to move node and node.parent, but proving to be just as difficult.

2

There are 2 best solutions below

0
On BEST ANSWER

solved my issue by recreating #insert

def insert(root, node)
    root = @root
    current = @root
    @heapsize += 1 #every insert increases heapsize; used for balancing heap

    until current.left.nil? || current.right.nil?
      if @heapsize % 2 == 0
        current = current.left
      else
        current = current.right
      end
    end

    if current.left.nil? && current.right.nil?
      current.left = node
      node.parent = current
    elsif current.left.nil? && !current.right.nil?
      current.left = node
      node.parent = current
    elsif !current.left.nil? && current.right.nil?
      current.right = node
      node.parent = current
    end

  #if rating > (greater than) its parents rating 
    while node.rating >= node.parent.rating
      loop = 1
      temp_parent = node.parent
      temp_parent_right = node.parent.right
      temp_parent_left = node.parent.left
      temp_node_left = node.left
      temp_node_right = node.right
  # if node is greater then its parent and node is to the left of parent
      if node.parent.parent.nil? && node == node.parent.left
        puts "im here on left and parents parent is nil"
        node.right = node.parent.right
        node.parent = node.parent.parent
        node.left = temp_parent

        node.left.parent = node
        node.left.left = temp_node_left
        node.left.right = temp_node_right

        if !node.right.nil?
          node.right.parent = node
        end

        @root = node
        break
  # if node is greater then its parent and node is to the right of parent
      elsif node.parent.parent.nil? && node == node.parent.right
        puts "im here on right and parents parent is nil"

        node.left = node.parent.left
        node.parent = node.parent.parent 
        node.right = temp_parent 

        node.right.parent = node
        node.right.right = temp_node_right
        node.right.left = temp_node_left
        node.left.parent = node

        @root = node
        break


      elsif !node.parent.nil? && node == node.parent.left
        puts "im here on left and my parents parent is not nil"

        if node.parent.parent.left == node.parent
          node.parent.parent.left = node
          node.parent.parent.left.parent = node.parent.parent
          node.left = temp_parent
          node.right = temp_parent_right
          node.left.parent = node
          unless node.right.nil?
            node.right.parent = node
          end
          node.left.left = temp_node_left
          node.left.right = temp_node_right
        elsif node.parent.parent.right == node.parent
          node.parent.parent.right = node
          node.parent.parent.right.parent = node.parent.parent
          node.left = temp_parent
          node.right = temp_parent_right
          node.left.parent = node
          unless node.right.nil?
            node.right.parent = node
          end
          node.left.left = temp_node_left
          node.left.right = temp_node_right
        end

      elsif !node.parent.nil? && node == node.parent.right

        if node.parent.parent.right == node.parent
          node.parent.parent.right = node
          node.parent.parent.right.parent = node.parent.parent
          node.right = temp_parent
          node.left = temp_parent_right
          node.right.parent = node
          unless node.left.nil?
            node.left.parent = node
          end
          node.left.left = temp_node_left
          node.left.right = temp_node_right
        elsif node.parent.parent.left == node.parent
          node.parent.parent.left = node
          node.parent.parent.left.parent = node.parent.parent
          node.left = temp_parent
          node.left = temp_parent_right
          node.right.parent = node
          unless node.right.nil?
            node.right.parent = node
          end
          node.left.left = temp_node_left
          node.left.right = temp_node_right
        end

      end
    end
  end
0
On

This is correct as HeapSearchTree#insert mutates nodes rather than swapping them correctly. Your insert code mutates the title and ratings of a node. The variable matrix is bound to a Node with the title "The Matrix" but this is changed to "The Martian" by the insert method.

[2] pry(main)> tree = HeapSearchTree.new
[3] pry(main)> matrix = Node.new("The Matrix", 87)
[4] pry(main)> martian = Node.new("The Martian", 92)

[5] pry(main)> tree.insert(matrix)
=> #<Node:0x007f92348c7c10
 @left=nil,
 @parent=nil,
 @rating=87,
 @right=nil,
 @title="The Matrix">
[6] pry(main)> matrix.object_id
=> 70132961787400
[7] pry(main)> matrix.title
=> "The Matrix"

[8] pry(main)> tree.insert(martian)
=> nil

[9] pry(main)> matrix.object_id
=> 70132961787400
[10] pry(main)> matrix.title
=> "The Martian"

[11] pry(main)> tree.find(matrix.title)
END OF RECURSION
movie_title entered: The Martian
root.title: The Martian

What this shows is the node bound to the variable matrix is displaying the right title, and hence searching for that title returns the correct result. I would, as you mention, reimplement the insert to correctly swap nodes.