【JS数据结构与算法】二叉搜索树的常见方法

目录

一、引入二叉树的结构封装

二、插入insert(key)

三、二叉树的遍历方式

1、先序遍历

2、中序遍历

3、后序遍历

四、二叉搜索树查找最值

五、二叉搜索树查找某个Key

六、二叉搜索树删除结点

七、完整代码


一、引入二叉树的结构封装

function BinarySearchTree(){

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

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

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

}

二、插入insert(key)

思路:

创建insert方法

    1、创建新结点

    2、判断是否为空树
         2.1如果是空树,则让根的属性指向新结点
         2.2如果不是非空树,则寻找合适的位置插入

再创建一个用于内部使用的方法insertNode()
1、比较结点与新结点的值大小
       1.1如果结点的值比新结点的值
               比较结点的右子树为空时插入,不为空时继续往比较
       1.2 如果结点的值比新结点的值
                比较结点的左子树为空时插入,不为空时继续往比较

对于以下这棵树,想要插入20这个结点,执行以下步骤:

代码实现:

      // 插入元素,给用户使用
      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);
          }
        }
      }

代码测试

    // 测试
    // 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);

          ——》

三、二叉树的遍历方式

二叉搜索树的遍历即是二叉树的遍历,想要遍历每一个结点,就必须按照某种特定的顺序进行遍历。

二叉树常见的方法有三种:

  • 先序遍历
  • 中序遍历
  • 后序遍历

还有一种不常见的遍历是层次遍历,就是按照树的每一层从上到下从左开始往右遍历。

1、先序遍历

操作定义:

若二叉树为空,则空操作;否则

(1)访问根结点;

(2)先序遍历左子树;

(3)先序遍历右子树。

                             

代码实现:

      // 先序遍历
      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);
        }
      }

测试代码

// 测试
    // 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、中序遍历

操作定义:

若二叉树为空,则空操作;否则

(1)中序遍历左子树;

(2)访问根结点;

(3)中序遍历右子树。

                             

代码实现:

      // 中序遍历
      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);
        }
      }

测试代码

// 测试
    // 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、后序遍历

操作定义:

若二叉树为空,则空操作;否则

(1)后序遍历左子树;

(2)后序遍历右子树。

(3)访问根结点;

                             

代码实现:

      // 后序遍历
      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);
        }
      }

测试代码

// 测试
    // 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 

四、二叉搜索树查找最值

如果树为空,则无最值,

如果树不为空

  • 从根结点开始一直向找到最后即为最值。
  • 从根结点开始一直向找到最后即为最值。

代码实现:

      // 最大值
      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

查找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;
      }

六、二叉搜索树删除结点

之前提起一点,要进行二叉搜索树的结点删除操作是非常复杂的,因为情况比较多,那我们就一点一点来进行分析,操作是不难理解的,最主要的就是考虑周全。针对二叉树的结构,我们以删除结点相连的子结点个数分三种情况

情况一:删除的结点无子结点(即是叶子结点)。

情况二:删除的结点有一个子结点。

情况三:删除的结点有两个子结点。

具体步骤:

1、寻找要删除的结点。

  • 1.1 定义变量,保存信息。

current 保存根结点;

parent 记录删除结点的父节点;

isLeftChild 判断删除的结点是否是该父结点的左子结点。

var current = this.root; // 保存根结点
var parent = null;   // 记录删除结点的父节点
var isLeftChild = true;   //判断删除的结点是否是该父结点的左子结点
  • 1.2 查找要删除的结点,已经找到最后一个结点还没有找到就退出。
// 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 情况一:删除的结点是叶子结点(即没有子结点)。

①  如果该树只有一个根结点,将之移除即可。

②  如果该树有多个结点,又是针对叶子结点进行删除的话,就可以根据要删除的结点的父结点进行指针域变空即可。

// 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 情况二:删除的结点是有一个子结点。

①  删除的结点是根结点,把 this.root 指向一个要删除的结点的子结点。

②  查找到删除的结点不是根结点。有四种具体的情况。

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 情况三:删除的结点是有两个子结点。

如果我们删除的结点是有两个子结点的,有可能子结点中还有结点,那我们就需要找一个结点来替换删除的结点。

我们可以找比删除结点大一点点的,也可以找比删除结点小一点点的,也就是最接近删除结点的替代结点。

那就必定:

比删除结点小一点点的,一定是删除结点左子树的最大值(前驱

比删除结点大一点点的,一定是删除结点右子树的最小值(后继

这里使用后继查找替换结点的方式

①  删除的结点是根结点,把 this.root 指向要替换的结点,被替换的结点的父结点与右结点相连。

②  查找到删除的结点不是根结点。

// 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 

删除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 

删除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 

完整打印

七、完整代码

<!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>

猜你喜欢

转载自blog.csdn.net/weixin_42339197/article/details/99845315