JS数据结构(10)—— 二叉搜索树的实现

JS数据结构(10)—— 二叉搜索树

1.二叉搜索树(BST,Binary Search Tree)是什么?

想要知道什么是二叉搜索树,先得了解什么是树结构,什么是二叉树,可以在我的另外一篇博客 树结构,二叉树 来了解。

二叉搜索树,也叫二叉查找树二叉排序树

二叉搜索树的特性:

  • 非空左子树的所有键值小于其根节点的键值。
  • 非空右子树的所有键值大于其根节点的键值。
  • 左、右子树本身也是二叉搜索树。

二叉搜索树的特点就是相对较小的值总是保存在左节点上,相对较大的值总是保存在右节点上。
基于这个特点,使得二叉搜索树的查找效率非常高。
其实二叉搜索树的查找思想就是二分查找,查找所需要的最大次数是二叉搜索树的深度。

2.二叉搜索树的封装
2.1 代码解析
  1. 我们封装一个构造函数BinarySearchTree用来表示二叉搜索树

  2. 二叉搜索树只需要一个属性Root,因为其他节点都可以通过Root来找到

  3. 我们还需要封装一个Node类用于保存每个节点

  4. Node类中包含三个属性,左子树,右子树和key

  5. 二叉搜索树的常见操作:
    (1)insert(key):向树中插入一个新的key
    (2)search(key):在树中查找一个key
    (3)remove(key):从树中移除某个key
    (4)preOrderTraverse:通过先序遍历遍历树
    (5)inOrderTraverse:通过中序遍历遍历树
    (6)postOrderTraverse:通过后序遍历遍历树
    (7)mix:返回树中最小的key
    (8)max:返回树中最大的key

  6. 遍历:
    树的遍历是指访问树的每个节点,但是树和线性结构不太一样,线性结构我们通常从前到后或者从后到前遍历,但是树不一样。
    二叉树的遍历常见的有:

    • 先序遍历:先访问根节点,再先序遍历其左子树,最后先序遍历其右子树
    • 中序遍历:先先序遍历根节点的左子树,再访问根节点,最后先序遍历根节点的右子树
    • 后序遍历:先先序遍历根节点的左子树,再先序遍历根节点的右子树,最后访问根节点

    测试:
    按顺序插入:9 8 18 4 6 57 2 10
    生成的树是:
    在这里插入图片描述
    手工计算:

    • 先序遍历 9 8 4 2 6 18 10 57
    • 中序遍历 2 4 6 8 9 10 18 57
    • 后序遍历 2 6 4 8 10 57 18 9
  7. 删除
    二叉搜索树的删除是最复杂的一个操作,删除节点要先查找到该节点,找到之后还需要考虑三种情况:

    • 该节点没有叶节点
    • 该节点有一个叶节点
    • 该节点有两个叶节点
2.2 代码实现
function BinarySearchTree () {
  // 属性
  this.root = null;
  // Node节点类
  function Node(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
  
  // 操作
  // insert(key):向树中插入一个新的key  给用户调用的方法
  BinarySearchTree.prototype.insert = function(key) {
    // 创建新节点
    var newNode = new Node(key);
    // 判断根节点是否为空
    if(this.root == null)
      this.root = newNode;
    else {
      this.insertNode(this.root, newNode);
    }
  }
  // insertNode(node, newNode):插入的递归方法
  BinarySearchTree.prototype.insertNode = function(node, newNode) {
    if(newNode.key < node.key) {
      // 向左查找
      if(node.left == null) {
        node.left = newNode;
      } else {
        this.insertNode(node.left, newNode);
      }
    } else {
      // 向右查找
      if(node.right == null) {
        node.right = newNode;
      } else {
        this.insertNode(node.right, newNode);
      }
    }
  }
  // search(key):在树中查找一个key
  BinarySearchTree.prototype.search = function(key) {
    var node = this.root;
    while(node) {
      if(node.key == key) {
        return true;
      } else if(node.key < key) {
        node = node.right;
      } else {
        node = node.left;
      }
    }
    return false;
  }

  // 遍历
  // preOrderTraverse:通过先序遍历遍历树
  BinarySearchTree.prototype.preOrderTraverse = function(callback) {
    console.log('先序遍历:');
    this.preOrderTraverseNode(this.root, callback);
  }
  // preOrderTraverseNode:先序遍历的递归函数
  BinarySearchTree.prototype.preOrderTraverseNode = function(node, callback) {
    if(node) {
      callback(node.key);
      this.preOrderTraverseNode(node.left, callback);
      this.preOrderTraverseNode(node.right, callback);
    }
  }

  // 中序遍历
  // inOrderTraverse:通过中序遍历遍历树
  BinarySearchTree.prototype.inOrderTraverse = function(callback) {
    console.log('中序遍历:');
    this.inOrderTraverseNode(this.root, callback);
  }
  // inOrderTraverseNode:中序遍历的递归函数
  BinarySearchTree.prototype.inOrderTraverseNode = function(node, callback) {
    if(node) {
      this.inOrderTraverseNode(node.left, callback);
      callback(node.key);
      this.inOrderTraverseNode(node.right, callback);
    }
  }
  
  // 后序遍历
  // postOrderTraverse:通过后序遍历遍历树
  BinarySearchTree.prototype.postOrderTraverse = function(callback) {
    console.log('后序遍历:');
    this.postOrderTraverseNode(this.root, callback);
  }
  // postOrderTraverseNode:后序遍历的递归函数
  BinarySearchTree.prototype.postOrderTraverseNode = function(node, callback) {
    if(node) {
      this.postOrderTraverseNode(node.left, callback);
      this.postOrderTraverseNode(node.right, callback);
      callback(node.key);
    }
  }
  // getRoot():返回树的根节点
  BinarySearchTree.prototype.getRoot = function() {
    return this.root;
  }

  // mix():返回树中最小的key
  BinarySearchTree.prototype.mix = function() {
    var node = this.root;
    if(this.root == null) {
      return null;
    }
    if(node) {
      while(node && node.left) {
        node = node.left;
      }
    }
    return node.key;
  }
  // max:返回树中最大的key
  BinarySearchTree.prototype.max = function() {
    var node = this.root;
    if(this.root == null) {
      return null;
    }
    if(node) {
      while(node && node.right) {
        node = node.right;
      }
    }
    return node.key;
  }
  
  // remove(key):从树中移除某个key
  BinarySearchTree.prototype.remove = function(key) {
    // 先判断是否存在key,如果不存在,则直接return 0
    if(!this.search(key)) return false;
    this.removeNode(this.root, key);
  }
  BinarySearchTree.prototype.removeNode = function(node, key) {
    if(node == null)
      return null;
    if(node.key > key) {
      node.left = this.removeNode(node.left, key);
    } else if(node.key < key) {
      node.right = this.removeNode(node.right, key);
    } else {   // 找到要删除的节点
      // 要删除的节点是叶子节点,直接让该节点为null
      if(node.left == null && node.right == null) {
        node = null;
      } 
      // 要删除的节点有一个子节点,则把要删除的节点替换为它的子节点
      else if(node.left == null) {
        node = node.right;
      } else if(node.right == null) {
        node = node.left;
      } 
      // 要删除的节点有两个子节点,这时可以用左子树里的最大节点来替换要删除的节点,也可以用右子树里的最小节点来替换要删除的节点
      // 这里我采用左子树的最大节点来替换
      else {
        // 找到要删除的节点的左子树里最大的值
        var LeftMax = this.getLeftMax(node.left);
        // 让要删除的节点的key等于刚才找到的左子树里最大值的key,保证了要删除的节点只改变了key,左右子树不变
        node.key = LeftMax.key;
        // 让要删除的节点的左子树里删除它的最大值
        node.left = this.removeNode(node.left, LeftMax.key);
      }
    }
    return node;
  }
  BinarySearchTree.prototype.getLeftMax = function(node) {
    if(node) {
      while(node && node.right) {
        node = node.right;
      }
      return node;
    }
    return null;
  }
}

// 测试:
var bst = new BinarySearchTree();
bst.insert(9);
bst.insert(8);
bst.insert(18);
bst.insert(4);
bst.insert(6);
bst.insert(57);
bst.insert(2);
bst.insert(10);
var result = "";
bst.preOrderTraverse(function(key) {
  result += key + "  ";
});
console.log(result);
// 先序遍历: 
// 9  8  4  2  6  18  10  57  
result = "";
bst.inOrderTraverse(function(key) {
  result += key + "  ";
});
console.log(result);
// 中序遍历:
// 2  4  6  8  9  10  18  57  
result = "";
bst.postOrderTraverse(function(key) {
  result += key + "  ";
});
console.log(result);
// 后序遍历:
// 2  6  4  8  10  57  18  9

console.log('min:' + bst.mix(bst.getRoot())); 
// min:2
console.log('max:' + bst.max(bst.getRoot()));
// max:57
console.log(bst.search(18));    // true
console.log(bst.search(99));	// false

bst.remove(4);

result = "";
bst.inOrderTraverse(function(key) {
  result += key + "  ";
});
console.log(result);
// 中序遍历:
// 2  6  8  9  10  18  57  

(如果有什么错误,欢迎大佬在评论区指正,thank you~)

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值