Data Structures in Swift — Binary Search Tree
By: Mateusz Ślaski
Tags: Data Structures, Algorithms, Swift, Programming, iOS App Development

Basic implementation

There you can find basic implementation of BST with some usefull computed properties:

This class is not describing an entire tree but the single node. Parameters like left , right and parent can store reference to another nodes, so basically when we will store the root of the tree we can use it to manage a whole tree.

All of computed properties (instead of count ) are straightforward. Let’s take a look at count . There we have to recursively loop whole tree and count every single node. That’s why we have to check every node at the left branch left?.count ?? 0 and at the right branch right?.count ?? 0 . Don’t forget to add the value of root .

Inserting new nodes

The easiest way to insert new node to the tree is to use recursive method. (spoiler: almost every tree’s operation is using recursive way).

Insert method

Basically logic of that method is very simple. We have to check value of the root and determine where our new value should be stored: at left or right branch. If there is no more left or right child to look at, we have to create new BinarySearchTree object and connect it to the tree using his parent property.

According to BST rules we have to always start inserting from the root of the tree. Insert method can be executed on every node, so if we start from different node than root we can broke a tree.

Example of insert method

BST will look like this:

Remember to insert values at random order. Below you can check the differences between trees with the same values inserted in both ways: random order and sorted order.

If we create BST using sorted data the tree’s structure is similar to linked list, it has only one branch.

Searching

The purpose of BST is to find some value as quick as possible, so let’s check how to do that:

Search method

Traversing the tree

If you want to check all nodes you have to use one of the traversing method.

In-order traverse. First we have to look at the left child of a node then at the node itself and finally at its right child. Pre-order traverse. First we have to look at a node then its left and right children. Post-order traverse. First we have to look at the left and right children and process the node itself last.

All of them are working the same but in different orders.

Let’s check implementations:

Traversing methods

Let’s see output from every method. inOrder: 15 17 19 21 27 33 , preOrder: 21 17 15 19 33 27 and postOrder: 15 19 17 27 33 21 .

We can make here a little improvement and create one method for traversing where user can choose type of traverse. To reach it we have to make few changes:

Make all three traverse methods private Create an Enumeration type for traverse types Create one public method which will use enum type as an input

Improved traversing

Deleting nodes

Deleting nodes is straightforward if we keep in mind one important thing. After removing a node we have to replace it with with its biggest child from left branch or smallest child from the right branch.

To make delete operation more readable we have to prepare some helper functions. First of them will handle parent reconnection:

reconnectParent method

Basically we have to check if current node’s parent is one of the children. If yes just connect new node to it. If it’s not a child connect it to self.

Second thing is abillity to find minimum and maximum:

minimum and maximum methods

For minimum we have to find last node on the left branch, and for maximum last node on right branch.

Now, we are ready to remove node from the tree:

remove method

Let’s talk about removing step by step:

Temporary object of type BinarySearchTree?. Replacement for current node can be either the biggest one on the left branch or the smallest one on the right branch. If both of them are nils will be a nil as well. Delete replacement node. Sync up rest of the tree. Clean current node’s parameters. Return replacement. Perhaps it can be useful at some cases.

Tree structure after removing a node:

Let’s check if all is working fine:

remove example

Depth

Method for depth is simple and straightforward:

depth method

From our node we have to go upwards using parent parameter. Every time when parent of particullar node is not a nill we have to increment a counter. As we know the root doesn’t has a parent so when we found a nil we can return our counter.

Height

It is very similar to depth method, but here we are going downwards.

height method

If our node is a Leaf, the height is 0, but if it has any child we can calculate a height using recursion. We have to choose “longer” branch for it.

Predecessor

Why we need predecessor and successor? BST is always sorted, but we can’t check the predecessor of root by just checking root.left parameter. The predecessor can be a child of node which is connected to root.left . To solve that issue we can use dedicated methods.

Let’s check implementation of predecessor:

predecessor method

If our node has left child we have to check maximum of left branch. That’s all to find predecessor, but… if our node doesn’t has left child we have to use different pattern to find predecessor. First we have to find our node’s parent . If value of that parent is less than our current value we are at home, if it is not less we have to swi to parent and check again.

Successor

Implementation is very similar to predecessor so let’s check the code:

successor method

There is nothing new so go ahead 😃.

Validation

Is it possible to create not valid BST? Of course it is. Let’s check image below:

Not valid BST

Root has value 38, left child of root has value 13. Now we are going to add new node to our tree. New node has value 55. When we call insert method at left child (node with value 13) instead of a root our tree become not valid BST.

To avoid this situation we can add validation inside insert method, something like if it’s not a root return nil. Instead of that we will create method which can validate our tree:

isValidBST method

Validation method is using minimum and maximum values, ie Int.min and Int.max . Next we have to check if value of current node is less than minValue or greater than maxValue. Next we are checking left branch of tree, and right branch of tree — recursively ofc 😉. If left and right branches are valid, whole tree will be valid.

Let’s check simple usage of validation method:

isValidBST usage

Bonus

If you want to create BST from data stored at array you can use second init() method:

convenience init