From 10876145b23007b9376a580650a1ff40c563b687 Mon Sep 17 00:00:00 2001 From: Eve Le Date: Tue, 25 Feb 2020 23:52:24 -0800 Subject: [PATCH 1/8] Implemented BST methods --- lib/tree.rb | 199 ++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 176 insertions(+), 23 deletions(-) diff --git a/lib/tree.rb b/lib/tree.rb index c0d4b51..cb11009 100644 --- a/lib/tree.rb +++ b/lib/tree.rb @@ -16,51 +16,204 @@ 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) + # 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 + + # 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) + # 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 - # Time Complexity: - # Space Complexity: def preorder - raise NotImplementedError + return preorder_helper(@root) + end + + # Time Complexity: O(n) + # 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 - # Time Complexity: - # Space Complexity: def postorder - raise NotImplementedError + return postorder_helper(@root) end - - # Time Complexity: - # Space Complexity: + + # Time Complexity: O(n) + # Space Complexity: O(n) + 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 = [] + current = @root + if current + queue = [] + queue << current + 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] + # rearrange new subtree from children if current is not a leaf node + if current && (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 + parent = current_and_parent_pair[:parent] + 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 end From 63ddb5310c72186260b077c29a50f9452af9a8fd Mon Sep 17 00:00:00 2001 From: Eve Le Date: Tue, 25 Feb 2020 23:55:04 -0800 Subject: [PATCH 2/8] Fixed shadowing variable warning --- lib/tree.rb | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/tree.rb b/lib/tree.rb index cb11009..399dec7 100644 --- a/lib/tree.rb +++ b/lib/tree.rb @@ -144,10 +144,9 @@ def height # Space Complexity: O(n) def bfs list = [] - current = @root - if current + if @root queue = [] - queue << current + queue << @root queue.each do |current| list << {key: current.key, value: current.value} queue << current.left if current.left From 5a24df7bd5cc94514352e4d5b741fcd141ba17dd Mon Sep 17 00:00:00 2001 From: Eve Le Date: Wed, 26 Feb 2020 00:19:58 -0800 Subject: [PATCH 3/8] Updated time and space complexities --- lib/tree.rb | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/tree.rb b/lib/tree.rb index 399dec7..135a061 100644 --- a/lib/tree.rb +++ b/lib/tree.rb @@ -56,7 +56,7 @@ def find(key) end end - # Time Complexity: O(n) + # Time Complexity: O(n) with n is the number of nodes in the tree # Space Complexity: O(n) def inorder list = [] @@ -99,7 +99,7 @@ def inorder # return inorder_helper(@root) # end - # Time Complexity: O(n) + # 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 @@ -113,7 +113,7 @@ def preorder return preorder_helper(@root) end - # Time Complexity: O(n) + # 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 @@ -127,8 +127,8 @@ def postorder return postorder_helper(@root) end - # Time Complexity: O(n) - # Space Complexity: O(n) + # 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 From fa15c0f9e04307d5e1371dcd958d34928746517e Mon Sep 17 00:00:00 2001 From: Eve Le Date: Wed, 26 Feb 2020 21:07:00 -0800 Subject: [PATCH 4/8] Added tests for delete() and fixed bug in delete method --- lib/tree.rb | 51 +++++++++++++++++++++++++++++------------------ test/tree_test.rb | 24 ++++++++++++++++++++++ 2 files changed, 56 insertions(+), 19 deletions(-) diff --git a/lib/tree.rb b/lib/tree.rb index 135a061..48f10ae 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,7 +15,7 @@ class Tree def initialize @root = nil end - + # Time Complexity: O(logn) in best case # Space Complexity: O(1) def add(key, value) @@ -29,7 +29,7 @@ def add(key, value) link_node_to_parent(parent, new_node) end end - + # IN CLASS PRACTICE: add() recursive # def add_helper(current_node, key, value) # return TreeNode.new(key, value) if !current_node @@ -44,7 +44,7 @@ def add(key, value) # 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) @@ -108,7 +108,7 @@ def preorder_helper(current_node, list = []) preorder_helper(current_node.right, list) return list end - + def preorder return preorder_helper(@root) end @@ -122,7 +122,7 @@ def postorder_helper(current_node, list = []) list << {key: current_node.key, value: current_node.value} return list end - + def postorder return postorder_helper(@root) end @@ -138,7 +138,7 @@ def height_helper(current_node, level = 0) def height return height_helper(@root) end - + # Optional Method # Time Complexity: O(n) # Space Complexity: O(n) @@ -159,19 +159,32 @@ def bfs 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] # rearrange new subtree from children if current is not a leaf node - if current && (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 - parent = current_and_parent_pair[:parent] - link_node_to_parent(parent, new_subtree) + if current + 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 + link_node_to_parent(parent, new_subtree) + else + if !parent + @root = nil + else + if parent.value > current.value + parent.left = nil + else + parent.right = nil + end + end + + end end end - + # Useful for printing def to_s return "#{self.inorder}" diff --git a/test/tree_test.rb b/test/tree_test.rb index 345bf66..ddfcd12 100644 --- a/test/tree_test.rb +++ b/test/tree_test.rb @@ -118,5 +118,29 @@ 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 end end From d5928660e9ec3a9d0e0e840b041367019d8a2413 Mon Sep 17 00:00:00 2001 From: Eve Le Date: Wed, 26 Feb 2020 21:15:39 -0800 Subject: [PATCH 5/8] Simplified delete() --- lib/tree.rb | 29 ++++++++++++++++------------- 1 file changed, 16 insertions(+), 13 deletions(-) diff --git a/lib/tree.rb b/lib/tree.rb index 48f10ae..36b9319 100644 --- a/lib/tree.rb +++ b/lib/tree.rb @@ -160,9 +160,11 @@ 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] - # rearrange new subtree from children if current is not a leaf node + # rearrange new subtree from children if current is not a leaf node, else remove leaf node if current - if (current.left || current.right) + if !current.left && !current.right + remove_leaf(parent, current) + else left = current.left right = current.right right_subtree_leftmost = find_leftmost_node(right) @@ -170,17 +172,6 @@ def delete(key) new_subtree = right_subtree_leftmost ? right : left link_node_to_parent(parent, new_subtree) - else - if !parent - @root = nil - else - if parent.value > current.value - parent.left = nil - else - parent.right = nil - end - end - end end end @@ -228,4 +219,16 @@ def link_node_to_parent(parent, node) parent.right = node end end + + def remove_leaf(parent, leaf) + if !parent + @root = nil + elsif parent && leaf + if parent.value > leaf.value + parent.left = nil + else + parent.right = nil + end + end + end end From 8f2cf62e9ec0715c78f8bc5180550f6c23d56631 Mon Sep 17 00:00:00 2001 From: Eve Le Date: Wed, 26 Feb 2020 21:27:38 -0800 Subject: [PATCH 6/8] More simplification in delete method --- lib/tree.rb | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/lib/tree.rb b/lib/tree.rb index 36b9319..9f934ff 100644 --- a/lib/tree.rb +++ b/lib/tree.rb @@ -160,19 +160,20 @@ 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] - # rearrange new subtree from children if current is not a leaf node, else remove leaf node if current - if !current.left && !current.right - remove_leaf(parent, current) - else + 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 - link_node_to_parent(parent, new_subtree) end + + link_node_to_parent(parent, new_subtree) end end @@ -219,12 +220,12 @@ def link_node_to_parent(parent, node) parent.right = node end end - - def remove_leaf(parent, leaf) + + def remove_child(parent, current) if !parent @root = nil - elsif parent && leaf - if parent.value > leaf.value + elsif current + if parent.value > current.value parent.left = nil else parent.right = nil From 0d166c66f1223de2bacbdcd22f926c28066e0aaa Mon Sep 17 00:00:00 2001 From: Eve Le Date: Thu, 27 Feb 2020 10:52:22 -0800 Subject: [PATCH 7/8] Added test for delete --- test/tree_test.rb | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) diff --git a/test/tree_test.rb b/test/tree_test.rb index ddfcd12..b743922 100644 --- a/test/tree_test.rb +++ b/test/tree_test.rb @@ -142,5 +142,22 @@ # 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 end end From 135ebdd3d4b5c19d1d2a757e348ddbed6c2bdbc6 Mon Sep 17 00:00:00 2001 From: Eve Le Date: Thu, 27 Feb 2020 10:53:35 -0800 Subject: [PATCH 8/8] Fixed bug in remove_child and added test --- lib/tree.rb | 2 +- test/tree_test.rb | 19 +++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/lib/tree.rb b/lib/tree.rb index 9f934ff..14a2fab 100644 --- a/lib/tree.rb +++ b/lib/tree.rb @@ -225,7 +225,7 @@ def remove_child(parent, current) if !parent @root = nil elsif current - if parent.value > current.value + if parent.key > current.key parent.left = nil else parent.right = nil diff --git a/test/tree_test.rb b/test/tree_test.rb index b743922..9e44a51 100644 --- a/test/tree_test.rb +++ b/test/tree_test.rb @@ -159,5 +159,24 @@ {: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