红黑树性质
- 性质1:节点是红色或黑色
- 性质2:根节点是黑色的
- 性质3:每个红色节点的两个子节点都是黑色(从每个叶子到根的所有路径上不能有两个连续的红色节点)
- 性质4:从任何一节点到其他每个叶子的所有路径都包含相同数目的黑色节点
性质看不懂,来看234树转换为红黑树
234树转红黑树示意图:
从上图你能看出红色节点是与黑色节点是同级的,又与黑球组合一起实现234树中的多个数据项,这也是性质四中为啥只看黑色节点的原因
红色节点可以看做是虚拟节点,当与其他节点组合才会组成234树中的多项数据
性质三种不能有连续的红色节点与红色节点下两个黑色节点。你看一个234树中数据项有三个,而红色与其他节点组合成为234树中的数据项,要是有两个连续红色节点,那数据项里怎吗弄?
说白就是通过红色节点来实现234树中的多叉与单个节点中存在多个数据值,这些都是逻辑上实现的,物理上它还是二叉树,红黑树就是利用234树永远是平衡的这个特点
总结
红黑树就是普普通通的二叉平衡树,只是节点的意义不一样罢了
二叉平衡树的实现代码
class Node {
constructor(value) {
this.value = value;
this.left = null;
this.right = null;
}
}
const node9 = new Node('9');
const node8 = new Node('8');
const node7 = new Node('7');
const node6 = new Node('6');
const node5 = new Node('5');
const node4 = new Node('4');
node9.left = node6;
node6.left = node5;
node5.left = node4;
node6.right = node7;
node7.right = node8;
/**
* 利用单旋将二叉不平衡树变为二叉平衡树
* @param {*} root 跟节点
* @returns 返回平衡之后的根节点
*/
function change(root) {
if (isBalanceTree(root)) return root; // 返回平衡之后的根节点
// 走到这说明不是二叉平衡树
// 判断平不平衡需要从下往上判断
// 判断对二叉树进行平衡操作要按照后序遍历的顺序
// 后序遍历 先左子树 后右子树 最后自己
// 为何进行后序遍历,当根节点不是二叉平衡树,可能对子树进行单旋操作就成为二叉平衡树
if (root.left != null) root.left = change(root.left); // change函数会返回平衡之后的跟节点,故需要接收返回的的根节点
if (root.right != null) root.right = change(root.right);// change函数会返回平衡之后的跟节点,故需要接收返回的的根节点
// 获取深度
let leftTree = getDeep(root.left);
let rightTree = getDeep(root.right);
// 判断那里不符合二叉平衡树的要求
if (Math.abs(leftTree - rightTree) < 2) return root;
if (leftTree < rightTree) { //左浅 右深 左单旋
// 获取变化分支的深度
let changeTree = getDeep(root.right.left)
// 获取不变分支的深度
let noChangeTree = getDeep(root.right.right)
// 当变化分支的深度大于不变化的分支时需要右左双旋
if (changeTree > noChangeTree) {
//将右子树进行右单旋
root.right = rightRotate(root.right)
}
// 生成左单旋后的二叉树
let newRoot = leftRotate(root)
// 将其生成后的新二叉树的左子树进行判断
newRoot.left = change(newRoot.left)
// 新二叉树的左子树会发生改变,因此将再次判断整个二叉树
newRoot = change(newRoot)
return newRoot;
} else if (leftTree > rightTree) { //左深 右浅 右单旋
// 获取变化分支的深度
let changeTree = getDeep(root.left.right)
// 获取不变分支的深度
let noChangeTree = getDeep(root.left.left)
// 当变化分支的深度大于不变化分支的深度将进行左右单旋
if (changeTree > noChangeTree) {
// 将其左子树进行左单旋
root.left = leftRotate(root.left)
}
let newRoot = rightRotate(root);
// 将其生成后的新二叉树的右子树进行判断
newRoot.right = change(newRoot.right)
// 新二叉树的右子树会发生改变,因此将再次判断整个二叉树
newRoot = change(newRoot)
return newRoot
}
return root
}
/**
* 判断是否为二叉平衡树
* 平衡二叉的满足条件
* 根节点的左子树与右子树的高度差不能超过1
* 这棵二叉树的每个子树的符合第一条
* @param {*} root 根节点
* @returns Boolean
*/
function isBalanceTree(root) {
if (!root) return true;
// 获取左右子树的深度
let left = getDeep(root.left);
let right = getDeep(root.right);
// 当左子树与右子树的深度差大于1说明不是二叉平衡树
if (Math.abs(left - right) > 1) return false;
// 遍历这个二叉树的每一个子树
else return isBalanceTree(root.left) && isBalanceTree(root.right);
}
/**
* 获取二叉树的深度
* @param {*} root 节点
* @returns Number 返回左右子树中最深的层数
*/
function getDeep(root) {
if (!root) return 0;
let left = getDeep(root.left);
let right = getDeep(root.right);
return Math.max(left, right) + 1;
}
/**
* 左单旋
* 旋转节点:不平衡的节点为旋转节点
* 新根:旋转节点的右子树的根节点
* @param {*} root 旋转节点
* @returns 新的根节点
*/
function leftRotate(root) {
// 找到新根
let newNode = root.right;
// 找到变化分支
let changeNode = newNode.left;
// 当前旋转节点的右孩子为变化分支
root.right = changeNode;
// 新根的左孩子为旋转节点
newNode.left = root;
// 返回新的根节点
return newNode;
}
/**
* 右单旋
* 旋转节点:不平衡的节点为旋转节点
* 新根:旋转节点的左子树的根节点
* @param {*} root 旋转节点
* @returns 新的根节点
*/
function rightRotate(root) {
// 找到新根
let newNode = root.left;
// 找到变化分支
let changeNode = newNode.right;
// 当前旋转节点的左孩子为变化分支
root.left = changeNode
// 新根的右孩子为旋转节点
newNode.right = root;
// 返回新的根节点
return newNode;
}
/**
* 判断是否为平衡二叉树
* 平衡二叉的满足条件
* 根节点的左子树与右子树的高度差不能超过1
* 这棵二叉树的每个子树的符合第一条
* @param {*} root 节点
* @returns Boolean
*/
function isBalanceTree(root) {
if (!root) return true;
let letTree = getDeep(root.left)
let rightTree = getDeep(root.right)
if (Math.abs(letTree - rightTree) > 1) return false
else return isBalanceTree(root.left) && isBalanceTree(root.right);
}
// 测试右右双旋
let root = change(node9);
console.log(isBalanceTree(root))
红黑树参考资料
参考资料1
参考资料2