[JS data structure and algorithm] Common methods of binary search tree

table of Contents

1. Introduce the structure encapsulation of the binary tree

Two, insert insert(key)

Third, the traversal method of the binary tree

1. First-order traversal

2. In-order traversal

3. Post-order traversal

Fourth, the binary search tree finds the best value

Five, binary search tree to find a key

Six, the binary search tree delete node

Seven, complete code


1. Introduce the structure encapsulation of the binary tree

function BinarySearchTree(){

      // 新结点创建的构造函数
      function Node(key){
        this.key = key;
        this.lchild = null;
        this.rchild = null;
      }

      // 保存根的属性
      this.root = null;

      // 创建关于二叉搜索树的相关操作方法

}

Two, insert insert(key)

Ideas:

Create insert method

    1. Create a new node

    2. Determine whether it is an empty tree.
         2.1 If it is an empty tree, let the attribute of the root point to the new node.
         2.2 If it is not a non-empty tree, find a suitable location to insert

Then create a method for internal use insertNode ()
. 1, compare the new node and the node size value
       1.1 if the value of the new node than the node values of the small
               insert empty right subtree node comparison, there are no to continue to empty the right comparison
       1.2 If the value of the new node than the node's value large
                insertion compare left subtree of the node is empty, does not continue to empty to the left comparison

For the following tree, if you want to insert the 20 node, perform the following steps:

Code:

      // 插入元素,给用户使用
      BinarySearchTree.prototype.insert = function(key){
        // 1、创建新结点
        var newNode = new Node(key);

        // 2、判断是否为空树
        // 2.1如果是空树,则让根的属性指向新结点
        if (this.root == null){
          this.root = newNode;
        }else{// 2.2如果不是非空树,则寻找合适的位置插入
          this.insertNode(this.root, newNode);
        }
      }

      // 用于内部使用
      BinarySearchTree.prototype.insertNode = function(node, newNode){

        // 1、比较结点与新结点的值大小
        // 1.1如果结点的值比新结点的值小
        if (node.key < newNode.key) {
          // 比较结点的右子树为空时插入,不为空时继续往下比较
          if (node.rchild == null) {
            node.rchild = newNode;
          }else{
            this.insetNode(node.rchild, newNode);
          }
        }else{ // 1.2 如果结点的值比新结点的值大
          // 比较结点的左子树为空时插入,不为空时继续往下比较
          if (node.lchild == null) {
            node.lchild = newNode;
          }else{
            this.insertNode(node.lchild, newNode);
          }
        }
      }

Code test

    // 测试
    // 1、创建BinarySearchTree
    var bst = new BinarySearchTree();

    // 2、插入数据
    bst.insert(18);
    bst.insert(4);
    bst.insert(2);
    bst.insert(7);
    bst.insert(30);
    bst.insert(24);
    bst.insert(20);

    // 3、输出结构
    console.log(bst);

          ——》

Third, the traversal method of the binary tree

The traversal of the binary search tree is the traversal of the binary tree. If you want to traverse each node, you must traverse in a certain order.

There are three common methods for binary trees:

  • Preorder traversal
  • In-order traversal
  • Post-order traversal

Another uncommon traversal is hierarchical traversal, which is to traverse from top to bottom from left to right according to each level of the tree.

1. First-order traversal

Operation definition:

If the binary tree is empty, no operation; otherwise

(1) Visit the root node;

(2) Traverse the left subtree first;

(3) Traverse the right subtree first.

                             

Code:

      // 先序遍历
      BinarySearchTree.prototype.preOrderTraversal = function(fun){
        // 让preOrderTraversalNode去实现递归操作
        this.preOrderTraversalNode(this.root, fun)
      }

      // 内部使用
      BinarySearchTree.prototype.preOrderTraversalNode = function(node, fun){

        if (node) {
          // 1、打印当前结点
          fun(node.key);

          // 2、遍历所有的左子树;
          this.preOrderTraversalNode(node.lchild, fun);

          // 3、遍历所有的右子树。
          this.preOrderTraversalNode(node.rchild, fun);
        }
      }

Test code

// 测试
    // 1、创建BinarySearchTree
    var bst = new BinarySearchTree();

    // 2、插入数据
    bst.insert(18);
    bst.insert(4);
    bst.insert(2);
    bst.insert(7);
    bst.insert(30);
    bst.insert(24);
    bst.insert(20);

    // 3、先序遍历
    var result = '';
    bst.preOrderTraversal(function (key){
      result += key + ' ';
    })
    console.log(result); //18 4 2 7 30 24 20 

2. In-order traversal

Operation definition:

If the binary tree is empty, no operation; otherwise

(1) In order to traverse the left subtree;

(2) Visit the root node;

(3) In order to traverse the right subtree.

                             

Code:

      // 中序遍历
      BinarySearchTree.prototype.midOrderTraversal = function(fun){
        // 让midOrderTraversalNode去实现递归操作
        this.midOrderTraversal(this.root, fun)
      }

      // 内部使用
      BinarySearchTree.prototype.midOrderTraversalNode = function(node, fun){

        if (node) {

          // 1、遍历所有的左子树;
          this.midOrderTraversalNode(node.lchild, fun);

          // 2、打印当前结点
          fun(node.key);

          // 3、遍历所有的右子树。
          this.midOrderTraversalNode(node.rchild, fun);
        }
      }

Test code

// 测试
    // 1、创建BinarySearchTree
    var bst = new BinarySearchTree();

    // 2、插入数据
    bst.insert(18);
    bst.insert(4);
    bst.insert(2);
    bst.insert(7);
    bst.insert(30);
    bst.insert(24);
    bst.insert(20);

    // 3、中序遍历
    var result = '';
    bst.midOrderTraversal(function (key){
      result += key + ' ';
    })
    console.log(result); //2 4 7 18 20 24 30 

3. Post-order traversal

Operation definition:

If the binary tree is empty, no operation; otherwise

(1) Traverse the left subtree in post-order;

(2) Traverse the right subtree in post-order.

(3) Visit the root node;

                             

Code:

      // 后序遍历
      BinarySearchTree.prototype.postOrderTraversal = function(fun){
        // 让postOrderTraversalNode去实现递归操作
        this.postOrderTraversalNode(this.root, fun)
      }

      // 内部使用
      BinarySearchTree.prototype.postOrderTraversalNode = function(node, fun){

        if (node) {

          // 1、遍历所有的左子树;
          this.postOrderTraversalNode(node.lchild, fun);

          // 2、遍历所有的右子树。
          this.postOrderTraversalNode(node.rchild, fun);

          // 3、打印当前结点
          fun(node.key);
        }
      }

Test code

// 测试
    // 1、创建BinarySearchTree
    var bst = new BinarySearchTree();

    // 2、插入数据
    bst.insert(18);
    bst.insert(4);
    bst.insert(2);
    bst.insert(7);
    bst.insert(30);
    bst.insert(24);
    bst.insert(20);

    // 3、先序遍历
    var result1 = '';
    bst.preOrderTraversal(function (key){
      result1 += key + ' ';
    })
    console.log(result1); //18 4 2 7 30 24 20 

    // 4、中序遍历
    var result2 = '';
    bst.midOrderTraversal(function (key){
      result2 += key + ' ';
    })
    console.log(result2); //2 4 7 18 20 24 30 


    // 5、后序遍历
    var result3 = '';
    bst.postOrderTraversal(function (key){
      result3 += key + ' ';
    })
    console.log(result3); //2 7 4 20 24 30 18 

Fourth, the binary search tree finds the best value

If the tree is empty, there is no maximum value,

If the tree is not empty

  • It has to start from the root node and right find the last is the most great value.
  • Has to start from the root left to find the last is the most small value.

Code:

      // 最大值
      BinarySearchTree.prototype.max = function(){
        var node = this.root;

        var key = null;
        while(node){
          key = node.key;
          node = node.rchild;
        }
        return key;
      }

      // 最小值
      BinarySearchTree.prototype.min = function(){
        var node = this.root;

        var key = null;
        while(node){
          key = node.key;
          node = node.lchild;
        }
        return key;
      }

Five, binary search tree to find a key

Finding the key is very similar to the search when inserting an item. Here we use a loop to write the code.

      // 查找某个key
      BinarySearchTree.prototype.searchKey = function(key){
        var node = this.root;

        while(node != null){
          if (node.key < key) {
            node = node.rchild;
          }else if (node.key > key) { 
            node = node.lchild;
          }else{
            return true;
          }
        }
        return false;
      }

 

Six, the binary search tree delete node

I mentioned a point before that it is very complicated to perform the node deletion operation of the binary search tree, because there are many situations, then we will analyze it bit by bit. The operation is not difficult to understand, and the most important thing is to consider it well. For the structure of the binary tree, we divide the number of sub-nodes connected by the deleted node into three cases

Case 1: The deleted node has no child nodes (that is, it is a leaf node).

Case 2: The deleted node has a child node.

Situation 3: The deleted node has two child nodes.

Specific steps:

1. Find the node to be deleted.

  • 1.1 Define variables and save information.

current saves the root node;

The parent record deletes the parent node of the node;

isLeftChild determines whether the deleted node is the left child node of the parent node.

var current = this.root; // 保存根结点
var parent = null;   // 记录删除结点的父节点
var isLeftChild = true;   //判断删除的结点是否是该父结点的左子结点

 

  • 1.2 Find the node to be deleted, and exit if the last node has been found.
// 1.2 查找要删除的结点
while(current.key != key){
  parent = current;
  if (key < current.key) {
    isLeftChild = true;
    current = current.lchild;
  }else{
    isLeftChild = false;
    current = current.rchild;
  }

  // 已经找到最后一个结点还没有找到就退出
  if (current == null) return false;
}

 

2. Find the node and delete the node according to the corresponding situation.

  • 2.1 Case 1: The deleted node is a leaf node (that is, there are no child nodes).

① If the tree has only one root node, just remove it.

② If the tree has multiple nodes and the leaf nodes are deleted, the pointer field can be made empty according to the parent node of the node to be deleted.

// 2.1删除的结点是叶子结点(即没有子结点)
if (current.lchild == null && current.rchild == null) {
  if (current == this.root) {
    this.root = null;
  } else if (isLeftChild) {
    parent.lchild = null;
  } else {
    parent.rchild = null;
  }
}

 

  • 2.2 Case 2: The deleted node has a child node.

① The deleted node is the root node. Point this.root to a child node of the node to be deleted.

② The deleted node is not found to be the root node. There are four specific situations.

else if (current.rchild == null) {
  if (current == this.root) {
    this.root = current.lchild;
  } else if (isLeftChild) {
    parent.lchild = current.lchild;
  } else {
    parent.rchild = current.lchild;
  }
} else if (current.lchild == null) {
  if (current == this.root) {
    this.root = current.rchild;
  } else if (isLeftChild) {
    parent.lchild = current.rchild;
  } else {
    parent.rchild = current.rchild;
  }
}

 

  • 2.3 Case 3: The deleted node has two child nodes.

If the node we delete has two child nodes, it is possible that there are still nodes in the child nodes, then we need to find a node to replace the deleted node.

We can find the one that is a little larger than the deleted node, or the one that is a little smaller than the deleted node, that is, the replacement node that is closest to the deleted node.

Then it must:

A little bit smaller than the deleted node must be the maximum value ( predecessor ) of the left subtree of the deleted node .

The one slightly larger than the deleted node must be the minimum value ( successor ) of the right subtree of the deleted node .

Here we use the method of subsequent search and replacement of nodes

① The deleted node is the root node, point this.root to the node to be replaced, and the parent node of the replaced node is connected to the right node.

② The deleted node is not found to be the root node.

// 2.3删除的结点两个子结点
else {
  // 1、寻找后继
  var replaceNode = this.getReplaceNode(current);

  // 2、判断是否是根结点
  if (current == this.root) {
    this.root = replaceNode;
  } else if (isLeftChild) {
    parent.lchild = replaceNode;
  } else {
    parent.rchild = replaceNode;
  }

  // 3、将替代的结点的左子树  = 删除结点的左子树
  replaceNode.lchild = current.lchild;
}
// 找后继的方法
BinarySearchTree.prototype.getReplaceNode = function (delNode) {

  // 1、定义变量,来保存找到的后继
  var replaceNode = delNode; // 替代的结点
  var previous = delNode.rchild;  //删除结点的右结点
  var replaceParent = delNode;

  // 2、循环查找
  while(previous != null){
    replaceParent = replaceNode;
    replaceNode = previous;
    previous = previous.lchild;
  }

  // 3、判断找到的替换结点(后继)是否就是delNode的rchild结点,如果是则填补结点
  if (replaceNode != delNode.rchild) {
    // 后继结点必定是小于两个子结点的结点,将后继结点的父结点的左结点指向替代结点的右结点,连接操作。
    replaceParent.lchild = replaceNode.rchild;
    // 替换
    replaceNode.rchild = delNode.rchild;
  }
  return replaceNode;
}

 

Code test:

    // 测试
    // 1、创建BinarySearchTree
    var bst = new BinarySearchTree();

    // 2、插入数据
    bst.insert(18);
    bst.insert(4);
    bst.insert(2);
    bst.insert(7);
    bst.insert(30);
    bst.insert(24);
    bst.insert(20);

    // 3、先序遍历
    var result1 = '';
    bst.preOrderTraversal(function (key){
      result1 += key + ' ';
    })
    console.log('先序遍历:' + result1); //18 4 2 7 30 24 20 

 

Delete 18

    // 6、删除操作
    // 删除根结点18
    bst.remove(18);

    // 先序遍历
    var result4 = '';
    bst.preOrderTraversal(function (key){
      result4 += key + ' ';
    })
    console.log('先序遍历为18 4 2 7 30 24 20 ,删除结点18后得到先序遍历结果:' + result4); 
    //先序遍历为18 4 2 7 30 24 20 ,删除结点18后得到先序遍历结果:20 4 2 7 30 24 

    // 插入数据
    bst.insert(26);
    bst.insert(21);
    bst.insert(25);
    bst.insert(27);

    //  先序遍历
    var result5 = '';
    bst.preOrderTraversal(function (key){
      result5 += key + ' ';
    })
    console.log('在先序为20 4 2 7 30 24 的二叉树搜索树中插入26、21、25、27,得到先序遍历结果:' + result5); 
    //在先序为20 4 2 7 30 24 的二叉树搜索树中插入26、27、28、29,得到先序遍历结果:20 4 2 7 30 24 21 26 25 27 

Delete 24

    // 删除结点24
    bst.remove(24);

    // 先序遍历
    var result6 = '';
    bst.preOrderTraversal(function (key){
      result6 += key + ' ';
    })
    console.log('先序遍历为20 4 2 7 30 24 21 26 25 27 ,删除结点24后得到先序遍历结果:' + result6); 
    //先序遍历为20 4 2 7 30 24 21 26 25 27 ,删除结点24后得到先序遍历结果:20 4 2 7 30 25 21 26 27 

Full print

Seven, complete code

<!DOCTYPE html>
<html>
<head>
  <meta charset="utf-8">
  <title>二叉搜索树的相关方法</title>
</head>
<body>
  <script type="text/javascript">
    function BinarySearchTree(){

      // 新结点创建的构造函数
      function Node(key){
        this.key = key;
        this.lchild = null;
        this.rchild = null;
      }

      // 保存根的属性
      this.root = null;

      // 创建关于二叉搜索树的相关操作方法

      // 插入元素,给用户使用
      BinarySearchTree.prototype.insert = function(key){
        // 1、创建新结点
        var newNode = new Node(key);

        // 2、判断是否为空树
        // 2.1如果是空树,则让根的属性指向新结点
        if (this.root == null){
          this.root = newNode;
        }else{// 2.2如果不是非空树,则寻找合适的位置插入
          this.insertNode(this.root, newNode);
        }
      }

      // 用于内部使用
      BinarySearchTree.prototype.insertNode = function(node, newNode){

        // 1、比较结点与新结点的值大小
        // 1.1如果结点的值比新结点的值小
        if (node.key < newNode.key) {
          // 比较结点的右子树为空时插入,不为空时继续往下比较
          if (node.rchild == null) {
            node.rchild = newNode;
          }else{
            this.insertNode(node.rchild, newNode);
          }
        }else{ // 1.2 如果结点的值比新结点的值大
          // 比较结点的左子树为空时插入,不为空时继续往下比较
          if (node.lchild == null) {
            node.lchild = newNode;
          }else{
            this.insertNode(node.lchild, newNode);
          }
        }
      }

      // 先序遍历
      BinarySearchTree.prototype.preOrderTraversal = function(fun){
        // 让preOrderTraversalNode去实现递归操作
        this.preOrderTraversalNode(this.root, fun)
      }

      // 内部使用
      BinarySearchTree.prototype.preOrderTraversalNode = function(node, fun){

        if (node) {
          // 1、打印当前结点
          fun(node.key);

          // 2、遍历所有的左子树;
          this.preOrderTraversalNode(node.lchild, fun);

          // 3、遍历所有的右子树。
          this.preOrderTraversalNode(node.rchild, fun);
        }
      }

      // 中序遍历
      BinarySearchTree.prototype.midOrderTraversal = function(fun){
        // 让midOrderTraversalNode去实现递归操作
        this.midOrderTraversalNode(this.root, fun)
      }

      // 内部使用
      BinarySearchTree.prototype.midOrderTraversalNode = function(node, fun){

        if (node) {

          // 1、遍历所有的左子树;
          this.midOrderTraversalNode(node.lchild, fun);

          // 2、打印当前结点
          fun(node.key);

          // 3、遍历所有的右子树。
          this.midOrderTraversalNode(node.rchild, fun);
        }
      }

      // 后序遍历
      BinarySearchTree.prototype.postOrderTraversal = function(fun){
        // 让postOrderTraversalNode去实现递归操作
        this.postOrderTraversalNode(this.root, fun)
      }

      // 内部使用
      BinarySearchTree.prototype.postOrderTraversalNode = function(node, fun){

        if (node) {

          // 1、遍历所有的左子树;
          this.postOrderTraversalNode(node.lchild, fun);

          // 2、遍历所有的右子树。
          this.postOrderTraversalNode(node.rchild, fun);

          // 3、打印当前结点
          fun(node.key);
        }
      }

      // 最大值
      BinarySearchTree.prototype.max = function(){
        var node = this.root;

        var key = null;
        while(node){
          key = node.key;
          node = node.rchild;
        }
        return key;
      }

      // 最小值
      BinarySearchTree.prototype.min = function(){
        var node = this.root;

        var key = null;
        while(node){
          key = node.key;
          node = node.lchild;
        }
        return key;
      }

      // 查找某个key
      BinarySearchTree.prototype.searchKey = function(key){
        var node = this.root;

        while(node != null){
          if (node.key < key) {
            node = node.rchild;
          }else if (node.key > key) { 
            node = node.lchild;
          }else{
            return true;
          }
        }
        return false;
      }

      // 删除某个结点
      BinarySearchTree.prototype.remove = function(key){
        // 1、寻找要删除的结点
        // 1.1 定义变量,保存信息
        var current = this.root; // 保存根结点
        var parent = null;   // 记录删除结点的父节点
        var isLeftChild = true;   //判断删除的结点是否是该父结点的左子结点

        // 1.2 查找要删除的结点
        while(current.key != key){
          parent = current;
          if (key < current.key) {
            isLeftChild = true;
            current = current.lchild;
          }else{
            isLeftChild = false;
            current = current.rchild;
          }

          // 已经找到最后一个结点还没有找到就退出
          if (current == null) return false;
        }

        // 2、根据对应的情况删除结点
        // 2.1删除的结点是叶子结点(即没有子结点)
        if (current.lchild == null && current.rchild == null) {
          if (current == this.root) {
            this.root = null;
          } else if (isLeftChild) {
            parent.lchild = null;
          } else {
            parent.rchild = null;
          }
        }

        // 2.2删除的结点有一个子结点
        else if (current.rchild == null) {
          if (current == this.root) {
            this.root = current.lchild;
          } else if (isLeftChild) {
            parent.lchild = current.lchild;
          } else {
            parent.rchild = current.lchild;
          }
        } else if (current.lchild == null) {
          if (current == this.root) {
            this.root = current.rchild;
          } else if (isLeftChild) {
            parent.lchild = current.rchild;
          } else {
            parent.rchild = current.rchild;
          }
        }

        // 2.3删除的结点两个子结点
        else {
          // 1、寻找后继
          var replaceNode = this.getReplaceNode(current);

          // 2、判断是否是根结点
          if (current == this.root) {
            this.root = replaceNode;
          } else if (isLeftChild) {
            parent.lchild = replaceNode;
          } else {
            parent.rchild = replaceNode;
          }

          // 3、将替代的结点的左子树  = 删除结点的左子树
          replaceNode.lchild = current.lchild;
        }
      }

      // 找后继的方法
      BinarySearchTree.prototype.getReplaceNode = function (delNode) {

        // 1、定义变量,来保存找到的后继
        var replaceNode = delNode; // 替代的结点
        var previous = delNode.rchild;  //删除结点的右结点
        var replaceParent = delNode;

        // 2、循环查找
        while(previous != null){
          replaceParent = replaceNode;
          replaceNode = previous;
          previous = previous.lchild;
        }

        // 3、判断找到的替换结点(后继)是否就是delNode的rchild结点,如果是则填补结点
        if (replaceNode != delNode.rchild) {
          // 后继结点必定是小于两个子结点的结点,将后继结点的父结点的左结点指向替代结点的右结点,连接操作。
          replaceParent.lchild = replaceNode.rchild;
          // 替换
          replaceNode.rchild = delNode.rchild;
        }
        return replaceNode;
      }
    }

    // 测试
    // 1、创建BinarySearchTree
    var bst = new BinarySearchTree();

    // 2、插入数据
    bst.insert(18);
    bst.insert(4);
    bst.insert(2);
    bst.insert(7);
    bst.insert(30);
    bst.insert(24);
    bst.insert(20);

    // 3、先序遍历
    var result1 = '';
    bst.preOrderTraversal(function (key){
      result1 += key + ' ';
    })
    console.log('先序遍历:' + result1); //18 4 2 7 30 24 20 

    // 4、中序遍历
    var result2 = '';
    bst.midOrderTraversal(function (key){
      result2 += key + ' ';
    })
    console.log('中序遍历:' + result2); //2 4 7 18 20 24 30 


    // 5、后序遍历
    var result3 = '';
    bst.postOrderTraversal(function (key){
      result3 += key + ' ';
    })
    console.log('后序遍历:' + result3); //2 7 4 20 24 30 18 


    console.log('最大值是' + bst.max());
    console.log('最小值是' + bst.min());
    console.log('是否存在24?' + bst.searchKey(24)); //true
    console.log('是否存在30?' + bst.searchKey(30)); //true
    console.log('是否存在5?' + bst.searchKey(5)); //false
    console.log('是否存在8?' + bst.searchKey(8)); //false
    console.log('是否存在500?' + bst.searchKey(500)); //false


    // 6、删除操作
    // 删除根结点18
    bst.remove(18);

    // 先序遍历
    var result4 = '';
    bst.preOrderTraversal(function (key){
      result4 += key + ' ';
    })
    console.log('先序遍历为18 4 2 7 30 24 20 ,删除结点18后得到先序遍历结果:' + result4); 
    //先序遍历为18 4 2 7 30 24 20 ,删除结点18后得到先序遍历结果:20 4 2 7 30 24 

    // 插入数据
    bst.insert(26);
    bst.insert(21);
    bst.insert(25);
    bst.insert(27);

    // 先序遍历
    var result5 = '';
    bst.preOrderTraversal(function (key){
      result5 += key + ' ';
    })
    console.log('在先序为20 4 2 7 30 24 的二叉树搜索树中插入26、21、25、27,得到先序遍历结果:' + result5); 
    //在先序为20 4 2 7 30 24 的二叉树搜索树中插入26、27、28、29,得到先序遍历结果:20 4 2 7 30 24 21 26 25 27 

    // 删除结点24
    bst.remove(24);

    // 先序遍历
    var result6 = '';
    bst.preOrderTraversal(function (key){
      result6 += key + ' ';
    })
    console.log('先序遍历为20 4 2 7 30 24 21 26 25 27 ,删除结点24后得到先序遍历结果:' + result6); 
    //先序遍历为20 4 2 7 30 24 21 26 25 27 ,删除结点24后得到先序遍历结果:20 4 2 7 30 25 21 26 27 
  </script>

</body>
</html>

 

Guess you like

Origin blog.csdn.net/weixin_42339197/article/details/99845315