Implement a Binary Search Tree in Ruby

Ask questions Research chat →

https://medium.com/analytics-vidhya/implement-a-binary-search-tree-in-ruby-c3fa9192410b · scraped

ruby programming

Attachments

Scraped Content

— 1056 words · 2026-02-14 17:40:26 UTC ·

Excerpt

My previous post introduced the singly-linked list. The linked list is a widely used data structure due to its performance in inserting and deleting an element. However, if there’s an order in your dataset and you want to search for a particular item very frequently, the linked list isn’t the best choice due to it’s linear searching time. What we’re looking for is a simple data structure whose average running time for most operations is sublinear. The data structure that we are referring to is the binary search tree(BST). ![](https://miro.medium.com/v2/resize:fit:700/1*JR__fqQqt0EkS5_aLsm4rQ.jpeg) ## Properties of BST The binary search tree is one type of tree data structure. It shares the same structures with the tree. But having its own properties: - In a binary search tree, each node only contains a left child and right child; - Each of the nodes in the left subtree has data value that is less than the node’s data value; - Each of the nodes in the right subtree has data value th
My previous post introduced the singly-linked list. The linked list is a widely used data structure due to its performance in inserting and deleting an element. However, if there’s an order in your dataset and you want to search for a particular item very frequently, the linked list isn’t the best choice due to it’s linear searching time. What we’re looking for is a simple data structure whose average running time for most operations is sublinear. The data structure that we are referring to is the binary search tree(BST). ![](https://miro.medium.com/v2/resize:fit:700/1*JR__fqQqt0EkS5_aLsm4rQ.jpeg) ## Properties of BST The binary search tree is one type of tree data structure. It shares the same structures with the tree. But having its own properties: - In a binary search tree, each node only contains a left child and right child; - Each of the nodes in the left subtree has data value that is less than the node’s data value; - Each of the nodes in the right subtree has data value that is greater than (or equal to) the node’s data value a binary search tree ![](https://miro.medium.com/v2/resize:fit:700/1*NR_Hwab2DUj8ruLgFy6WQg.jpeg) You can play with it using this website to see how’s BST is structured! # Code with me to build a BST in Ruby! ## Step 1: Create a class for the node and the BST BST is composed of nodes. Each node has a value, and two pointers point to the left and the right child. We can implement a node class first: ```plain text class TreeNode attr_accessor :value, :left, :right #node is initialized with a value, and two children are nil def initialize(value) @value = value @left = nil @right = nil end end ``` That’s it for node class! Super simple, right? Now let's write a BST class: ```plain text class BST attr_accessor :root, :size def initialize() @root = nil @size = 0; end end ``` I set two variables for the BST here: root and size because of: 1). BST must have a root. It’s the starting point of the tree. 2): For the simplicity of calculations, we can easily add or minis one to the class variable @size when we insert or delete a node. ## Step 2: Instance method: insert(value) Recall the properties of BST: the value of the left child is less than the value itself, the value of the right child is greater than the value itself. That’s the pattern of inserting a new value. When we see a new value, we wanna compare it with root’s value, if the new value is greater than the root’s value, we go right. Otherwise, we go left. ```plain text def insert(value) if @root == nil @root = TreeNode.new(value) else curr_node = @root previous_node = @root #while loop helps finding the position of insertion while curr_node != nil previous_node = curr_node if value < curr_node.value curr_node = curr_node.left else curr_node = curr_node.right end end if value < previous_node.value previous_node.left = TreeNode.new(value) else previous_node.right = TreeNode.new(value) end end @size += 1 end ``` ## Step 3: Instance method: find_min() and find_max() Obviously, the minimum value node is the most left child. And the maximum value node is the most right child. ```ruby def find_max(node = self.root) if node == nil return nil elsif node.right == nil return node end return find_max(node.right) end def find_min(node = self.root) if node == nil return nil elsif node.left == nil return node end return find_min(node.left) end ``` ## Step 4: Instance method: contains(value)? This is a boolean function, and it’ll return true when the BST has the value we passed in. ```ruby def contains?(value, node = self.root) if node == nil return false elsif value < node.value return contains?(value, node.left) elsif value > node.value return contains?(value, node.right) else return true end end ``` The algorithm behind the code is that we recursively find the left subtree and right subtree, to see whether the value of the node matches the value we want to find. ## Step 5: Instance method: print() For BST, there are four common traversals: - In-order: Recursively process the left nodes first, then this node, then processes the right nodes recursively. This will result in “normal order” - Pre-order: Process this node first, then process the left node recursively, then the right nodes recursively - Post-order: Recursively process the left nodes, then right nodes, then this node - Level-order: Process the nodes based on depth: the nodes closest to the root first, then successively lower nodes. Here, I’ll implement my print() using in-order traversal: ```plain text def print_in_order(node = self.root) if node != nil print "(" print_in_order(node.left) print ", #{node.value}, " print_in_order(node.right) print ")" end end ``` ## Step 6: Instance method: remove(value) When we want to remove a node from BST, there’re three cases we need to consider: - If the node has zero children: it’s the ‘leaf’ node, we can simply remove from the tree; - If the node has one child(left or right child): after we remove this node, we need to promote its child to the position; - If the node has two children: we need to find a candidate to promote. The candidates could be the smallest data of the right subtree or the largest data of the left subtree. I draw some graphs to illustrate the cases: ![](https://miro.medium.com/v2/resize:fit:700/1*t3CzqIgFVG0e4dQRow1ZOQ.jpeg) ![](https://miro.medium.com/v2/resize:fit:700/1*Q38zPObESfnlt9VPHons8A.jpeg) ```plain text def remove(value, node = self.root) removeHelper(value, node = self.root) @size -= 1 node endprivate #this helper method will avoid the multiple size decreses in recursion def removeHelper(value, node = self.root) if node == nil return nil end if node.value > value node.left = removeHelper(value, node.left) elsif node.value < value node.right = removeHelper(value, node.right) else if node.left != nil && node.right != nil temp = node min_of_right_subtree = find_min(node.right) node.value = min_of_right_subtree.value node.right = removeHelper(min_of_right_subtree.value, node.right) elsif node.left != nil node = node.left elsif node.right != nil node = node.right else node = nil end end return node end ``` ## Step 7: Instance method: clear() clear() is a straightforward method: we set the root to nil and reset the size to zero! ```plain text def clear() self.root = nil self.size = 0 end ``` ## Step 8: Instance method: size() This is the easiest method! Since we already have a class variable @size that representing the size of the tree, here, we can simply return this value: ```plain text def size() @size end ```

Visibility

Visible to everyone

Reading Status

Related Bookmarks

My Note


Saved!

Annotations

Export as Markdown
+ Annotate selection

Add Annotation