二叉树、AVL树、红黑树
根节点: 位于数顶部的节点;
内部节点: 存在子元素的节点;
外部节点: 没有子元素的节点;
树的深度: 节点的深度取决于它的祖先节点的数量;
树的高度: 所有节点深度的最大值;
森林:多颗子树;
二叉树:最多只能有两个子节点,一个是左侧子节点,另一个是右侧子节点;
二叉搜索树(BST): 左侧节点存储比父节点小的值,右侧存储比有节点大的值;
对于树的操作,递归很nice,用递归则必须要清楚退出条件;
树的节点通过指针(引用)来表示节点之间的关系,一个指向左子节点,另一个指向右子节点,默认是指向null的,表示没有子节点,使用element来存储值
class Node {
constructor() {
this.element= element; //值
this.left = null; //左指针
this.right = null; //右指针
}
}
声明一个变量来控制tree的根节点root
class BinarySearchTree {
constructor() {
this.root = null; //Node类型的根节点
}
}
使用递归来操作树不要太香,
首先进行越界判断,如果根节点为空则表示树为空,传入的值可直接作为根节点插入,它的左右指针会自动设置为null,如果不为空这向下递归调用insertNode方法;
insertNode方法接收两个参数,当前节点以及要插入的值value;首先比较当前节点的值与value,由于二叉树左边的值始终小于当前节点,判断value的值是否小于当前的值,且当前值的左指针不为null,则继续向左查找,直到找到指针为空的节点,将value的节点至于该节点的左侧,如果value的值大于当前节点的值,同理往右查找放置节点;
insert(value) {
if (this.root == null) {
this.root = new Node(value); //创建一个节点存储元素
} else {
this.insertNode(this.root, value); //递归插入key
}
}
insertNode(node, value) {
//如果value比当前的值element小
if (node.element > value) {
//左子节点不为空
if (node.left == null) {
//连接左子节点
node.left = new Node(element)
} else {
//不为空继续向下遍历
this.insertNode(node.left, element);
}
} else {
if (node.right == null) {
node.right = new Node(element)
} else {
this.insertNode(node.right, element);
}
}
}
这里需要传入一个回调函数callback用来处理数据,回调函数接收遍历的值;
树的遍历方法有三种,先序,中序,后序遍历;
先序遍历:以优先于后代节点的顺序访问每一个节点,它会先访问节点本身,然后在访问节点的左侧,左侧访问完再访问右侧;
中序遍历:从最小到最大的顺序访问所有节点,先访问左子节点,再对每个根节点执行回调函数,最后访问右节点;
后序遍历:先访问所有的左右节点,在访问节点本身;
traverse(callBack) {
//传入根节点,以及cb回调函数
this.traverseNode(this.root, callBack);
}
//先序遍历
traverseNode(node, callBack) {
//越界判断
if (node != null) {
callBack(node.element);
this.inOrderTraverseNode(node.left, callBack);
this.inOrderTraverseNode(node.right, callBack);
}
}
//中序遍历
traverseNode(node, callBack) {
//越界判断
if (node != null) {
this.inOrderTraverseNode(node.left, callBack);
callBack(node.element);
this.inOrderTraverseNode(node.right, callBack);
}
}
//后序遍历
traverseNode(node, callBack) {
//越界判断
if (node != null) {
this.inOrderTraverseNode(node.left, callBack);
this.inOrderTraverseNode(node.right, callBack);
callBack(node.element);
}
}
//callback函数
const cb = function(value) {
console.log(value)
}
在二叉树中,最小值总是在树最后一层的最左边,最大值则在最右边;
要想获取树的最小值,只需从根节点一直往左遍历,直到找到node的左节点为null的节点,即目标节点,获取最大值同理
/**
* @获取最小值
* */
min() {
return this.minNode(this.root);
}
minNode(node) {
let min = node; //标记当前节点
//遍历tree,如果当前节点不为空,或left指针不为空
while (min != null && min.left != null) {
min = min.left; //继续查找
}
//退出循环即找到最后一个节点,直接返回出去供调用者接收
return min;
}
/**
* @获取最大值
* */
max() {
return this.maxNode(this.root);
}
maxNode(node) {
let max = node;
while (max != null && max.right != null) {
max = max.right;
}
return max;
}
search方法接收一个值value,在树种查找value,如果存在则返回true,不存在返回false;
当遍历到节点的指针为null表示已经找到了树的最后一层。此时返回false没有找到;
search(value) {
return this.searchNode(this.root, value);
}
searchNode(node, value) {
//如果当前节点为空(传进来的node.left == null)
if (node == null) {
//查找完毕依旧没有找到,返回false
return false;
}
//对比当前节点的值,是否从左或右查找
if (node.element > value) {
//如果遍历到最后一层,node.left会传入null
return this.searchNode(node.left, value);
} else if (node.element< value) {
return this.searchNode(node.right, value);
} else {
//返回每一次查找的结果,一旦找到直接返回true
return true;
}
}
移除操作需要画图看得清…
首先定义递归退出条件,当遍历完整颗树依然没有发现value,则返回null;
递归调用removeNode,依次从根节点左右查找,
remove(value) {
this.root = this.removeNode(this.root, value);
}
removeNode(node, value) {
//未找到要移除的节点 返回null
if (node == null) {
return null;
}
//compare
if (node.element> value) {
//当前节点的值比目标值大, 继续往左找
node.left = this.removeNode(node.left, value);
return node;
} else if (node.element< value) {
//当前节点的key比目标key小, 继续往右找
node.right = this.removeNode(node.right, value);
return node;
} else {
//找到了 判断目标节点是否存在子节点
//无子节点
if (node.left == null && node.right == null) {
node = null;
return node;
}
//只有一侧存在节点
if (node.left == null) {
node = node.right;
return node;
} else if (node.right == null) {
node = node.left;
return node;
}
//同时存在左右子节点
//找到右子节点树的最小节点,更新当前节点的key为最小节点的key
const aux = this.minNode(node.right);
node.key = aux.key;
//移除最小节点
node.right = this.removeNode(node.right, aux.element);
return node;
}
}
接下来直接写注释了,不画图很绕
//计数器,用来处理平衡因子的数值
const BalanceFactor = {
UNBALANCED_RIGHT: 1,
SLIGHTLY_UNBALANCED_RIGHT: 2,
BALANCED: 3,
SLIGHTLY_UNBALANCED_LEFT: 4,
UNBALANCED_LEFT: 5
}
class AVLTree extends BinarySearchTree {
constructor() {
this.root = null;
}
}
左子树的最大深度L 与 右子树的最大深度R 获取最大深度满足公式:
`max(L, R) + 1;
getNodeHeight(node) {
//递归 子节点为null退出
if (node == null) {
return 0;
}
return Math.max(
//左右两边比较,取最大值
this.getNodeHeight(node.left),
this.getNodeHeight(node.right)
) + 1;
}
获取一个节点node的平衡因子,计算左右节点树的高度差
getBalanceFactor(node) {
const heightDifference = this.getNodeHeight(node.left) - this.getNodeHeight(node.right);
switch(heightDifference) {
case -2:
return BalanceFactor.UNBALANCED_RIGHT; //右节点多于左节点 1
case -1:
return BalanceFactor.SLIGHTLY_UNBALANCED_RIGHT; // 2
case 1:
return BalanceFactor.SLIGHTLY_UNBALANCED_LEFT; //4
case 2:
return BalanceFactor.UNBALANCED_LEFT; // 5
default:
return BalanceFactor.BALANCED; // 3
}
}
/**
* @desc 左-左旋转 LL:向右的单旋转
* @param { root } 根节点
* @return { Node } 新的根节点
*/
rotationLL(node) {
const tmp = node.left; //标记root的左侧节点 (新的根节点)
node.left = tmp.right; //将root的右节点替换为左子节点的右节点
tmp.right = node; //将标记的右节点连接node
return tmp; //标记的节点作为根节点返回
}
/**
* @desc 右-右 RR: 向右的单旋转
* @param { root } 根节点
* @return { node } 新的根节点
*/
rotationRR(node) {
const temp = node.right;
node.right = temp.left;
temp.left = node;
return temp;
}
/**
* @desc 左-右 LR: 向右的双旋转 (左侧的高度大于右侧的高度)
* @param { root } 根节点
* @return { Node } 新的根节点
*/
rotationLR(node) {
node.left = this.rotationRR(node.left);
return this.rotationLL(node);
}
/**
* @desc 右-左 RL: 向左的双循环 (右侧的高度大于左侧的高度)
* @param { root } 根节点
* @return { Node } 新的根节点
*/
rotationRL(node) {
node.right = this.rotationLL(node.right);
return this.rotationRR(node);
}
AVL树的插入操作与二叉树一致,不同在于,AVL树插入节点后需要检查树是否平衡,不平衡需要进行旋转操作
/**
* @param { key } 要插入的值
* @return { Node }
*/
insert(key) {
this.root = this.insertNode(this.root, key);
};
insertNode(node, key) {
//遍历插入节点
if (node == null) {
return new Node(key); //如果数空,直接返回要插入值的节点
} else if (node.element > key) {
node.left = this.insertNode(node.left, key);
} else if (node.element < key) {
node.right = this.insertNode(node.right, key);
} else {
return node;
}
const balanceFactor = this.getBalanceFactor(node); //计算左右节点的高度差
//如果左侧高
if (balanceFactor == BalanceFactor.UNBALANCED_LEFT) {
//判断 如果要插入的值小于左子节点,则说明往左插 左侧多了,进行左左单循环,右侧多了则进行左-右双循环
if (node.left.element > key) {
node = this.rotationLL(node);
} else {
return this.rotationLR(node);
}
};
//右侧高
if (balanceFactor = BalanceFactor,UNBALANCED_RIGHT) {
if (node.right.element < key) {
node = this.rotationRR(node);
} else {
return this.rotationRL(node);
}
}
return node;
}
移除操作与二叉树也是一样的,移除后检查树是否平衡
/**
* @desc 移除节点
* @param { Node }
* @return { Node }
*/
removeNode(node, key) {
node = super.removeNode(Node, key); //继承二叉树的移除操作的方法
if (node == null) {
return node;
};
//检查树是否平衡
//获取节点左右差值
const balanceFactor = this.getBalanceFactor(node);
// 如果左边多2
if (balanceFactor === BalanceFactor.UNBALANCED_LEFT) {
//让根节点的左子节点作为根节点继续计算差值
const balanceFactorLeft = this.getBalanceFactor(node.left);
//如果是左子节点的左侧多,则进行左-左单旋转
if (balanceFactorLeft === BalanceFactor.BALANCED || balanceFactorLeft === BalanceFactorSLIGHTLY_UNBALANCED_LEFT) {
return this.rotationLL(node);
}
// 如果右侧多,则进行左-右双循环
if (balanceFactorLeft === balanceFactor.SLIGHTLY_UNBALANCED_RIGHT) {
return this.rotationLR(node.left);
}
}
if (balanceFactor === BalanceFactor.UNBALANCED_RIGHT) {
const balanceFactorRight = this.getBalanceFactor(node.right);
if (balanceFactorRight === BalanceFactor.BALANCED || balanceFactorRight === BalanceFactorSLIGHTLY_UNBALANCED_RIGHT) {
return this.rotationLR(node);
}
if (balanceFactorRight === BalanceFactor.SLIGHTLY_UNBALANCED_LEFT) {
return this.rotationRL(node.right);
}
}
};
红黑树规则:
1.每个节点不是黑色就是红色
2. 树的根节点是黑的
3. 所有叶子节点都是黑色的
4. 如果一个节点是红的,那么它的两个子节点都是黑的
5. 不能有两个相邻的红节点,一个红节点不能有红的父节点或子节点
6. 从给定的节点到它的后代节点的所有路径包含相同数量的黑色节点
相对于二叉树,红黑树节点多了一个指向父节点的指针, 还有一个颜色属性,默认都为红色
class RedBlackNode {
constructor(val) {
this.val = val;
this.left = null;
this.right = null;
this.color = Colors.RED; //创建的节点默认为红色
this.parent = null; //指向父节点的引用
}
//验证是否为红节点,返回boolean
isRed() {
return this.color === Colors.RED;
}
}
class RedBlackTree {
constructor() {
this.root = null;
};
}
插入节点后,要进行两个操作:重新填色,旋转 (旋转操作使用AVL树的方法)
如果要插入节点的颜色与其父节点相同,则需要 依次向上修改
如果要插入节点父节点的颜色为红色,需要改变父节点,祖父节点和父节点的相邻节点
fixTreeProperties(node) {
while (node && node.parent && node.parent.color.isRed() && node.color !== Colors.BLACK) {
//标记父节点以及父节点的上一级节点
let parent = node.parent;
const grandParent = parent.parent;
//父节点是上一级节点的左子节点
if (grandParent && grandParent.left === parent) {
//标记其右节点
const uncleR = grandParent.right;
//如果目标节点的上一级节点都是红色的
if (uncleR && uncleR.color === Colors.RED) {
grandParent.color = Colors.RED;
parent.color = Colors.BLACK;
uncleR.color = Colors.BLACK;
node = grandParent; //将当前节点的引用指向parent,继续检查树是否有其他冲突;
} else {
//如果上一层节点颜色不相同,进行相对应的旋转操作
//当前节点在父节点的右侧
if(node === parent.right) {
this.rotationRR(parent);
node = parent;
parent = node.parent;
}
this.rotationLL(grandParent);
parent.color = Colors.BLACK;
grandParent.color = Colors.RED;
node = parent;
}
} else if (grandParent && grandParent.left === parent) {
//父节点是上一级节点的右子节点, 标记其左节点
const uncleL = grandParent.left;
if (uncleL && uncleL.color === Colors.RED) {
grandParent.color = Colors.RED;
parent.color = Colors.BLACK;
uncleL.color = Colors.BLACK;
node = grandParent;
} else {
if (node === parent.left) {
this.rotationLL(parent);
node = parent;
parent = node.parent;
} else if (node === parent.right) {
this.rotationRR(grandParent);
parent.color = Colors.BLACK;
grandParent.color = Colors.RED;
node = parent;
}
}
}
}
this.root.color = Colors.BLACK;
}
#####5.7.3:insert 红黑树的插入操作
/**
* @desc 插入操作
* @param { key } 传入值
* @return { void }
*/
insert(key) {
if (!this.root) {
//如果树空
this.root = new RedBlackNode(key); //定义一个node节点
//数的根节点是黑的
this.root.color = Colors.BLACK;
} else {
//如果树不为空,继续向下查找执行插入操作
const newNode = this.insertNode(this.root, key);
//验证红黑树规则是否满足
this.fixTreeProperties(newNode);
}
};
/**
* @des 插入节点
* @param { Node, Key } node节点对象, 要插入的值
* @return { Node } 返回插入值的节点
*/
insertNode(node, key) {
if (key < node.key) {
if (node.left == null) {
node.left = new RedBlackNode(key);
node.left.parent = node;
return node.left;
} else {
return this.insertNode(node.left, key);
}
}
if (key > node.key) {
if (node.right == null) {
node.right = new RedBlackNode(key);
node.right.parent = node;
return node.right;
} else {
return this.insertNode(node.right, key);
}
}
}