一、基本概念
- 根节点:二叉树的最顶端的节点;
- 分支结点:非根节点且有子节点;
- 叶子节点:最末端,没有子节点;
- 左右子树:左边的子节点为左子树,同理右子树;
- 树的深度:也称为树的高度,树中所有结点的层次最大值称为树的深度,即:层数,从0开始;
二、二叉排序树
二叉排序树(Binary Sort Tree),又称二叉查找树(Binary Search Tree),亦称二叉搜索树。
- 若左子树不空,则左子树上值小于它的根结点的值;
- 若右子树不空,则右子树上值大于它的根结点的值;
- 左、右子树也分别为二叉排序树;
- 没有键值相等的结点;
用以上规则,绘制出来的树形数据结构,即为二叉排序树。
三、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);
五、删除节点
删除原则
删除 节点后,不可违反二叉排序树的创建规则:左节点值 < 节点值 < 有节点值。
删除节点分四种情况:
- 删除没有子节点的节点;
- 删除只有左节点的节点;
- 删除只有右节点的节点;
- 删除既有左节点又有右节点的节点;
删除逻辑:
- 找到要删除的节点;
- 找到适合的节点,替换需删除的节点,分四种情况处理,逻辑见下代码注释 先处理前三种情况:
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);
删除节点的第四种情况
删除逻辑:
- 不破坏二叉搜索树是规则性,找到适合的节点替换需删除节点;
- 要找的节点在这棵树上;
- 这个节点要比被删除的左节点大;
- 这个节点要比被删除的右节点小;
删除逻辑白话:
- 需删除节点的右节点为根 的子树中 所有节点的集合中找到最小的;
- 被删除节点->右节点->一直往左找,找到最小 = 被删除节点的后继节点
//removeNode方法中添加代码:
// 找到最小值节点
const minNode = this.findMinNode(node.right);
// 最小值节点的值 赋给 被删除点的值,即完成替换
node.key = minNode.key;
// 替换后删除最小值节点
node.right = this.removeNode(node.right, minNode.key);
return node;
总结
- 二叉树在处理数据上有明显的性能优势;
- 对二叉排序树进行增删,不可违反二叉排序树的规则(左<中<右)