js实现二叉排序树

1,783 阅读3分钟

一、基本概念

  1. 根节点:二叉树的最顶端的节点;
  2. 分支结点:非根节点且有子节点;
  3. 叶子节点:最末端,没有子节点;
  4. 左右子树:左边的子节点为左子树,同理右子树;
  5. 树的深度:也称为树的高度,树中所有结点的层次最大值称为树的深度,即:层数,从0开始;

二、二叉排序树

二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。

  1. 若左子树不空,则左子树上值小于它的根结点的值;
  2. 若右子树不空,则右子树上值大于它的根结点的值;
  3. 左、右子树也分别为二叉排序树;
  4. 没有键值相等的结点;

用以上规则,绘制出来的树形数据结构,即为二叉排序树。

三、js创建二叉排序树

//创建节点类
class CreateNode {
  constructor(key) {
    this.key = key;
    this.left = null;
    this.right = null;
  }
};


//创建二叉树的类
class BinarySearchTree {
  constructor() {
    this.root = null;
  }
  //创建root or root插入子节点
  insert(key) {
    if (this.root) {
      this.insertNode(this.root, new CreateNode(key));
    } else {
      this.root = new CreateNode(key)
    }
  };
  //给节点插入子节点 (参数:接受两个节点) 
  insertNode(node, newNode) {
    if (node.key > newNode.key) {
      if (node.left) {
        this.insertNode(node.left, newNode);
      } else {
        node.left = newNode;
      }
    } else if (node.key < newNode.key) {
      if (node.right) {
        this.insertNode(node.right, newNode);
      } else {
        node.right = newNode;
      }
    };
  };

}


const data = [123,4,5,56,7,898,52,5132];
const tree = new BinarySearchTree();
data.forEach(item=>{
	tree.insert(item);
});

四、中序遍历、前序遍历、后序遍历、层次遍历

//中序遍历:左 中 右
  inOrderTraverce(cb){
    this.inOrderTraverceNodes(this.root,cb)
  }
  inOrderTraverceNodes(node,cb){
    if(node){
      this.inOrderTraverceNodes(node.left,cb);
      cb(node.key)
      this.inOrderTraverceNodes(node.right,cb);
    }
  }
  //前序遍历:中 左 右
  preOrderTraverce(cb){
    this.preOrderTraverceNodes(this.root,cb)
  }
  preOrderTraverceNodes(node,cb){
    if(node){
      cb(node.key)
      this.preOrderTraverceNodes(node.left,cb);
      this.preOrderTraverceNodes(node.right,cb);
    }
  }
  
  //后序遍历:左 右 中 
  afterOrderTraverce(cb){
    this.afterOrderTraverceNodes(this.root,cb)
  }
  afterOrderTraverceNodes(node,cb){
    if(node){
      this.afterOrderTraverceNodes(node.left,cb);
      this.afterOrderTraverceNodes(node.right,cb);
      cb(node.key)
    }
  }
  //层次遍历
  levelOrderTraverce(cb){ 
    const quene = [this.root]; 
    let node = null;
    while(quene.length){
      node = quene.shift()
      cb(node.key);
      if(node.left){
        quene.push(node.left);
      }
      if(node.right){
        quene.push(node.right);
      }
    };
  };
  
//使用遍历  
const data = [123,4,5,56,7,898,52,5132];
const tree = new BinarySearchTree();
data.forEach(item=>{
	tree.insert(item);
});

tree.afterOrderTraverce();

五、查找最大值,最小值,特定值

//查询最小值:找到最左边的节点
  findMin() {
    return this.min(this.root);
  }
  min(node) {
    if (node) {
      while (node.left) {
        node = node.left
      }
      return node.key;
    } else {
      return null;
    }
  }

  //查询最大值:找到最左边的节点
  findMax() {
    return this.max(this.root);
  }
  max(node) {
    if (node) {
      while (node.right) {
        node = node.right
      }
      return node.key;
    } else {
      return null;
    }
  }

  //查找特定值
  find(key) {
    return this.findKey(this.root, key);
  }
  findKey(node, key) {
    if (!node) {
      return false;
    }
    if (node.key < key) {
      return this.findKey(node.right, key);
    } else if (node.key > key) {
      return this.findKey(node.left, key);
    } else {
      return true
    }
  }
  
//使用查找  
const data = [123,4,5,56,7,898,52,5132];
const tree = new BinarySearchTree();
data.forEach(item=>{
	tree.insert(item);
});

tree.findMin();
tree.findMax();
tree.find(5);

五、删除节点

删除原则

删除 节点后,不可违反二叉排序树的创建规则:左节点值 < 节点值 < 有节点值。

删除节点分四种情况:
  1. 删除没有子节点的节点;
  2. 删除只有左节点的节点;
  3. 删除只有右节点的节点;
  4. 删除既有左节点又有右节点的节点;
删除逻辑:
  1. 找到要删除的节点;
  2. 找到适合的节点,替换需删除的节点,分四种情况处理,逻辑见下代码注释 先处理前三种情况:
  remove(key) {
    this.removeNode(this.root, key)
  }
  //找到最小值的节点
  findMinNode(node) {
    if (node) {
      while (node.left) {
        node = node.left
      }
      return node;
    } else {
      return null;
    }
  }
  //删除节点的方法
  removeNode(node, key) {
    if (!node) {
      return null
    }
    if (node.key > key) {
      node.left = this.removeNode(node.left, key);
      return node;
    } else if (node.key < key) {
      node.right = this.removeNode(node.right, key);
      return node;
    } else {
    
    //找到需要删除的节点后,分情况处理
      if (!node.left && !node.right) {
      //没有子节点的节点 => 直接将需删除的节点置为null
        node = null;
        return node;
      } else if (node.left && !node.right) {
      //删除只有左节点的节点 => 将左节点替换需删除节点
        node = node.left;
        return node;
      } else if (!node.left && node.right) {
      //删除只有右节点的节点 => 将右节点替换需删除节点
        node = node.right;
        return node;
      } else {
        //第四种,较复杂,下面单独分析
      }
    }
  }
  
  
  
//使用删除  
const data = [123,4,5,56,7,898,52,5132];
const tree = new BinarySearchTree();
data.forEach(item=>{
	tree.insert(item);
});

tree.removeNode(56);
删除节点的第四种情况

删除逻辑:

  1. 不破坏二叉搜索树是规则性,找到适合的节点替换需删除节点;
  2. 要找的节点在这棵树上;
  3. 这个节点要比被删除的左节点大;
  4. 这个节点要比被删除的右节点小;

删除逻辑白话:

  1. 需删除节点的右节点为根 的子树中 所有节点的集合中找到最小的;
  2. 被删除节点->右节点->一直往左找,找到最小 = 被删除节点的后继节点
//removeNode方法中添加代码:
// 找到最小值节点
const minNode = this.findMinNode(node.right);
// 最小值节点的值 赋给 被删除点的值,即完成替换
node.key = minNode.key;
// 替换后删除最小值节点
node.right = this.removeNode(node.right, minNode.key);
return node;

总结

  1. 二叉树在处理数据上有明显的性能优势;
  2. 对二叉排序树进行增删,不可违反二叉排序树的规则(左<中<右)