Cartesian trees, also known as Treap (tree + heap) are a type of binary search tree that offers a combination of the performance of a self-balancing tree with the efficiency of a heap. Cartesian trees are used to store and organize data in a way that allows for quick access, search, insertion, and deletion. This makes them one of the most efficient data structures for dealing with large datasets. In this article, we will explore the time and space complexities associated with access, search, insertion, and deletion in Cartesian trees. We will also provide coding examples to illustrate how these operations are performed in Python.

What is a Cartesian Tree?

A Cartesian tree is a binary tree that consists of a root node and two subtrees, each of which is also a Cartesian tree. Each node in the tree stores a value, and the elements in a Cartesian tree are arranged according to a specific ordering rule. The Cartesian tree is typically constructed using a heap-ordered tree structure, with the root node having the highest priority. Each node in the tree has a priority which is used to determine the ordering of the elements.

How does Cartesian Trees Work?

Cartesian Trees are a data structure used to store binary search trees (BSTs) in an array. A Cartesian Tree consists of a root node and a number of other nodes representing the elements that make up the BST. Each node has three fields:

  • The value of the node
  • The index of its left child
  • The index of its right child

The root node is always located at index 0. The left and right child nodes of each node can be determined by knowing the index of the node and its left and right child values.

Cartesian Trees are used to speed up certain operations on BSTs. For example, searching for a particular element can be done in O(log n) time instead of O(n) time with a regular BST. This is because the nodes of the Cartesian Tree are stored in an array, which allows for quicker access than a regular BST.

To build a Cartesian Tree, one must first build a BST using the standard insertion and deletion algorithms. Once the BST is built, the root node is placed at index 0 and the remaining nodes are placed in the array in an in-order fashion. For example, if the BST contains the elements 5, 3, 8, 7, 6, 4, then the array would be [5, 3, 4, 8, 6, 7]. Then, the left and right child indices for each node can be determined. For example, the left child of the root node (5) is 3 and the right child is 8.

Once the tree is built, the same operations that can be performed on a regular BST (such as searching, insertion, and deletion) can be performed on the Cartesian Tree.

Time Complexity for Access, Search, Insertion, and Deletion

Access

Accessing an element in a Cartesian tree has an average time complexity of O(log n). This means that the time required to access an element increases logarithmically with the size of the tree.

Search

Searching for an element in a Cartesian tree has an average time complexity of O(log n). This means that the time required to search for an element in the tree increases logarithmically with the size of the tree.

Insertion

Inserting an element into a Cartesian tree has an average time complexity of O(log n). This means that the time required to insert an element into the tree increases logarithmically with the size of the tree.

Deletion

Deleting an element from a Cartesian tree has an average time complexity of O(log n). This means that the time required to delete an element from the tree increases logarithmically with the size of the tree.

Worst Case Time Complexity for Access, Search, Insertion, and Deletion

Access

The worst case time complexity for accessing an element in a Cartesian tree is O(n). This means that the time required to access an element may increase linearly with the size of the tree.

Search

The worst case time complexity for searching for an element in a Cartesian tree is O(n). This means that the time required to search for an element in the tree may increase linearly with the size of the tree.

Insertion

The worst case time complexity for inserting an element into a Cartesian tree is O(n). This means that the time required to insert an element into the tree may increase linearly with the size of the tree.

Deletion

The worst case time complexity for deleting an element from a Cartesian tree is O(n). This means that the time required to delete an element from the tree may increase linearly with the size of the tree.

Space Complexity

The space complexity for a Cartesian tree is O(n). This means that the amount of space required for the tree is proportional to the size of the tree.

Python Implementation

In this section, we will provide coding examples in Python that demonstrate how to create and manipulate Cartesian trees.

Creating a Cartesian Tree

We will start by creating a simple Cartesian tree in Python.

class Node: 
    def __init__(self, val): 
        self.val = val 
        self.left = None
        self.right = None

class CartesianTree: 
    def __init__(self): 
        self.root = None 
  
    def insert(self, val): 
        # Create a new node 
        newNode = Node(val) 
  
        # Base Case 
        if self.root is None: 
            self.root = newNode 
            return 
  
        # Traverse and find the correct position 
        temp = self.root 
        while temp is not None: 
            if newNode.val < temp.val: 
                if temp.left is None: 
                    temp.left = newNode 
                    return 
                else: 
                    temp = temp.left 
            else: 
                if temp.right is None: 
                    temp.right = newNode 
                    return 
                else: 
                    temp = temp.right 

In the above code, we create a Node class that contains the data associated with each node in the tree. We then create a CartesianTree class, which contains the methods for manipulating the tree. The insert() method is used to add a node to the tree. It first checks to see if the tree is empty, and if so it creates a new node and assigns it to the root. Otherwise, it traverses the tree to find the correct position for the new node.

Searching in a Cartesian Tree

Next, we will demonstrate how to search for an element in a Cartesian tree.

def search(self, val): 
        # Base Case 
        if self.root is None: 
            return False 
  
        # Traverse and search for the element 
        temp = self.root 
        while temp is not None: 
            if temp.val == val: 
                return True 
            elif val < temp.val: 
                temp = temp.left 
            else: 
                temp = temp.right 
        return False

The search() method is used to search for an element in the tree. It first checks to see if the tree is empty, and if so it returns False. Otherwise, it traverses the tree to search for the element. If the element is found, it returns True; otherwise, it returns False.

Deleting from a Cartesian Tree

Finally, we will demonstrate how to delete an element from a Cartesian tree.

def delete(self, val): 
        # Base Case 
        if self.root is None: 
            return 
  
        # Find the node to be deleted 
        temp = self.root 
        parent = None 
        while temp is not None and temp.val != val: 
            parent = temp 
            if val < temp.val: 
                temp = temp.left 
            else: 
                temp = temp.right 
  
        # If node to be deleted is not found 
        if temp is None: 
            return 
  
        # If node to be deleted has one child 
        if temp.left is None: 
            if parent is None: 
                self.root = temp.right 
            elif temp is parent.left: 
                parent.left = temp.right 
            else: 
                parent.right = temp.right 
  
        # If node to be deleted has two children 
        elif temp.right is None: 
            if parent is None: 
                self.root = temp.left 
            elif temp is parent.left: 
                parent.left = temp.left 
            else: 
                parent.right = temp.left 
  
        # If node to be deleted has two children 
        else: 
            successor = temp.right 
            while successor.left is not None: 
                successor = successor.left 
            temp.val = successor.val 
            if successor is temp.right: 
                temp.right = successor.right 
            else: 
                successorParent.left = successor.right 

The delete() method is used to delete an element from the tree. It first finds the node to be deleted and its parent. If the node to be deleted has one child, it deletes the node and assigns its child to the parent. If the node to be deleted has two children, it finds the in-order successor of the node and deletes it.

Conclusion

In this article, we have explored the time and space complexities associated with access, search, insertion, and deletion in Cartesian trees. We have also provided coding examples in Python to illustrate how these operations are performed. Cartesian trees are a powerful data structure that are used to store and organize data in a way that allows for quick access, search, insertion, and deletion. Coding

Exercises

Write a function to traverse a Cartesian tree in pre-order.

def preOrder(root): 
    if root is not None: 
        print root.val 
        preOrder(root.left) 
        preOrder(root.right) 

Write a function to traverse a Cartesian tree in post-order.

def postOrder(root): 
    if root is not None: 
        postOrder(root.left) 
        postOrder(root.right) 
        print root.val 

Write a function to traverse a Cartesian tree in-order.

def inOrder(root): 
    if root is not None: 
        inOrder(root.left) 
        print root.val 
        inOrder(root.right) 

Write a function to search for an element in a Cartesian tree.

def search(root, val): 
    if root is None: 
        return False 

    if root.val == val: 
        return True 
    elif val < root.val: 
        return search(root.left, val) 
    else: 
        return search(root.right, val) 

Write a function to delete an element from a Cartesian tree.

def delete(root, val): 
    # Base Case 
    if root is None: 
        return 

    # Find the node to be deleted 
    temp = root 
    parent = None 
    while temp is not None and temp.val != val: 
        parent = temp 
        if val < temp.val: 
            temp = temp.left 
        else: 
            temp = temp.right 

    # If node to be deleted is not found 
    if temp is None: 
        return 

    # If node to be deleted has one child 
    if temp.left is None: 
        if parent is None: 
            root = temp.right 
        elif temp is parent.left: 
            parent.left = temp.right 
        else: 
            parent.right = temp.right 

    # If node to be deleted has two children 
    elif temp.right is None: 
        if parent is None: 
            root = temp.left 
        elif temp is parent.left: 
            parent.left = temp.left 
        else: 
            parent.right = temp.left 

    # If node to be deleted has two children 
    else: 
        successor = temp.right 
        while successor.left is not None: 
            successor = successor.left 
        temp.val = successor.val 
        if successor is temp.right: 
            temp.right = successor.right 
        else: 
            successorParent.left = successor.right