[Data structure and algorithm] JavaScript implements binary search tree

1. Encapsulation of binary search tree

Basic properties of binary search trees:

As shown in the figure: the binary search tree has four most basic attributes: root pointing to the node (root), in the node key (key), left pointer (right), a> (right). right pointer

Insert image description here

Therefore, in addition to defining the root attribute in a binary search tree, a node internal class should also be defined, which contains the three attributes left, right, and key of each node:

    //封装二叉搜索树
    function BinarySearchTree(){
    
    

      //节点内部类
      function Node(key){
    
    
        this.key = key
        this.left = null
        this.right = null
      }

      //属性
      this.root = null
  }

Common operations on binary search trees:

  • insert(key): Insert a new key into the tree;
  • search(key): Search for a key in the tree. If the node exists, it returns true; if it does not exist, it returns false;
  • inOrderTraverse: Traverse all nodes through in-order traversal;
  • preOrderTraverse: Traverse all nodes through pre-order traversal;
  • postOrderTraverse: Traverse all nodes through post-order traversal;
  • min: Returns the smallest value/key in the tree;
  • max: Returns the largest value/key in the tree;
  • remove(key): remove a key from the tree;
1. Insert data

Implementation ideas:

  • First create a node object based on the incoming key;
  • Then determine whether the root node exists. If it does not exist, pass: this.root = newNode, and directly use the new node as the root node of the binary search tree.
  • If a root node exists, redefine an internal method insertNode() to find the insertion point.
     //insert方法:对外向用户暴露的方法
      BinarySearchTree.prototype.insert = function(key){
    
    
        //1.根据key创建节点
        let newNode = new Node(key)
          
        //2.判断根节点是否存在
        if (this.root == null) {
    
    
          this.root = newNode
          //根节点存在时
        }else {
    
    
          this.insertNode(this.root, newNode)
        }
      }

Implementation ideas of internal method insertNode():

Based on comparing the two incoming nodes, the location where the new node is suitable for insertion is searched until the new node is successfully inserted.

When newNode.key < node.key search left:

  • Case 1: When node has no left child node, insert directly:
  • Case 2: When node has a left child node, insertNode() is called recursively until newNode is successfully inserted without a left child node. This situation is no longer met, and insertNode() is no longer called, and the recursion stops.

Insert image description here

When newNode.key >= node.key, search to the right, similar to searching to the left:

  • Case 1: When node has no right child node, insert directly:
  • Case 2: When the node has a right child node, insertNode() is still called recursively until the node passed into the insertNode method has no right child node and newNode is successfully inserted:

Insert image description here

insertNode() code implementation:

      //内部使用的insertNode方法:用于比较节点从左边插入还是右边插入
      BinarySearchTree.prototype.insertNode = function(node, newNode){
    
    
        //当newNode.key < node.key向左查找
/*----------------------分支1:向左查找--------------------------*/      
        if(newNode.key < node.key){
    
    
          //情况1:node无左子节点,直接插入
/*----------------------分支1.1--------------------------*/
          if (node.left == null) {
    
    
            node.left = newNode
          //情况2:node有左子节点,递归调用insertNode(),直到遇到无左子节点成功插入newNode后,不再符合该情况,也就不再调用insertNode(),递归停止。
/*----------------------分支1.2--------------------------*/
          }else{
    
    
            this.insertNode(node.left, newNode)
          }
        //当newNode.key >= node.key向右查找
/*-----------------------分支2:向右查找--------------------------*/        
        }else{
    
    
          //情况1:node无右子节点,直接插入
/*-----------------------分支2.1--------------------------*/ 
          if(node.right == null){
    
    
            node.right == newNode
          //情况2:node有右子节点,依然递归调用insertNode(),直到遇到无右子节点成功插入newNode为止
/*-----------------------分支2.2--------------------------*/ 
          }else{
    
    
            this.insertNode(node.right, newNode)
          }
        }
      }

Detailed explanation of the process:

To better understand, take the following binary search tree as an example:

Insert image description here

Want to insert data 10 into the above binary search tree (blue):

  • First pass key = 10 into the insert method. Since there is a root node 9, the insetNode method is called directly. The parameters passed in are: node = 9, newNode = 10;
  • Since 10 > 9, enter branch 2 and look to the right for a suitable insertion position;
  • Since the right child node of root node 9 exists and is 13, branch 2.2 is entered, and the insertNode method is called recursively. The parameters passed in are: node = 13, newNode = 10;
  • Since 10 < 13 , enter branch 1 and look to the left for a suitable insertion position;
  • Since the left child node of parent node 13 exists and is 11, branch 1.2 is entered and the insertNode method is called recursively. The parameters passed in are: node = 11, newNode = 10;
  • Since 10 < 11, enter branch 1 and look to the left for a suitable insertion position;
  • Since the left child node of parent node 11 does not exist, branch 1.1 is entered and node 10 is successfully inserted. Since the conditions of branch 1.2 are not met, the insertNode method will not continue to be called and the recursion will stop.

Test code:

    //测试代码
    //1.创建BinarySearchTree
    let bst = new BinarySearchTree()

    //2.插入数据
    bst.insert(11);
    bst.insert(7);
    bst.insert(15);
    bst.insert(5);
    bst.insert(9);
	console.log(bst);

You should get the binary search tree shown below:

Insert image description here

Test Results

Insert image description here

2. Traverse the data

The tree traversal mentioned here is not only for binary search trees, but applies to all binary trees. Since the tree structure is not a linear structure, there are many options for traversal methods. The three common binary tree traversal methods are:

  • Preorder traversal;
  • In-order traversal;
  • Postorder traversal;

There is also level-order traversal, which is less used.

2.1. Preorder traversal

The process of preorder traversal is:

  • First, traverse the root node;
  • Then, traverse its left subtree;
  • Finally, traverse its right subtree;

Insert image description here

As shown in the figure above, the node traversal order of the binary tree is: A -> B -> D -> H -> I -> E -> C -> F -> G.

Code:

	  //先序遍历
      //掺入一个handler函数方便之后对得到的key进行处理
      BinarySearchTree.prototype.preOrderTraversal = function(handler){
    
    
        this.preOrderTraversalNode(this.root, handler)
      }

      //封装内部方法,对某个节点进行遍历
      BinarySearchTree.prototype.preOrderTraversalNode = function(node,handler){
    
    
        if (node != null) {
    
    
          //1.处理经过的节点
          handler(node.key)
/*----------------------递归1----------------------------*/
          //2.遍历左子树中的节点
          this.preOrderTraversalNode(node.left, handler)
/*----------------------递归2----------------------------*/
          //3.遍历右子树中的节点
          this.preOrderTraversalNode(node.right, handler)
        }
      }

Detailed explanation of the process:

Take traversing the following binary search tree as an example:

Insert image description here

First call the preOrderTraversal method, and then call the preOrderTraversalNode method in the method to traverse the binary search tree. In the preOrderTraversalNode method, recursion 1 is responsible for traversing the left child node, and recursion 2 is responsible for traversing the right child node. Recursion 1 is executed first, and the execution process is shown in the figure below:

Note: preOrderTraversalNode() is A()

Insert image description here

It can be seen that method A was called recursively 4 times in total, passing in 11, 7, 5, and 3 respectively. Finally, it encountered null and did not satisfy the node != null condition and ended recursion 1; note that only the first recursion 1 has been executed at this time. Recursion 2 is not executed, and after recursion 1 is executed to null and stops, it must return upward layer by layer, and push the called functions out of the function call stack in order.

Regarding the function call stack: The previous four recursions pushed a total of 4 functions into the function call stack. Now the recursive execution is completed and the functions are pushed out of the stack layer by layer.

It is worth noting that: each level of function has only completed recursion 1. When returning to the level function, for example, A (3) will continue to execute recursion 2 to traverse the right child node in the binary search tree;

During the execution of recursion 2, method A will be continuously called, and recursion 1 and recursion 2 will be executed in sequence, and so on until null is encountered and the node != null condition is not satisfied, then the recursion will be stopped and returned layer by layer, and so on. In the same way, layer A(5), layer A(7), and layer A(11) must go through the above cycle until all nodes in the binary search tree have been traversed.

The specific process is shown in the figure below:

Insert image description here

Test code:

    //测试代码
    //1.创建BinarySearchTree
    let bst = new BinarySearchTree()

    //2.插入数据
    bst.insert(11);
    bst.insert(7);
    bst.insert(15);
    bst.insert(5);
    bst.insert(3);
    bst.insert(9);
    bst.insert(8);
    bst.insert(10);
    bst.insert(13);
    bst.insert(12);
    bst.insert(14);
    bst.insert(20);
    bst.insert(18);
    bst.insert(25);
    bst.insert(6);
    
    //3.测试遍历
    let resultString = ""
    //掺入处理节点值的处理函数
    bst.preOrderTraversal(function(key){
    
    
      resultString += key + "->"
    })
    alert(resultString)

The following sequence should be output: 11 -> 7 -> 5 -> 3 -> 6 -> 9 -> 8 -> 10 -> 15 -> 13 ->12 -> 14 -> 20 -> 18 -> 25.

Test Results:

Insert image description here

2.2. In-order traversal

Implementation idea: The principle is the same as pre-order traversal, but the order of traversal is different.

  • First, traverse its left subtree;
  • Then, traverse the root (parent) node;
  • Finally, traverse its right subtree;

Code:

      //中序遍历
      BinarySearchTree.prototype.midOrderTraversal = function(handler){
    
    
        this.midOrderTraversalNode(this.root, handler)
      }

      BinarySearchTree.prototype.midOrderTraversalNode = function(node, handler){
    
    
        if (node != null) {
    
    
          //1.遍历左子树中的节点
          this.midOrderTraversalNode(node.left, handler)
          
          //2.处理节点
          handler(node.key)

          //3.遍历右子树中的节点
          this.midOrderTraversalNode(node.right, handler)
        }
      }

Detailed explanation of the process:

The order of traversal should be as shown below:

Insert image description here

First call the midOrderTraversal method, and then call the midOrderTraversalNode method to traverse the binary search tree. First use recursion 1 to traverse the nodes in the left subtree; then, process the parent node; finally, traverse the nodes in the right subtree.

Test code:

  //测试代码
    //1.创建BinarySearchTree
    let bst = new BinarySearchTree()

    //2.插入数据
    bst.insert(11);
    bst.insert(7);
    bst.insert(15);
    bst.insert(5);
    bst.insert(3);
    bst.insert(9);
    bst.insert(8);
    bst.insert(10);
    bst.insert(13);
    bst.insert(12);
    bst.insert(14);
    bst.insert(20);
    bst.insert(18);
    bst.insert(25);
    bst.insert(6);	
    
    //3.测试中序遍历
    let resultString2 =""
    bst.midOrderTraversal(function(key){
    
    
      resultString2 += key + "->"
    })
    alert(resultString2)

The order of the output nodes should be: 3 -> 5 -> 6 -> 7 -> 8 -> 9 -> 10 -> 11 -> 12 -> 13 -> 14 -> ; 15 -> 18 -> 20 -> 25 .

Test Results:

Insert image description here

2.3. Subsequent traversal

Implementation idea: The principle is the same as pre-order traversal, but the order of traversal is different.

  • First, traverse its left subtree;
  • Then, traverse its right subtree;
  • Finally, traverse the root (parent) node;

Code:

      //后序遍历
      BinarySearchTree.prototype.postOrderTraversal = function(handler){
    
    
        this.postOrderTraversalNode(this.root, handler)
      }

      BinarySearchTree.prototype.postOrderTraversalNode = function(node, handler){
    
    
        if (node != null) {
    
    
          //1.遍历左子树中的节点
          this.postOrderTraversalNode(node.left, handler)
          
          //2.遍历右子树中的节点
          this.postOrderTraversalNode(node.right, handler)

          //3.处理节点
          handler(node.key)
        }
      }

Detailed explanation of the process:

The order of traversal should be as shown below:

Insert image description here

First call the postOrderTraversal method, and then call the postOrderTraversalNode method in the method to traverse the binary search tree. First use recursion 1 to traverse the nodes in the left subtree; then, traverse the nodes in the right subtree; finally, process the parent node.

Test code:

    //测试代码
    //1.创建BinarySearchTree
    let bst = new BinarySearchTree()

    //2.插入数据
    bst.insert(11);
    bst.insert(7);
    bst.insert(15);
    bst.insert(5);
    bst.insert(3);
    bst.insert(9);
    bst.insert(8);
    bst.insert(10);
    bst.insert(13);
    bst.insert(12);
    bst.insert(14);
    bst.insert(20);
    bst.insert(18);
    bst.insert(25);
    bst.insert(6);
    
    //3.测试后序遍历
    let resultString3 =""
    bst.postOrderTraversal(function(key){
    
    
      resultString3 += key + "->"
    })
    alert(resultString3)

The order of the output nodes should be: 3 -> 6 -> 5 -> 8 -> 10 -> 9 -> 7 -> 12 -> 14 -> 13 -> 18 -> ; 25 -> 20 -> 15 -> 11 .

Test Results:

Insert image description here

**Summary:** Three traversal methods are distinguished by the order in which the root (parent) node is traversed. For example: pre-order traversal traverses the root node first, in-order traversal traverses the root node second, and subsequent traversal traverses the root node last.

3. Find data
3.1. Find the maximum value & minimum value

Finding the maximum value in a binary search tree is very simple. The minimum value is at the far left of the binary search tree, and the maximum value is at the far right of the binary search tree. Just keep searching left/right to get the best value, as shown in the figure below:

Insert image description here

Code:

      //寻找最大值
      BinarySearchTree.prototype.max = function () {
    
    
        //1.获取根节点
        let node = this.root
        //2.定义key保存节点值
        let key = null
        //3.依次向右不断查找,直到节点为null
        while (node != null) {
    
    
          key = node.key
          node = node.right
        }
        return key
      }

      //寻找最小值
      BinarySearchTree.prototype.min = function(){
    
    
         //1.获取根节点
         let node = this.root
        //2.定义key保存节点值
        let key = null
        //3.依次向左不断查找,直到节点为null
        while (node != null) {
    
    
          key = node.key
          node = node.left
        }
        return key
      }

Test code:

   //测试代码
    //1.创建BinarySearchTree
    let bst = new BinarySearchTree()

    //2.插入数据
    bst.insert(11);
    bst.insert(7);
    bst.insert(15);
    bst.insert(5);
    bst.insert(3);
    bst.insert(9);
    bst.insert(8);
    bst.insert(10);
    bst.insert(13);
    bst.insert(12);
    bst.insert(14);
    bst.insert(20);
    bst.insert(18);
    bst.insert(25);
    bst.insert(6);
    
    //4.测试最值
    console.log(bst.max());
    console.log(bst.min());
    

Test Results:

Insert image description here

3.2. Find specific values

It is also very efficient to find specific values ​​in the binary search tree. Just start from the root node and compare the key value of the node you want to find. Ifnode.key < root, then search to the left, ifnode.key < root a> will search to the right until null is found or found. This can be implemented recursively or in a loop. node.key > root

Implementation code:

     //查找特定的key
      BinarySearchTree.prototype.search = function(key){
    
    
        //1.获取根节点
        let node = this.root

        //2.循环搜索key
        while(node != null){
    
    
          if (key < node.key) {
    
    
            //小于根(父)节点就往左边找
            node = node.left
            //大于根(父)节点就往右边找
          }else if(key > node.key){
    
    
            node = node.right
          }else{
    
    
            return true
          }
        } 
        return false
      }

Test code:

    //测试代码
    //1.创建BinarySearchTree
    let bst = new BinarySearchTree()

    //2.插入数据
    bst.insert(11);
    bst.insert(7);
    bst.insert(15);
    bst.insert(5);
    bst.insert(3);
    bst.insert(9);
    bst.insert(8);
    bst.insert(10);
    bst.insert(13);
    bst.insert(12);
    bst.insert(14);
    bst.insert(20);
    bst.insert(18);
    bst.insert(25);
    bst.insert(6);
    
    //3.测试搜索方法
    console.log(bst.search(24));//false
    console.log(bst.search(13));//true
    console.log(bst.search(2));//false

Test Results:

Insert image description here

4. Delete data

Implementation ideas:

**Step one:** First find the node that needs to be deleted. If not found, there is no need to delete it;

First define the variable current to save the node that needs to be deleted, the variable parent to save its parent node, and the variable isLeftChild to save whether current is the left node of the parent. This makes it easier to change the direction of the relevant node when deleting the node later.

Implementation code:

 		//1.1.定义变量
        let current = this.root
        let parent = null
        let isLeftChild = true

        //1.2.开始寻找删除的节点
        while (current.key != key) {
    
    
          parent = current
          // 小于则往左查找
          if (key < current.key) {
    
    
            isLeftChild = true
            current = current.left
          } else{
    
    
            isLeftChild = false
            current = current.rigth
          }
          //找到最后依然没有找到相等的节点
          if (current == null) {
    
    
            return false
          }
        }
        //结束while循环后:current.key = key

**Step 2:** Delete the specified node found. There are three situations:

  • Delete leaf nodes;
  • Delete nodes with only one child node;
  • Delete a node with two child nodes;
4.1. Case 1: No child nodes

There are two situations when there are no child nodes:

When the leaf node is the root node, as shown in the figure below, at this timecurrent == this.root, directly pass: < /span>, delete the root node. this.root = null

Insert image description here

There are two situations when the leaf node is not the root node, as shown in the following figure:

Insert image description here

If current = 8, you can delete node 8 by: parent.left = null;

If current = 10, you can delete node 10 by: parent.right = null;

Code:

        //情况1:删除的是叶子节点(没有子节点)
        if (current.left == null && current.right ==null) {
    
    
          if (current == this.root) {
    
    
            this.root = null
          }else if(isLeftChild){
    
    
            parent.left = null
          }else {
    
    
            parent.right =null
          }
        }
4.2. Case 2: There is a child node

There are six situations:

When current has a left child node (current.right == null):

  • Case 1: current is the root node (current == this.root), such as node 11. At this time, pass: this.root = current.left, and delete the root node 11;
  • Case 2: current is the left child node of the parent node parent (isLeftChild == true), such as node 5. At this time, parent.left = current.left is used to delete node 5;
  • Case 3: current is the right child node of the parent node parent (isLeftChild == false), such as node 9. At this time, parent.right = current.left is used to delete node 9;

Insert image description here

When current has a right child node (current.left = null):

  • Case 4: current is the root node (current == this.root), such as node 11. At this time, pass: this.root = current.right, and delete the root node 11.
  • Case 5: current is the left child node of the parent node parent (isLeftChild == true), such as node 5. At this time, parent.left = current.right is used to delete node 5;
  • Case 6: current is the right child node of the parent node parent (isLeftChild == false), such as node 9. At this time, parent.right = current.right is used to delete node 9;

Insert image description here

Implementation code:

        //情况2:删除的节点有一个子节点
        //当current存在左子节点时
        else if(current.right == null){
    
    
            if (current == this.root) {
    
    
              this.root = current.left
            } else if(isLeftChild) {
    
    
                parent.left = current.left
            } else{
    
    
                parent.right = current.left
            }
        //当current存在右子节点时
      } else if(current.left == null){
    
    
            if (current == this.root) {
    
    
              this.root = current.rigth
            } else if(isLeftChild) {
    
    
                parent.left = current.right
            } else{
    
    
                parent.right = current.right
            } 
      }
4.3. Case 3: There are two child nodes

This situation is very complicated. First, we will discuss this issue based on the following binary search tree:

Insert image description here

Delete node 9

Under the premise of ensuring that the original binary tree remains a binary search tree after deleting node 9, there are two ways:

  • Method 1: Select a suitable node from the left subtree of node 9 to replace node 9. It can be seen that node 8 meets the requirements;
  • Method 2: Select a suitable node from the right subtree of node 9 to replace node 9. It can be seen that node 10 meets the requirements;

Insert image description here

Delete node 7

Under the premise of ensuring that the original binary tree remains a binary search tree after deleting node 7, there are two ways:

  • Method 1: Select a suitable node from the left subtree of node 7 to replace node 7. It can be seen that node 5 meets the requirements;
  • Method 2: Select a suitable node from the right subtree of node 7 to replace node 7. It can be seen that node 8 meets the requirements;

Insert image description here

Delete node 15

Under the premise of ensuring that the original binary tree is still a binary search tree after deleting node 15, there are also two ways:

  • Method 1: Select a suitable node from the left subtree of node 15 to replace node 15. It can be seen that node 14 meets the requirements;
  • Method 2: Select a suitable node from the right subtree of node 15 to replace node 15. It can be seen that node 18 meets the requirements;

Insert image description here

I believe you have discovered the pattern!

Summary of rules:If the node to be deleted has two child nodes, or even the child node has child nodes, in this case, you need to delete the node from the nodeFind a suitable node among the following child nodes to replace the current node.

If current is used to represent the node that needs to be deleted, the appropriate node refers to:

  • The node in the left subtree of current that is slightly smaller than current, that is, the left subtree of current maximum value in ;
  • The node in the right subtree of current that is slightly larger than current, that is, the right subtree of current Minimum value in ;

Precursor & Successor

In a binary search tree, these two special nodes have special names:

  • The node that is slightly smaller than current is called the predecessor of the current node. For example, node 5 in the figure below is the predecessor of node 7;
  • Nodes that are slightly larger than current are called successors of current node. For example, node 8 in the figure below is the successor of node 7;

Insert image description here

Code:

  • When looking for the successor of the node current that needs to be deleted, you need to search in the current'sright subtreeMinimum value, that is, always in current's right subtree Traverse left to find;
  • When searching for the predecessor, you need to search for the maximum valueleft subtree of current a>, that is, always to the right in current's left subtree Traverse search.

Only the case of finding the successor of current will be discussed below. The principle of finding the predecessor is the same and will not be discussed here.

4.4. Complete implementation
      //删除节点
      BinarySearchTree.prototype.remove = function(key){
    
    
/*------------------------------1.寻找要删除的节点---------------------------------*/
        //1.1.定义变量current保存删除的节点,parent保存它的父节点。isLeftChild保存current是否为parent的左节点
        let current = this.root
        let parent = null
        let isLeftChild = true

        //1.2.开始寻找删除的节点
        while (current.key != key) {
    
    
          parent = current
          // 小于则往左查找
          if (key < current.key) {
    
    
            isLeftChild = true
            current = current.left
          } else{
    
    
            isLeftChild = false
            current = current.right
          }
          //找到最后依然没有找到相等的节点
          if (current == null) {
    
    
            return false
          }
        }
        //结束while循环后:current.key = key

/*------------------------------2.根据对应情况删除节点------------------------------*/
        //情况1:删除的是叶子节点(没有子节点)
        if (current.left == null && current.right ==null) {
    
    
          if (current == this.root) {
    
    
            this.root = null
          }else if(isLeftChild){
    
    
            parent.left = null
          }else {
    
    
            parent.right =null
          }
        }
        //情况2:删除的节点有一个子节点
        //当current存在左子节点时
        else if(current.right == null){
    
    
            if (current == this.root) {
    
    
              this.root = current.left
            } else if(isLeftChild) {
    
    
                parent.left = current.left
            } else{
    
    
                parent.right = current.left
            }
        //当current存在右子节点时
      } else if(current.left == null){
    
    
            if (current == this.root) {
    
    
              this.root = current.right
            } else if(isLeftChild) {
    
    
                parent.left = current.right
            } else{
    
    
                parent.right = current.right
            } 
      }
        //情况3:删除的节点有两个子节点
        else{
    
    
          //1.获取后继节点
          let successor = this.getSuccessor(current)

          //2.判断是否根节点
          if (current == this.root) {
    
    
            this.root = successor
          }else if (isLeftChild){
    
    
            parent.left = successor
          }else{
    
    
            parent.right = successor
          }

          //3.将后继的左子节点改为被删除节点的左子节点
          successor.left = current.left
        }
      }

      //封装查找后继的方法
      BinarySearchTree.prototype.getSuccessor = function(delNode){
    
    
        //1.定义变量,保存找到的后继
        let successor = delNode
        let current = delNode.right
        let successorParent = delNode

        //2.循环查找current的右子树节点
        while(current != null){
    
    
          successorParent = successor
          successor = current
          current = current.left
        }

        //3.判断寻找到的后继节点是否直接就是删除节点的right节点
        if(successor != delNode.right){
    
    
          successorParent.left = successor.right
          successor.right = delNode.right 
        }
        return successor
      }

Test code:

   //测试代码
    //1.创建BinarySearchTree
    let bst = new BinarySearchTree()

    //2.插入数据
    bst.insert(11);
    bst.insert(7);
    bst.insert(15);
    bst.insert(5);
    bst.insert(3);
    bst.insert(9);
    bst.insert(8);
    bst.insert(10);
    bst.insert(13);
    bst.insert(12);
    bst.insert(14);
    bst.insert(20);
    bst.insert(18);
    bst.insert(25);
    bst.insert(6);
    bst.insert(19);
    
   //3.测试删除代码
    //删除没有子节点的节点
    bst.remove(3)
    bst.remove(8)
    bst.remove(10)

    //删除有一个子节点的节点
    bst.remove(5)
    bst.remove(19)

    //删除有两个子节点的节点
    bst.remove(9)
    bst.remove(7)
    bst.remove(15)

    //遍历二叉搜索树并输出
    let resultString = ""
    bst.midOrderTraversal(function(key){
    
    
      resultString += key + "->"
    })
    alert(resultString)

Test Results:

Insert image description here

It can be seen that the nodes in the three cases have been successfully deleted.

5. Complete encapsulation of binary search tree
    //封装二叉搜索树
    function BinarySearchTree(){
    
    

      //节点内部类
      function Node(key){
    
    
        this.key = key
        this.left = null
        this.right = null
      }

      //属性
      this.root = null

      //方法
      //一.插入数据:insert方法:对外向用户暴露的方法
      BinarySearchTree.prototype.insert = function(key){
    
    
        //1.根据key创建节点
        let newNode = new Node(key)
          
        //2.判断根节点是否存在
        if (this.root == null) {
    
    
          this.root = newNode
          //根节点存在时
        }else {
    
    
          this.insertNode(this.root, newNode)
        }
      }

      //内部使用的insertNode方法:用于比较节点从左边插入还是右边插入
      BinarySearchTree.prototype.insertNode = function(node, newNode){
    
    
        //当newNode.key < node.key向左查找
        if(newNode.key < node.key){
    
    
          //情况1:node无左子节点,直接插入
          if (node.left == null) {
    
    
            node.left = newNode
          //情况2:node有左子节点,递归调用insertNode(),直到遇到无左子节点成功插入newNode后,不再符合该情况,也就不再调用insertNode(),递归停止。
          }else{
    
    
            this.insertNode(node.left, newNode)
          }
        //当newNode.key >= node.key向右查找
        }else{
    
    
          //情况1:node无右子节点,直接插入
          if(node.right == null){
    
    
            node.right = newNode
          //情况2:node有右子节点,依然递归调用insertNode(),直到遇到无右子节点成功插入newNode为止
          }else{
    
    
            this.insertNode(node.right, newNode)
          }
        }
      }

      //二.树的遍历
      //1.先序遍历
      //掺入一个handler函数对得到的key进行处理
      BinarySearchTree.prototype.preOrderTraversal = function(handler){
    
    
        this.preOrderTraversalNode(this.root, handler)
      }

      //封装内部方法,对某个节点进行遍历
      BinarySearchTree.prototype.preOrderTraversalNode = function(node,handler){
    
    
        if (node != null) {
    
    
          //1.处理经过的节点
          handler(node.key)

          //2.遍历经过节点的左子节点
          this.preOrderTraversalNode(node.left, handler)

          //3.遍历经过节点的右子节点
          this.preOrderTraversalNode(node.right, handler)
        }
      }

      //2.中序遍历
      BinarySearchTree.prototype.midOrderTraversal = function(handler){
    
    
        this.midOrderTraversalNode(this.root, handler)
      }

      BinarySearchTree.prototype.midOrderTraversalNode = function(node, handler){
    
    
        if (node != null) {
    
    
          //1.遍历左子树中的节点
          this.midOrderTraversalNode(node.left, handler)
          
          //2.处理节点
          handler(node.key)

          //3.遍历右子树中的节点
          this.midOrderTraversalNode(node.right, handler)
        }
      }

      //3.后序遍历
      BinarySearchTree.prototype.postOrderTraversal = function(handler){
    
    
        this.postOrderTraversalNode(this.root, handler)
      }

      BinarySearchTree.prototype.postOrderTraversalNode = function(node, handler){
    
    
        if (node != null) {
    
    
          //1.遍历左子树中的节点
          this.postOrderTraversalNode(node.left, handler)
          
          //2.遍历右子树中的节点
          this.postOrderTraversalNode(node.right, handler)

          //3.处理节点
          handler(node.key)
        }
      }

      //三.寻找最值
      //寻找最大值
      BinarySearchTree.prototype.max = function () {
    
    
        //1.获取根节点
        let node = this.root
        //2.定义key保存节点值
        let key = null
        //3.依次向右不断查找,直到节点为null
        while (node != null) {
    
    
          key = node.key
          node = node.right
        }
        return key
      }

      //寻找最小值
      BinarySearchTree.prototype.min = function(){
    
    
         //1.获取根节点
         let node = this.root
        //2.定义key保存节点值
        let key = null
        //3.依次向左不断查找,直到节点为null
        while (node != null) {
    
    
          key = node.key
          node = node.left
        }
        return key
      }

      //查找特定的key
      BinarySearchTree.prototype.search = function(key){
    
    
        //1.获取根节点
        let node = this.root

        //2.循环搜索key
        while(node != null){
    
    
          if (key < node.key) {
    
    
            //小于根(父)节点就往左边找
            node = node.left
            //大于根(父)节点就往右边找
          }else if(key > node.key){
    
    
            node = node.right
          }else{
    
    
            return true
          }
        } 
        return false
      }

      //四.删除节点
      BinarySearchTree.prototype.remove = function(key){
    
    
/*------------------------------1.寻找要删除的节点---------------------------------*/
        //1.1.定义变量current保存删除的节点,parent保存它的父节点。isLeftChild保存current是否为parent的左节点
        let current = this.root
        let parent = null
        let isLeftChild = true

        //1.2.开始寻找删除的节点
        while (current.key != key) {
    
    
          parent = current
          // 小于则往左查找
          if (key < current.key) {
    
    
            isLeftChild = true
            current = current.left
          } else{
    
    
            isLeftChild = false
            current = current.right
          }
          //找到最后依然没有找到相等的节点
          if (current == null) {
    
    
            return false
          }
        }
        //结束while循环后:current.key = key

/*------------------------------2.根据对应情况删除节点------------------------------*/
        //情况1:删除的是叶子节点(没有子节点)
        if (current.left == null && current.right ==null) {
    
    
          if (current == this.root) {
    
    
            this.root = null
          }else if(isLeftChild){
    
    
            parent.left = null
          }else {
    
    
            parent.right =null
          }
        }
        //情况2:删除的节点有一个子节点
        //当current存在左子节点时
        else if(current.right == null){
    
    
            if (current == this.root) {
    
    
              this.root = current.left
            } else if(isLeftChild) {
    
    
                parent.left = current.left
            } else{
    
    
                parent.right = current.left
            }
        //当current存在右子节点时
      } else if(current.left == null){
    
    
            if (current == this.root) {
    
    
              this.root = current.right
            } else if(isLeftChild) {
    
    
                parent.left = current.right
            } else{
    
    
                parent.right = current.right
            } 
      }
        //情况3:删除的节点有两个子节点
        else{
    
    
          //1.获取后继节点
          let successor = this.getSuccessor(current)

          //2.判断是否根节点
          if (current == this.root) {
    
    
            this.root = successor
          }else if (isLeftChild){
    
    
            parent.left = successor
          }else{
    
    
            parent.right = successor
          }

          //3.将后继的左子节点改为被删除节点的左子节点
          successor.left = current.left
        }
      }

      //封装查找后继的方法
      BinarySearchTree.prototype.getSuccessor = function(delNode){
    
    
        //1.定义变量,保存找到的后继
        let successor = delNode
        let current = delNode.right
        let successorParent = delNode

        //2.循环查找current的右子树节点
        while(current != null){
    
    
          successorParent = successor
          successor = current
          current = current.left
        }

        //3.判断寻找到的后继节点是否直接就是删除节点的right节点
        if(successor != delNode.right){
    
    
          successorParent.left = successor.right
          successor.right = delNode.right 
        }
        return successor
      }
    }

2. Balanced tree

Disadvantages of binary search trees:

When the inserted data is ordered data, the depth of the binary search tree will be too large. For example, the original binary search tree consists of 11 7 15 on the right, as shown in the figure below:

Insert image description here

When a set of ordered data is inserted: 6 5 4 3 2, it will become a search binary tree with too much depth, which will seriously affect the performance of the binary search tree.

Insert image description here

unbalanced tree

  • A better binary search tree, its data should be evenly distributed on the left and right;
  • But after insertingcontinuous data, the data distribution in the binary search tree becomesnot Uniform, we call this kind of tree unbalanced tree;
  • For abalanced binary tree, the efficiency of insertion/search operations isO( logN);
  • For an unbalanced binary tree, it is equivalent to writing a linked list, and the search efficiency becomes < a i=3>O(N);

tree balance

In order to operate a tree in faster time O(logN), we needEnsure that the tree is always balanced:

  • At least most of them are balanced, and the time complexity at this time is close to O(logN);
  • This requires that the number of descendant nodes to the left of each node in the tree should be as equal as possible to The number of descendant nodes on the right;

Common balanced trees

  • AVL tree: It is the earliest balanced tree, which maintains the balance of the tree by storing an extra piece of data in each node. Since the AVL tree is a balanced tree, its time complexity is also O(logN). However, its overall efficiency is not as good as red-black tree, and it is less used in development.
  • Red-black tree: The balance of the tree is also maintained through some features, and the time complexity is also O(logN). When performing operations such as insertion/deletion, the performance is better than that of AVL trees, so the applications of balanced trees are basically red-black trees.

Guess you like

Origin blog.csdn.net/weixin_46862327/article/details/134775802