diff --git a/lib/tree.rb b/lib/tree.rb index c0d4b51..14a2fab 100644 --- a/lib/tree.rb +++ b/lib/tree.rb @@ -1,13 +1,13 @@ class TreeNode attr_reader :key, :value attr_accessor :left, :right - - def initialize(key, val) + + def initialize(key, val) @key = key @value = val @left = nil @right = nil - end + end end class Tree @@ -15,52 +15,221 @@ class Tree def initialize @root = nil end - - # Time Complexity: - # Space Complexity: + + # Time Complexity: O(logn) in best case + # Space Complexity: O(1) def add(key, value) - raise NotImplementedError + current_and_parent_pair = find_current_and_parent_nodes(key) + if current_and_parent_pair[:current] + # update new value if key exists + current_and_parent_pair[:current].value = value + else + new_node = TreeNode.new(key,value) + parent = current_and_parent_pair[:parent] + link_node_to_parent(parent, new_node) + end end - - # Time Complexity: - # Space Complexity: + + # IN CLASS PRACTICE: add() recursive + # def add_helper(current_node, key, value) + # return TreeNode.new(key, value) if !current_node + # if current_node.key > key + # current_node.left = add_helper(current_node.left, key, value) + # else + # current_node.right = add_helper(current_node.right, key, value) + # end + # return current_node + # end + + # def add(key, value) + # @root = add_helper(@root, key, value) + # end + + # Time Complexity: O(logn) in best case + # Space Complexity: O(1) def find(key) - raise NotImplementedError + current_and_parent_pair = find_current_and_parent_nodes(key) + if current_and_parent_pair[:current] + return current_and_parent_pair[:current].value + else + return nil + end end - - # Time Complexity: - # Space Complexity: + + # Time Complexity: O(n) with n is the number of nodes in the tree + # Space Complexity: O(n) def inorder - raise NotImplementedError + list = [] + stack = [] + + current = @root + while current + stack << current + current = current.left + end + + while !stack.empty? + top = stack.pop + list << {key: top.key, value: top.value} + + if top.right + stack << top.right + + current = top.right.left + while current + stack << current + current = current.left + end + end + end + + return list end - - # Time Complexity: - # Space Complexity: + + # IN CLASS PRACTICE: inorder() recursive + # def inorder_helper(current_node, list = []) + # return list if !current_node + # inorder_helper(current_node.left, list) + # list << {key: current_node.key, value: current_node.value} + # inorder_helper(current_node.right, list) + # return list + # end + + # def inorder + # return inorder_helper(@root) + # end + + # Time Complexity: O(n) with n is the number of nodes in the tree + # Space Complexity: O(n) + def preorder_helper(current_node, list = []) + return list if !current_node + list << {key: current_node.key, value: current_node.value} + preorder_helper(current_node.left, list) + preorder_helper(current_node.right, list) + return list + end + def preorder - raise NotImplementedError + return preorder_helper(@root) end - - # Time Complexity: - # Space Complexity: + + # Time Complexity: O(n) with n is the number of nodes in the tree + # Space Complexity: O(n) + def postorder_helper(current_node, list = []) + return list if !current_node + postorder_helper(current_node.left, list) + postorder_helper(current_node.right, list) + list << {key: current_node.key, value: current_node.value} + return list + end + def postorder - raise NotImplementedError + return postorder_helper(@root) end - - # Time Complexity: - # Space Complexity: + + # Time Complexity: O(n) with n is the number of nodes in the tree + # Space Complexity: O(logn) which is also the height of the tree. + def height_helper(current_node, level = 0) + return level if !current_node + level = [height_helper(current_node.left, level + 1), height_helper(current_node.right, level + 1)].max + return level + end + def height - raise NotImplementedError + return height_helper(@root) end - + # Optional Method - # Time Complexity: - # Space Complexity: + # Time Complexity: O(n) + # Space Complexity: O(n) def bfs - raise NotImplementedError + list = [] + if @root + queue = [] + queue << @root + queue.each do |current| + list << {key: current.key, value: current.value} + queue << current.left if current.left + queue << current.right if current.right + end + end + return list end + + def delete(key) + current_and_parent_pair = find_current_and_parent_nodes(key) + current = current_and_parent_pair[:current] + parent = current_and_parent_pair[:parent] + if current + remove_child(parent, current) + new_subtree = nil + # rearrange new subtree from children if current is not a leaf node + if current.left || current.right + left = current.left + right = current.right + right_subtree_leftmost = find_leftmost_node(right) + right_subtree_leftmost.left = left if right_subtree_leftmost + + new_subtree = right_subtree_leftmost ? right : left + end + link_node_to_parent(parent, new_subtree) + end + end + # Useful for printing def to_s return "#{self.inorder}" end + + private + def find_current_and_parent_nodes(key) + current = @root + parent = nil + + while current + if current.key == key + return {current: current, parent: parent} + elsif current.key > key + parent = current + current = current.left + else + parent = current + current = current.right + end + end + + return {current: current, parent: parent} + end + + def find_leftmost_node(current_node) + leftmost = current_node + while leftmost && leftmost.left + leftmost = leftmost.left + end + return leftmost + end + + def link_node_to_parent(parent, node) + return if !node + if !parent + @root = node + elsif parent.key > node.key + parent.left = node + else + parent.right = node + end + end + + def remove_child(parent, current) + if !parent + @root = nil + elsif current + if parent.key > current.key + parent.left = nil + else + parent.right = nil + end + end + end end diff --git a/test/tree_test.rb b/test/tree_test.rb index 345bf66..9e44a51 100644 --- a/test/tree_test.rb +++ b/test/tree_test.rb @@ -118,5 +118,65 @@ expect(answer).must_be_nil expect(tree_with_nodes.find(47)).must_be_nil end + + it "can delete a leaf node" do + expect(tree_with_nodes.find(1)).must_equal "Mary" + expect(tree_with_nodes.find(25)).must_equal "Kari" + + tree_with_nodes.delete(1) + expect(tree_with_nodes.find(1)).must_be_nil + + tree_with_nodes.delete(25) + expect(tree_with_nodes.find(25)).must_be_nil + end + + it "can delete the only node of a tree" do + tree_with_one_nodes = Tree.new() + tree_with_one_nodes.add(5, "Peter") + # Arrange & Assert + expect(tree_with_one_nodes.find(5)).must_equal "Peter" + + # Act + tree_with_one_nodes.delete(5) + + # Assert + expect(tree_with_one_nodes.find(5)).must_be_nil + end + + it "can delete the node with only left subtree" do + # Arrange & Assert + expect(tree_with_nodes.find(3)).must_equal "Paul" + expect(tree_with_nodes.bfs).must_equal [{:key=>5, :value=>"Peter"}, {:key=>3, :value=>"Paul"}, + {:key=>10, :value=>"Karla"}, {:key=>1, :value=>"Mary"}, + {:key=>15, :value=>"Ada"}, {:key=>25, :value=>"Kari"}] + + # Act + tree_with_nodes.delete(3) + + # Assert + expect(tree_with_nodes.find(3)).must_be_nil + expect(tree_with_nodes.bfs).must_equal [{:key=>5, :value=>"Peter"}, {:key=>1, :value=>"Mary"}, + {:key=>10, :value=>"Karla"},{:key=>15, :value=>"Ada"}, + {:key=>25, :value=>"Kari"}] + end + + it "can delete the node with only right subtree" do + # Arrange & Assert + expect(tree_with_nodes.find(10)).must_equal "Karla" + expect(tree_with_nodes.bfs).must_equal [{:key=>5, :value=>"Peter"}, {:key=>3, :value=>"Paul"}, + {:key=>10, :value=>"Karla"}, {:key=>1, :value=>"Mary"}, + {:key=>15, :value=>"Ada"}, {:key=>25, :value=>"Kari"}] + + # Act + tree_with_nodes.delete(10) + + # Assert + expect(tree_with_nodes.find(10)).must_be_nil + expect(tree_with_nodes.find(3)).must_equal "Paul" + + expect(tree_with_nodes.bfs).must_equal [{:key=>5, :value=>"Peter"}, {:key=>3, :value=>"Paul"}, + {:key=>15, :value=>"Ada"}, {:key=>1, :value=>"Mary"}, + {:key=>25, :value=>"Kari"}] + end end end