Depth First Search Efficiency - ruby

I have implemented a DFS method which takes in a search value and a Binary Search Tree as arguments. The method then searches the tree for the given value, returning it when found.
When I call the method, there appears to be a duplicate node visit which may be affecting the efficiency. Is this visit in fact a duplication or just a reflection of the nature of DFS?
For instance, if my binary tree looks like the one below and I'm looking for the the value 3, the search is popping the 5 node off the stack, then the 2, then the 1, then retrieving the 2 node from the stack again before finding the 3. Is this stack retrieval of the 2 duplicative? Is it a proper DFS?
5
/ \
/ \
2 7
/ \ / \
1 3 6 8
\ \
4 9
Binary Tree
class Node
attr_accessor :value, :left, :right
def initialize(value)
#value = value
end
end
def build_tree(array, *indices)
array.sort.uniq!
mid = (array.length-1)/2
first_element = indices[0]
last_element = indices[1]
if !first_element.nil? && first_element >last_element
return nil
end
root = Node.new(array[mid])
root.left = build_tree(array[0..mid-1], 0, mid-1)
root.right = build_tree(array[mid+1..-1], mid+1, array.length-1)
return root
end
Depth First Search Method
def depth_first_search(search_value, tree)
stack = [tree]
visited = [tree]
while !stack.empty?
current = stack.last
visited << current
puts current
p current
if current.value == search_value
puts current
exit
elsif !current.left.nil? && !visited.include?(current.left)
if current.left.value == search_value
puts current.left
exit
else
visited << current.left
stack << current.left
end
elsif !current.right.nil? && !visited.include?(current.right)
if current.right.value == search_value
puts current.right
exit
else
visited << current.right
stack << current.right
end
else
stack.pop
end
end
puts "nil"
end
Method Call
binary_tree = build_tree([1,2,3,4,5,6,7,8,9])
depth_first_search(3, binary_tree)

Now, since it is DFS, it works that way. DFS in a binary-tree works exactly like pre-order traversal of the tree. So, for the example tree in the figure, DFS would be visiting like:
5-2-1-(2)-3-4-(3)-(2)-(5)-7-6-(7)-8-9
Here, the values in brackets is the "second visit" that you are calling, but, it does not actually visit those nodes. So, it is alright.
Also, I'd recommend using a binary search if the input tree is BST (not DFS).

Related

Binary Search Tree Insertion method in Ruby

I'm working on data structures and I got stuck on one of the challenges. The objective is to insert array elements into a binary search tree based on their value i.e ( the root_node of the main tree is array[0], the left subtree's root_node is less than the parent node, and the right subtree's root_node is greater than the parent node). This is to be done recursively until all array elements are inserted into the BST.
I have implemented two classes:
That represents the node with attributes ( data, left, right):
class Node
attr_reader :data
attr_accessor :left, :right
def initialize(data)
#data = data
end
end
The BST class to represent the binary search tree with the root value set to nil:
class BST
attr_accessor :root
def initialize
#root = nil
end
def insert(node)
insert_node(#root, node)
end
def pre_order(node = #root)
return '' if node.nil?
print "#{node.data} "
pre_order(node.left)
pre_order(node.right)
end
private
def insert_node(node, element)
if node.nil?
node = element
elsif node.data > element.data
node.left = insert_node(node.left, element)
else
node.right = insert_node(node.right, element)
end
node
end
end
The insert_node is a private method for BST which does the actual work to insert a node to the tree. I separated it from insert because of the requirements for the expected solution which gets evaluated using RSpec.
I then did a pre_order traversal to print each Node to the terminal window.
I have a binary_search_tree method which accepts an array as input and calls the insert method on each array element. It's main functionality is to convert an array to a binary tree and print out all the tree nodes in pre_order format.
def binary_search_tree(array)
tree = BST.new
array.each { |e| tree.insert(Node.new(e)) }
tree.pre_order
end
If I run the binary_search_tree method with [8, 3, 10, 1, 6, 14, 4, 7, 13] as an argument, I expect to get the nodes printed out in the format # => "8 3 1 6 4 7 10 14 13" but nothing is happening and I don't know where I might have gone wrong. I have been stuck on this challenge for hours today, if anyone could assist that will mean a lot. Thanks : )
sample input:
puts binary_search_tree([8, 3, 10, 1, 6, 14, 4, 7, 13])
expected output:
8 3 1 6 4 7 10 14 13
got:
Oh, I finally found a way around the challenge. I'm just gonna demonstrate how I did it for future reference:
So, instead of using two separate methods insert and insert_helper node, I decided to get rid of the redundant code and came up with one method to solve the challenge. Here's the BST class structure:
class BST
attr_accessor :root
def initialize
#root = nil
end
def insert(node, head = #root)
return #root = node if #root.nil?
return node if head.nil?
if node.data < head.data
head.left = insert(node, head.left)
elsif node.data > head.data
head.right = insert(node, head.right)
end
head
end
def pre_order(node = #root)
return '' if node.nil?
result = ''
result += "#{node.data} "
result += pre_order(node.left)
result += pre_order(node.right)
end
end
The insert method now accepts two parameters node and ( head which is an optional parameter ), to allow us to perform recursive operations on the subtree nodes and yield the desired result.
pre_order prints each node's data, this is done in the recursive approach so each node in the Binary Search Tree gets printed out in pre_order format.
Now if you call the BST pre_order method for example:
def binary_search_tree(array)
tree = BST.new
array.each { |e| tree.insert(Node.new(e)) }
tree.pre_order
end
puts binary_search_tree([8, 3, 10, 1, 6, 14, 4, 7, 13])
you get the result 8 3 1 6 4 7 10 14 13. By altering the pre_order method, you can print the tree nodes in post_order, inorder, e.t.c.
I hope this will be useful for the others. Happy coding!!
return '' if node.nil?
This is the stop condition of the recursive pre_order and it's what you return from binary_search_tree. You don't seem to have anything that would format your tree in your desired shape, "8 3 1 6 4 7 10 14 13". So add that logic and call it at the end of binary_search_tree method.

Level Order Traversal Binary Tree Issue

Problem statement:
Given a binary tree, return the level order traversal of its nodes'
values. (ie, from left to right, level by level).
For example:
Given binary tree [3,9,20,null,null,15,7],
3
/ \
9 20
/ \
15 7
return its level order traversal as:
[
[3],
[9,20],
[15,7]
]
I've solved this with BFS, which is the most intuitive way to do this. However, I tried solving it another way and I'm unable to. Below is sample input / correct output vs. my output:
Your input
[3,9,20,null,null,15,7]
Your answer
[[3],[[9],[],[20],[[15],[],[7],[]]]]
Expected answer
[[3],[9,20],[15,7]]
This is obviously because somewhere in the code [] is being returned, but here's my code:
def level_order(root)
return [] if root.nil?
arr = merge([level_order(root.left)], [level_order(root.right)]) #this returns an empty arr if both of those are nil..
arr.insert(0, [root.val])
end
def merge(arr1, arr2)
i = j = 0
while i < arr1.length && j < arr2.length
arr1[i] += arr2[j]
i += 1
j += 1
end
while j < arr2.length #check if any remaining elements in arr 2
arr1 << arr2[j]
j += 1
end
arr1
end
In the above, I dealt with [] case by doing += instead of << and the merge function works if one arr is empty. The idea here is that I'm merging each level of the level order traversal for both left and right sides, then inserting the root at the beginning of the array.
I also considered that the root could be inserted as an empty array, but this can't be happening because I have an initial return statement that's called if root is nil. Any ideas?
It should be as simple as changing this
arr = merge([level_order(root.left)], [level_order(root.right)])
To
arr = merge(level_order(root.left), level_order(root.right))
However I would have written this slightly differently:
input = [3,9,20,nil,nil,15,7]
output = []
start = 0
length = 1
while start < input.length do
output << input.slice(start, length).compact
start += length
length *= 2
end
puts output.inspect
This would avoid building a tree and would be more efficient than recursion.

Find Leaves of Binary Tree

Working on following problem:
Given a binary tree, collect a tree's nodes as if you were doing this: Collect and remove all leaves, repeat until the tree is empty.
Example:
Given binary tree
1
/ \
2 3
/ \
4 5
Returns [4, 5, 3], [2], [1].
Explanation:
1. Removing the leaves [4, 5, 3] would result in this tree:
1
/
2
2. Now removing the leaf [2] would result in this tree:
1
3. Now removing the leaf [1] would result in the empty tree:
[]
Returns [4, 5, 3], [2], [1].
My idea was a simple recursive algorithm shown below. The idea is to find the leaves of the left subtree and the right subtree, and weave them such that the depths are in the right subarray. I've tested the 'weave' method pretty thoroughly, and I think it's fine. My concern is with my recursive implementation-- I'm getting an answer way off from the correct one, and not sure why.
Below is my code with sample input/output:
def find_leaves(root)
return [] if root.nil?
#create leaf_arr of root.left and root.right
#weave them in order.
#add the root
left_arr = find_leaves(root.left)
right_arr = find_leaves(root.right)
weave(left_arr, right_arr) << [root]
end
def weave(arr1, arr2) #these are 2d arrs
i = 0
until i == arr1.length || i == arr2.length #potential nil/empty case here
arr1[i] += arr2[i]
i += 1
end
if i < arr2.length
#either arr 1 or arr2 isn't finished. if arr1 isn't finished, we're done. if arr2 isnt finished, do the below:
until i == arr2.length
arr1 << arr2[i]
i += 1
end
end
arr1
end
Sample input/output/correct answer:
Run Code Result: ×
input: [1,2,3,4,5]
Your answer: [[[4],[5],[3]],[[2,4,5]],[[1,2,3,4,5]]]
Expected answer: [[4,5,3],[2],[1]]
I've printed the output for the left_arr and right_arr variables and they look fine, and I've stress-tested my weave algorithm. Am I off conceptually here?
I can't comment so I will do it like this. (do remember that i dont know ruby)
I think something goes already wrong in how the double arrays (root.left and root.right) are defined. How are they defined? how is root defined?
But the following eplains the repeat of the whole array.
weave(left_arr, right_arr) << [root]
This should be someting in the line of this.
weave(left_arr, right_arr) << [root.root]
Otherwise you are appending the whole root array wich is [1,2,3,4,5].
So this explains the adding of last part. [[[4],[5],[3]],[[2,4,5]],[[1,2,3,4,5]]].
My suggestion in finding the error in weave would be to print arr1 and arr2 at every stage....
Could you show that..
In your code you are using pure depth first search algorithm DFS and with that algorithm I think that you can hardly achieve your goal with array joggling you are doing in weave function. Because your tree will be processed in this order 4 , 5 , 2 , 3 , 1.
One solution will be to do it with iteration (pseudo code):
function doJob(root) begin
leaves = findLeaves(root)
while leaves.size > 0 do begin
for each leaf in leaves delete(leaf)
leaves = findLeaves(root)
end
delete(root)
end
function findLeaves(node) begin
if node = nil then begin
return []
end
else begin
leftLeaves = findLeaves(node.left)
rightLeaves = fingLeaves(node.right)
leaves = leftLeaves + rightLeaves
if leaves.size == 0 then begin
leaves.add(node)
end
return leaves
end
end
Since this still sits open and seems to fair highly when I google search your title. I'll show a pretty expressive solution:
def find_leaves(root)
return [] if root.nil?
return [[root.val]] if root.left.nil? && root.right.nil?
todo = [root]
leaves = []
until todo.empty?
top = todo.shift
%w[left right].each do |path|
leaf = top.send(path)
next if leaf.nil?
if leaf.left.nil? && leaf.right.nil?
leaves << leaf.val
top.instance_variable_set("##{path}", nil)
else
todo << leaf
end
end
end
[leaves].concat(find_leaves(root))
end
A more refactored version:
def find_leaves(root)
leaves = []
search = lambda do |branch|
return -1 unless branch
i = 1 + [search[branch.left], search[branch.right]].max
(leaves[i] ||= []) << branch.val
i
end
search[root]
leaves
end
They're both about the same speed, and really the first one is easier to read and understand.

Method is slower after memoization

I'm working on the following algorithm problem on Leetcode:
Given a binary tree, determine if it is height-balanced. For this
problem, a height-balanced binary tree is defined as a binary tree in
which the depth of the two subtrees of every node never differ by more
than 1.
Here's the link: https://leetcode.com/problems/balanced-binary-tree/
I've written two solutions--both of which pass Leetcode's tests. The first one should be more expensive than the second one because it repeatedly scans each subtree's nodes to calculate the heights for each node. The second solution should collect all the node heights into a cache so that the calculation doesn't have to repeat. However, it turns out my second solution is actually slower than my first one, and I have no idea why. Any input?
Solution 1:
def is_balanced(root) #runs at 139 ms
return true if root.nil?
left_height = height(root.left)
right_height = height(root.right)
if !equal?(left_height, right_height)
return false
else
is_balanced(root.left) && is_balanced(root.right)
end
end
def height(node)
return 0 if node.nil?
[height(node.left), height(node.right)].max + 1
end
def equal?(num1, num2)
return true if (num1-num2).abs <= 1
false
end
Solution 2: (edited to include cobaltsoda's suggestion)
#memo = {}
def is_balanced(root)
#memo = {}
return true if root.nil?
left_height = height(root.left)
right_height = height(root.right)
if !equal?(left_height, right_height)
return false
else
is_balanced(root.left) && is_balanced(root.right)
end
end
def height(node)
#memo[node] = 0 if node.nil?
#memo[node] ||= [height(node.left), height(node.right)].max + 1
end
def equal?(num1, num2)
return true if (num1-num2).abs <= 1
false
end
Because the second solution uses memoization, shouldn't it cut out the redundancies and lower the time expenditure of the program? Not sure what I'm missing.
Also, with memoization this goes from O(NlogN) algorithm to O(N) correct?

Is it possible to convert this recursive solution (to print brackets) to an iterative version?

I need to print the different variations of printing valid tags "<" and ">" given the number of times the tags should appear and below is the solution in python using recursion.
def genBrackets(c):
def genBracketsHelper(r,l,currentString):
if l > r or r == -1 or l == -1:
return
if r == l and r == 0:
print currentString
genBracketsHelper(r,l-1, currentString + '<')
genBracketsHelper(r-1,l, currentString + '>')
return
genBracketsHelper(c, c, '')
#display options with 4 tags
genBrackets(4)
I am having a hard time really understanding this and want to try to convert this into a iterative version but I haven't had any success.
As per this thread: Can every recursion be converted into iteration? - it looks like it should be possible and the only exception appears to be the Ackermann function.
If anyone has any tips on how to see the "stack" maintained in Eclipse - that would also be appreciated.
PS. This is not a homework question - I am just trying to understand recursion-to-iteration conversion better.
Edit by Matthieu M. an example of output for better visualization:
>>> genBrackets(3)
<<<>>>
<<><>>
<<>><>
<><<>>
<><><>
I tried to keep basically the same structure as your code, but using an explicit stack rather than function calls to genBracketsHelper:
def genBrackets(c=1):
# genBracketsStack is a list of tuples, each of which
# represents the arguments to a call of genBracketsHelper
# Push the initial call onto the stack:
genBracketsStack = [(c, c, '')]
# This loop replaces genBracketsHelper itself
while genBracketsStack != []:
# Get the current arguments (now from the stack)
(r, l, currentString) = genBracketsStack.pop()
# Basically same logic as before
if l > r or r == -1 or l == -1:
continue # Acts like return
if r == l and r == 0:
print currentString
# Recursive calls are now pushes onto the stack
genBracketsStack.append((r-1,l, currentString + '>'))
genBracketsStack.append((r,l-1, currentString + '<'))
# This is kept explicit since you had an explicit return before
continue
genBrackets(4)
Note that the conversion I am using relies on all of the recursive calls being at the end of the function; the code would be more complicated if that wasn't the case.
You asked about doing this without a stack.
This algorithm walks the entire solution space, so it does a bit more work than the original versions, but it's basically the same concept:
each string has a tree of possible suffixes in your grammar
since there are only two tokens, it's a binary tree
the depth of the tree will always be c*2, so...
there must be 2**(c*2) paths through the tree
Since each path is a sequence of binary decisions, the paths correspond to the binary representations of the integers between 0 and 2**(c*2)-1.
So: just loop through those numbers and see if the binary representation corresponds to a balanced string. :)
def isValid(string):
"""
True if and only if the string is balanced.
"""
count = { '<': 0, '>':0 }
for char in string:
count[char] += 1
if count['>'] > count['<']:
return False # premature closure
if count['<'] != count['>']:
return False # unbalanced
else:
return True
def genBrackets(c):
"""
Generate every possible combination and test each one.
"""
for i in range(0, 2**(c*2)):
candidate = bin(i)[2:].zfill(8).replace('0','<').replace('1','>')
if isValid(candidate):
print candidate
In general, a recursion creates a Tree of calls, the root being the original call, and the leaves being the calls that do not recurse.
A degenerate case is when a each call only perform one other call, in this case the tree degenerates into a simple list. The transformation into an iteration is then simply achieved by using a stack, as demonstrated by #Jeremiah.
In the more general case, as here, when each call perform more (strictly) than one call. You obtain a real tree, and there are, therefore, several ways to traverse it.
If you use a queue, instead of a stack, you are performing a breadth-first traversal. #Jeremiah presented a traversal for which I know no name. The typical "recursion" order is normally a depth-first traversal.
The main advantage of the typical recursion is that the length of the stack does not grow as much, so you should aim for depth-first in general... if the complexity does not overwhelm you :)
I suggest beginning by writing a depth first traversal of a tree, once this is done adapting it to your algorithm should be fairly simple.
EDIT: Since I had some time, I wrote the Python Tree Traversal, it's the canonical example:
class Node:
def __init__(self, el, children):
self.element = el
self.children = children
def __repr__(self):
return 'Node(' + str(self.element) + ', ' + str(self.children) + ')'
def depthFirstRec(node):
print node.element
for c in node.children: depthFirstRec(c)
def depthFirstIter(node):
stack = [([node,], 0), ]
while stack != []:
children, index = stack.pop()
if index >= len(children): continue
node = children[index]
print node.element
stack.append((children, index+1))
stack.append((node.children, 0))
Note that the stack management is slightly complicated by the need to remember the index of the child we were currently visiting.
And the adaptation of the algorithm following the depth-first order:
def generateBrackets(c):
# stack is a list of pairs children/index
stack = [([(c,c,''),], 0), ]
while stack != []:
children, index = stack.pop()
if index >= len(children): continue # no more child to visit at this level
stack.append((children, index+1)) # register next child at this level
l, r, current = children[index]
if r == 0 and l == 0: print current
# create the list of children of this node
# (bypass if we are already unbalanced)
if l > r: continue
newChildren = []
if l != 0: newChildren.append((l-1, r, current + '<'))
if r != 0: newChildren.append((l, r-1, current + '>'))
stack.append((newChildren, 0))
I just realized that storing the index is a bit "too" complicated, since I never visit back. The simple solution thus consists in removing the list elements I don't need any longer, treating the list as a queue (in fact, a stack could be sufficient)!
This applies with minimum transformation.
def generateBrackets2(c):
# stack is a list of queues of children
stack = [[(c,c,''),], ]
while stack != []:
children = stack.pop()
if children == []: continue # no more child to visit at this level
stack.append(children[1:]) # register next child at this level
l, r, current = children[0]
if r == 0 and l == 0: print current
# create the list of children of this node
# (bypass if we are already unbalanced)
if l > r: continue
newChildren = []
if l != 0: newChildren.append((l-1, r, current + '<'))
if r != 0: newChildren.append((l, r-1, current + '>'))
stack.append(newChildren)
Yes.
def genBrackets(c):
stack = [(c, c, '')]
while stack:
right, left, currentString = stack.pop()
if left > right or right == -1 or left == -1:
pass
elif right == left and right == 0:
print currentString
else:
stack.append((right, left-1, currentString + '<'))
stack.append((right-1, left, currentString + '>'))
The output order is different, but the results should be the same.

Resources