Data structure: understand binary search tree (JavaScript)

Introduction to binary search tree

  1. Binary search tree is a binary tree with a certain order of magnitude between node values. For each node in the tree:
  2. If its left subtree exists, the value of each node in its left subtree is not greater than the value of the node;
  3. If its right subtree exists, the value of each node in its right subtree is not less than the node value.

Meet the conditions

  1. If the left subtree is not empty, the values of the left and right nodes on the left subtree are less than those of the root node;
  2. If its right subtree is not empty, the values of all nodes on its right subtree are greater than those of the root node;
  3. Its left and right subtrees should also be binary search trees respectively;

The node query process is to compare whether the element values are equal, return if they are equal, and judge the size if they are not equal. Iterate to query the left and right subtrees until equal elements are found or the child node is empty, and the returned node does not exist.

Two special binary trees

For a complete binary tree, all nodes shall fill each layer of the tree as much as possible. If there are still remaining nodes after the previous layer is filled, the next layer shall be filled from left to right as much as possible.

A binary tree with only one node in each layer.

Node instance

let Node = function (key) {
    this.key = key;
    this.left = null;
    this.right = null;
}
this.roots = null;

Instance a node

let node = new Node()
console.log(node)
//{ key: undefined, left: null, right: null }

1. Binary tree insertion

1.1 node instance

//Node instance
function Node(key) {
    this.key = key;
    this.left = null;
    this.right = null;
}

1.2 binary tree object

function BinarySearchTree() {
    this.roots = null;
    this.insert = insert
}

1.3 node insertion (three cases)

Due to the special nature of the binary search tree, each element in the binary search tree can only appear once. Therefore, in the process of insertion, if it is found that this element already exists in the binary search tree, it will not be inserted. Otherwise, find a suitable location for insertion.

1.3.1 the first case:_ root is empty

Insert directly, return true;

let insert = function (key) {
    let newNode = new Node(key)
    if (this.roots === null) {
        this.roots = newNode
    }
}

1.3.2 the second case: the element to be inserted already exists

As mentioned above, if the element already exists in the binary search tree, it will not be inserted. Return false directly; No longer let it insert;

function insertNode(node, newNode) {
    if(newNode.key === node.key){
        return false
    }
}
Node insertion test

It can be seen that node 2 is not repeatedly inserted;

let BST = new BinarySearchTree();
BST.insert(2)
BST.insert(2)
BST.insert(7)
BST.insert(3)
BST.insert(1)
console.log(BST)

1.3.3 the third case: the right position can be found

function insertNode(node, newNode) {
    if(newNode.key === node.key){
        return false
    }
    if (newNode.key < node.key) {
        // If the new node value is less than the current node value, the left child node is inserted
        if (node.left === null) {
            node.left = newNode
        } else {
            insertNode(node.left, newNode)
        }
    } else {
        // If the new node value is less than the current node value, the right child node is inserted
        if (node.right === null) {
            node.right = newNode
        } else {
            insertNode(node.right, newNode)
        }
    }
}

1.3.4 node insertion full version

//Node instance
function Node(key) {
    this.key = key;
    this.left = null;
    this.right = null;
}
//Binary search tree
function BinarySearchTree() {
    this.roots = null;
    this.insert = insert
}
let insert = function (key) {
    let newNode = new Node(key)
    if (this.roots === null) {
        this.roots = newNode
    } else {
        insertNode(this.roots, newNode)
    }
}
function insertNode(node, newNode) {
    if(newNode.key === node.key){
        return false
    }
    if (newNode.key < node.key) {
        // If the new node value is less than the current node value, the left child node is inserted
        if (node.left === null) {
            node.left = newNode
        } else {
            insertNode(node.left, newNode)
        }
    } else {
        // If the new node value is less than the current node value, the right child node is inserted
        if (node.right === null) {
            node.right = newNode
        } else {
            insertNode(node.right, newNode)
        }
    }
}

let BST = new BinarySearchTree();
BST.insert(2)
BST.insert(6)
BST.insert(7)
BST.insert(3)
BST.insert(1)
console.log(BST)

2. Binary tree traversal (four types)

2.1 binary tree traversal type

2.2 preorder traversal

The so-called preorder traversal is to access the root node first, then the left node, and finally the right node.

Then the traversal order is: 8 - > 4 - > 2 - > 6 - > 12 - > 9 - > 15

realization

//Preorder traversal is to access each node in the order that takes precedence over the descendant nodes.
let preOrderTraverse = function (callback) {
    preOrderTraverseNode(this.roots, callback)
}
function preOrderTraverseNode(node, callback) {
    if (node!==null) {
        callback(node.key)
        preOrderTraverseNode(node.left, callback)
        preOrderTraverseNode(node.right, callback)
    }
}
let BST = new BinarySearchTree();
let tree =[8,4,2,6,12,9,15]
tree.forEach(v=>{
    BST.insert(v)
})
BST.preOrderTraverse(key=>console.log(key)) //8,4,2,6,12,9,15

2.3 middle order traversal

The so-called middle order traversal is to access the left node first, then the root node, and finally the right node.

Then the order is: 2 - > 4 - > 6 - > 8 - > 9 - > 12 - > 15

realization

//Medium order traversal is a traversal method that accesses all nodes in order from minimum to maximum
let inOrderTraverse = function (callback) {
    inOrderTraverseNode(this.roots, callback)
}
function inOrderTraverseNode(node, callback) {
    if (node !== null) {
        inOrderTraverseNode(node.left, callback)
        callback(node.key)
        inOrderTraverseNode(node.right, callback)
    }
}
let BST = new BinarySearchTree();
let tree = [8, 4, 2, 6, 12, 9, 15]
tree.forEach(v => {
    BST.insert(v)
})
BST.inOrderTraverse(key => console.log(key)) //2->4->6->8->9->12->15

2.4 post order traversal

The so-called post order traversal is to access the left node first, then the right node, and finally the root node.

Then the order is: 2 - > 6 - > 4 - > 9 - > 15 - > 12 - > 8

realization

//Postorder traversal is to access the descendant nodes of the node first, and then access the node itself
let postOrderTraverse = function (callback) {
    postOrderTraverseNode(this.roots, callback)
}
function postOrderTraverseNode(node, callback) {
    if (node!==null) {
        postOrderTraverseNode(node.left, callback)
        postOrderTraverseNode(node.right, callback)
        callback(node.key)
    }
}

2.5 sequence traversal

The sequence traversal of binary tree, that is, the breadth traversal of binary tree, first traverses the adjacent nodes of the root node, and then traverses the child nodes of the adjacent nodes again. Breadth traversal is usually implemented by means of queues. Use the queue to store the number of nodes of the current layer, traverse the nodes of the current layer, push the nodes of the current layer into the array subRes [], and then push the left and right child nodes of the current node into the queue to enter the next layer for traversal until the complete tree is traversed, that is, the sequence traversal to the binary tree is completed.


Picture from leetcode: Summary of BFS usage scenarios: sequence traversal and shortest path problem

Very wonderful, like!!!

realization

let levelOrder = function (root) {
    if (!root) return []
    let res = [] //Result outermost array
    let queue = [root]
    while (queue.length > 0) {
        var len = queue.length//Number of nodes in the current layer
        var subRes = []
        for (var i = 0; i < len; i++) {
            var node = queue.shift() //Node dequeue
            subRes.push(node.val) //Adds the node value of the current layer to the subRes array
            //Queue the next level nodes
            if (node.left) {
                queue.push(node.left)
            }
            if (node.right) {
                queue.push(node.right)
            }
        }
        res.push(subRes)
    }
    return res
};

For details, see the solution of leetcode: 102. Sequence traversal of binary tree

3 binary tree search

In JavaScript, we can use hasOwnProperty to detect whether the specified key exists in the object. Now we implement a similar method in binary search, passing in a value to judge whether it exists in the binary search tree.

3.1 four cases

  1. First judge whether the incoming node is null. If it is null, it means that the search fails and returns false.
  2. If the node is found, return true.
  3. The node to be found is smaller than the current node. Continue to find the node on the left.
  4. The node to be found is larger than the current node. Continue to find the node on the right.

realization

let search = function (key) {
    searchNode(this.roots, key)
}
function searchNode(node, key) {
    //Search failed, return false
    if (node === null) {
        return false;
    }
    //Smaller than the current node, continue to find the node on the left
    if (node.key > key) {
        searchNode(node.left, key)
    }
    //Larger than the current node, continue to find the node on the right
    if (node.key < key) {
        searchNode(node.right, key)
    }
    return true;
}

4. Find the smallest node

Find the minimum value and search to the left of the binary tree until the node left is null and there is no left node, which proves that it is the minimum value.

realization

//minimum value
function min() {
    return minNode(this.roots) || null
}
function minNode(node) {
    console.log(node)
    while (node !== null && node.left !== null) {
        node = node.left
    }
    return node.key
}

let BST = new BinarySearchTree();
let tree = [8, 4, 2, 6, 12, 9, 15]
tree.forEach(v => {
    BST.insert(v)
})
console.log(BST.min())  //2

5. Find the largest node

Find the maximum value and search to the right side of the binary tree until the node right is null and there is no right node, which proves that it is the maximum value.

realization

//Maximum
let max = function () {
    return maxNode(this.roots) || null
}
function maxNode(node) {
    while (node !== null && node.right !== null) {
        node = node.right
    }
    return node.key
}

let BST = new BinarySearchTree();
let tree = [8, 4, 2, 6, 12, 9, 15]
tree.forEach(v => {
    BST.insert(v)
})
console.log(BST.max()) //15

6. Delete binary tree node

  1. First judge whether the node is null. If it is equal to null, it will be returned directly.
  2. Judge that the node to be deleted is smaller than the current node, and find it on the left side of the tree
  3. Judge that the node to be deleted is larger than the current node, and find it on the right side of the tree
  4. The node has been found, which can be divided into four cases:

    4.1. The current node has neither left nor right nodes. Delete it directly and return null

    4.2. If the left node is null, it proves that it has a right node. Change the reference of the current node to the reference of the right node and return the updated value

    4.3. If the right node is null, it proves that it has a left node. Change the reference of the current node to the reference of the left node and return the updated value

    4.4. If the left node and the right node are not empty

realization

//Remove node
let remove = function (key) {
    this.roots = removeNode(this.roots, key)
    console.log(this.roots)
}
function findMinNode(node, key) {
    while (node !== null && node.left !== null) {
        node = findMinNode(node.left, key);
    }
    return node;
}
function removeNode(node, key) {
    //1. To delete a node smaller than the current node, look to the left side of the tree
    if (node.key > key) {
        node.left = removeNode(node.left, key);
        return node;
    }
    //2. To delete a node larger than the current node, look it up on the right side of the tree
    if (node.key < key) {
        node.right = removeNode(node.right, key);
        return node;
    }
    if (node.key === key) {
        //1. The current node has neither left nor right nodes. Delete it directly and return null
        if (node.left === null && node.right === null) {
            return null;
        }
        //2. If the right node is null, it proves that it has a left node. Change the reference of the current node to the reference of the left node and return the updated value
        if (node.left !== null && node.right === null) {
            return node.left;
        }
        //3. If the left node is null, it proves that it has a right node. Change the reference of the current node to the reference of the right node and return the updated value
        if (node.left === null && node.right !== null) {
            return node.right;
        }
        node.right = findMinNode(node.right, key);
        return node;
    }
}

let BST = new BinarySearchTree();
let tree = [8, 4, 2, 6, 12, 9, 15]
tree.forEach(v => {
    BST.insert(v)
})
console.log(BST.remove(15)) //15

7. Search the complete code of binary tree

//Basic class
function BinarySearchTree() {
    let Node = function (key) {
        this.key = key;
        this.left = null;
        this.right = null;
    }
    this.roots = null;
    //Binary tree insertion
    this.insert = function (key) {
        let newNode = new Node(key)
        if (this.roots === null) {
            this.roots = newNode
        } else {
            insertNode(this.roots, newNode)
        }
    }
    function insertNode(node, newNode) {
        if (newNode.key < node.key) {
            // If the new node value is less than the current node value, the left child node is inserted
            if (node.left === null) {
                node.left = newNode
            } else {
                insertNode(node.left, newNode)
            }
        } else {
            // If the new node value is less than the current node value, the right child node is inserted
            if (node.right === null) {
                node.right = newNode
            } else {
                insertNode(node.right, newNode)
            }
        }
    }

    //Medium order traversal is a traversal method that accesses all nodes in order from minimum to maximum
    this.inOrderTraverse = function (callback) {
        inOrderTraverseNode(this.roots, callback)
    }
    function inOrderTraverseNode(node, callback) {
        if (node !== null) {
            inOrderTraverseNode(node.left, callback)
            callback(node.key)
            inOrderTraverseNode(node.right, callback)
        }
    }
    //Preorder traversal is to access each node in the order that takes precedence over the descendant nodes.
    this.preOrderTraverse = function (callback) {
        preOrderTraverseNode(this.roots, callback)
    }
    function preOrderTraverseNode(node, callback) {
        if (node !== null) {
            callback(node.key)
            preOrderTraverseNode(node.left, callback)
            preOrderTraverseNode(node.right, callback)
        }
    }
    //Postorder traversal is to access the descendant nodes of the node first, and then access the node itself
    this.postOrderTraverse = function (callback) {
        postOrderTraverseNode(this.roots, callback)
    }
    function postOrderTraverseNode(node, callback) {
        if (node !== null) {
            postOrderTraverseNode(node.left, callback)
            postOrderTraverseNode(node.right, callback)
            callback(node.key)
        }
    }

    //Search Binary Tree
    this.search = function (key) {
        searchNode(this.roots, key)
    }
    function searchNode(node, key) {
        if (node === null) {
            return false;
        }
        if (node.key > key) {
            searchNode(node.left, key)
        }
        if (node.key < key) {
            searchNode(node.right, key)
        }
        return true;
    }
    //minimum value
    this.min = function () {
        minNode(this.roots)
    }
    function minNode(node) {
        while (node !== null && node.left !== null) {
            node = node.left
        }
        return node.key
    }
    //Maximum
    this.max = function () {
        maxNode(this.roots)
    }
    function maxNode(node) {
        while (node !== null && node.right !== null) {
            node = node.right
        }
        return node.key
    }
    //Remove node
    this.remove = function (key) {
        this.roots = removeNode(this.roots, key)
    }
    function findMinNode(node, key) {
        while (node !== null && node.left !== null) {
            node = findMinNode(node.left, key);
        }
        return node;
    }
    function removeNode(node, key) {
        //1. To delete a node smaller than the current node, look to the left side of the tree
        if (node.key > key) {
            node.left = removeNode(node.left, key);
            return node;
        }
        //2. To delete a node larger than the current node, look it up on the right side of the tree
        if (node.key < key) {
            node.right = removeNode(node.right, key);
            return node;
        }
        if (node.key === key) {
            //1. The current node has neither left nor right nodes. Delete it directly and return null
            if (node.left === null && node.right === null) {
                return null;
            }
            //2. If the right node is null, it proves that it has a left node. Change the reference of the current node to the reference of the left node and return the updated value
            if (node.left !== null && node.right === null) {
                return node.left;
            }
            //3. If the left node is null, it proves that it has a right node. Change the reference of the current node to the reference of the right node and return the updated value
            if (node.left === null && node.right !== null) {
                return node.right;
            }
            node.right = findMinNode(node.right, key);
            return node;
        }
    }
}
let nodeTree = [1, 12, 2, 3, 4, 5, 14, 6, 19];
let BST = new BinarySearchTree();

nodeTree.forEach(v => {
    BST.insert(v)
})
console.log(BST.remove(19))
console.log(BST.max())
console.log(BST.min())
console.log(BST.preOrderTraverse(key => console.log(key)))
console.log(BST.inOrderTraverse(key => console.log(key)))
console.log(BST.postOrderTraverse(key => console.log(key)))

8. Summary

The results of the tree are different for different data and different insertion order. This is the limitation of binary tree. At the same time, when there are too many nodes, the performance consumption will be compared. If you have any questions, you can leave a message in the comment area, study together and improve together. You can also visit your personal blog address. Tell me Zhan to hide

Online DEMO address Online DEMO address

9. References

1. javascript implementation of binary tree search and node deletion

2. javascript implementation of binary tree

3. In depth understanding of JavaScript binary tree

4. Data structure (II): Binary Search Tree

5. Implement a binary search tree (JavaScript version)

6. Insertion and deletion diagram of binary search tree

7. Four traversal methods of binary tree

8.102. Sequence traversal of binary tree

Tags: Javascript data structure Binary tree

Posted on Sun, 21 Nov 2021 05:00:11 -0500 by Fluf