JS implements binary search tree [addition, deletion, modification and query] (detailed explanation)

Summary

If the emergence of a data structure must be because it can solve certain problems, our traditional linear list has arrays and linked lists. The former has a greater advantage in modifying and checking, and the latter has a greater advantage in adding and deleting.
But we often need to use these functions in the application, and there are some decisiveness in the choice of array or linked list.
The binary search tree is a data structure that has the characteristics of fast array modification and query and fast addition and deletion of linked lists.

A binary search tree has the characteristics of a binary tree, and all nodes in the left subtree of each node are smaller than the value of the current node, and all nodes in the right subtree of each node are larger than the value of the current node.

It is this feature that makes it highly efficient in data manipulation and retrieval operations.

OK, in this article we mainly use JS to implement a binary search tree.
The preparatory work is to define a binary tree class and a node class.

function BST () {
    
    
  this.root = null;
}

function Node (val, left, right) {
    
    
  this.val = val;
  this.left = left;
  this.right = right;
}

1. increase

To implement the operation of adding nodes, we have to do it in several situations:

1. The binary tree is empty

In this case, root === null, we can directly give the value of the new node to root.

2. A binary tree has only one root node

In this case, we can judge very well. We only need to judge which value is larger between the node node and the root node.
node.val > root.val : root.right = node
node.val < root.val : root.left = node

3. The number of nodes in the binary tree is greater than 2

This situation is more complicated, and it is also the difficulty of adding nodes.
We can think about this problem in this way, we take the root node as the current node .

Compare the value of the current node empty and the value of the new node
node.val > empty.val : empty = empty.right
node.val < empty.val : empty = empty.left

Until the current node has no child nodes, then check whether the new node is placed on the left or right of the current node.

insert image description here
As shown in the figure, if you want to insert the node 2 into the binary tree, then it is the above steps.

BST.prototype.add = function (val) {
    
    
  let node = new Node(val, null, null);
  //情况1
  if (this.root === null) {
    
    
    return this.root = node
  }
  let empty = this.root;
  //循环直到empty的左子节点和右子节点都为null
  while (empty.right != null || empty.left != null) {
    
    
    if (val > empty.val) {
    
    
    //情况2
      if (empty.right == null) {
    
    
        empty.right = node;
        return;
      } else {
    
    
        empty = empty.right;
        where = 'right'
      }
    } else {
    
    
    //情况2
      if (empty.left == null) {
    
    
        empty.left = node;
        return;
      } else {
    
    
        where = 'left'
      }
      empty = empty.left;
    }
  }
  //判断最后新节点为empty的左子节点还是右子节点
  if (val > empty.val) {
    
    
    empty.right = node;
  } else {
    
    
    empty.left = node
  }
}

2. check

Second, let's talk about how to find out whether an element exists, why not delete it first? Because the premise of deleting a node is to find the node, so let's talk about the search method first.

insert image description here
For example, if we want to find the node 12, it is actually relatively simple when we think of it.
We only need to compare with the root node first, find 12 > 5
and then compare with the right node of the root node, find 12 > 8
and then compare with the right node of 8, find 12 === 12

OK, this means that there are 12 nodes, and we can return true.
Here you will find that our solution to each step is the same, so we use recursive thinking to solve it .

BST.prototype.search = function (val) {
    
    
  const searchNode = function (node, val) {
    
    
  //两种情况下的递归终止条件,顺序不能错
    if (node === null) {
    
    
      return false
    }
    if (node.val === val) {
    
    
      return true
    }
    if (val > node.val) {
    
    
    //右子树递归
      return searchNode(node.right, val);
    } else {
    
    
    //左子树递归
      return searchNode(node.left, val);
    }
  }
  return searchNode(this.root, val)
}

3. delete

We already know how to find a node, what we want to do now is to delete this node, and we will divide it into several situations

1. node.left === null && node.right === null

In this case, the deleted node node has no child nodes, so we just delete this node directly .

2. node.left === null || node.right === null

The meaning of this situation is that the deleted node node has only one child node, and the other child node is null. In fact, this situation is also very simple. We only need to replace the child nodes of node with node itself .

3. node.left != null && node.right != null

In this case, it is more complicated. The deleted node has both left and right child nodes. In this case, we must ensure that the characteristics of the binary search tree are not changed after deletion.

insert image description here

For example, if we delete the node 4, now we don't consider the algorithm, how can we reconstruct the binary tree at a relatively low cost, and ensure that the binary tree still has the property of a binary search tree.

It seems that we can see two situations:
insert image description here
insert image description here
You will find that the difference between these two situations is that, in fact, we just find the node to be deleted, the node with the smallest left subtree or the largest right subtree, and assign the value of this node to The node to delete, and then exile itself , is fine.

So we can choose a method, whether it is the smallest left subtree or the largest right subtree, the deletion method can be implemented.

BST.prototype.remove = function (val) {
    
    

//找到要删除的节点
  const search = function (node, val) {
    
    
    if (val === node.left.val || val === node.right.val) {
    
    
      let parent = node.left.val === val ? node.left : node.right;
      let where = node.left.val === val ? 'left' : 'right';
      return [node, parent, where]
    }
    if (val > node.val) {
    
    
      return search(node.right, val)
    }
    if (val < node.val) {
    
    
      return search(node.left, val)
    }
  }
//保存要删除节点,要删除节点的父节点,已经删除的左右方向
  let [node, parent, where] = search(this.root, val)
  if (node) {
    
    
    if (parent.left == null && parent.right == null) {
    
    
      node[where] = null
    } else if (parent.left == null) {
    
    
      node[where] = parent.right;
    } else if (parent.right == null) {
    
    
      node[where] = parent.left;
    } else {
    
    
      let empty = parent
      //找到左子树中最小的节点
      while (empty.left.left != null) {
    
    
        empty = empty.left;
      }
      parent.val = empty.left.val;
      empty.left = null;
    }
  }
}

There may be many ways to achieve it, just look at your own code habits ^ - ^

4. change

It is also very simple to modify it with the above explanation. You only need to find the current node through the search, and then replace the value.
Not much to say here:

BST.prototype.replace = function (oldVal, newVal) {
    
    
  let node = this.root;
  const replaceNode = function (node) {
    
    
    if (node === null) {
    
    
      return false
    }
    if (node.val === oldVal) {
    
    
      return node.val = newVal;
    }
    if (node.val < oldVal) {
    
    
      replaceNode(node.right, oldVal);
    } else {
    
    
      replaceNode(node.left, oldVal)
    }
  }
  replaceNode(node);
}

The addition, deletion, modification and query of the binary search tree are finished here ^ _ ^

Guess you like

Origin blog.csdn.net/weixin_46726346/article/details/120289424