红黑树的构建
let sentinel = {color: 'black', value: null}; let root = sentinel; function data(data) { return typeof data === 'object' && data !== null ? data.key : data; } function Node({value, left = sentinel, right = sentinel, parent = null, color}) { Object.assign(this, { left, right, parent, value, color, }); }; function black(node) { node.color = 'black'; } function red(node) { node.color = 'red'; } function isBlack(node) { return node.color === 'black'; } function isRed(node) { return node.color === 'red'; } function rbtree_insert(node) { // root为空说明当前插入第一个节点 if (root === sentinel) { root = node; return; } let current = root; // 先插入 while(current) { // 比当前节点大则往右走,如果到达最后一个右孩子,则设置当前节点为他的右孩子 if (data(node.value) > data(current.value)) { if (current.right === sentinel) { current.right = node; node.parent = current; break; } current = current.right; } else { // 同上 if (current.left === sentinel) { current.left = node; node.parent = current; break; } current = current.left; } } // 插入成功后,标记插入节点为红色,尽量满足各种约束 red(node); /* 父节点是红色的话,需要开始调整 node节点是根节点的孩子时,不进入while。 node不是根节点并且父节点是红色,说明父节点还有父节点, 即父节点不是根节点,因为根节点必须是黑色 */ while(node !== root && isRed(node.parent)) { // node的父节点是node父节点的父节点的左孩子 if (node.parent === node.parent.parent.left) { /* node的父节点是红色,说明祖父节点是黑色,node的叔叔节点是红色, 则只需要做下面一步调整,即node的父节点和叔叔好节点变红,祖父节点变红。 因为以祖父节点为根的这棵子树中,调整前,父节点和叔叔节点共享 祖父节点的黑色,调整后,祖父节点为红色,但是父节点和叔叔节点为黑色了, 不影响以祖父节点为根节点的子树的黑高度。因为祖父节点变红了,如果祖父节点的父节点 也是红色,则又回到了一开始的问题,即跳到while。 1 当node.parent.parent是根节点的时候,直接退出循环,但这是他是红色的,所以循环外需要 2 把他强制变黑。如果node.parent.parent是根节点的孩子,则isRed(node.parent)不会成立,也会退出循环。 */ if (isRed(node.parent.parent.right)) { black(node.parent); black(node.parent.parent.right); red(node.parent.parent); node = node.parent.parent; } else { /* 如果叔叔节点是黑色,这时候的结构是, 父节点是红色,祖父是黑色,叔叔是黑色。node是红色。 调整理论也是类似上面,即父节点变黑,祖父节点变红,叔叔变黑。 但是因为调整前,以祖父节点为根的子树中,父节点和叔叔共享祖父的一个黑节点, 现在祖父变红,父节点变黑,对祖父节点到父节点这条路径的黑高度没影响,但是对 祖父到叔叔这条路径有影响,少了一个黑高度。这时候就要右旋转(见下面右旋转函数的解释)。 右旋导致父节点上升,替换祖父节点的位置,祖父下降成为父节点的右孩子,从而导致父节点原来的 右孩子(如果有的话)没有地方挂载。所以右旋转前,要先把以父节点为根的子树,左旋转(见下面左旋函数的结束)一下。 因为父节点的右孩子比父节点大,所以右孩子会替换父节点成为该子树的新根节点。而父节点变成右孩子的左孩子。 这时候,因为右孩子上升为根节点,并且左孩子是原来的父节点。会导致右孩子原来的左孩子(有的话)没有地方挂载。 所以这个左孩子会成为父节点的右孩子。我们会发现,这样左旋或右旋,是不是破坏红黑数的规则的。 */ if (node === node.parent.right) { rbtreeLeftRotate(node.parent); } black(node.parent); black(node.parent.parent.right); red(node.parent.parent); rbtreeRightRotate(node.parent.parent); } } else { // 类似上面的解释 if (isRed(node.parent.parent.left)) { black(node.parent); black(node.parent.parent.left); red(node.parent.parent); node = node.parent.parent; } else { if (node === node.parent.left) { rbtreeRightRotate(node.parent); } black(node.parent); black(node.parent.parent.left); red(node.parent.parent); rbtreeLeftRotate(node.parent.parent); } } } // 根节点可能会被变红,这里强制变黑 black(root); } // 左旋转,node成为右节点的左孩子,node右孩子的左孩子成为node的右孩子 // function rbtreeLeftRotate(node) { // 保存右孩子的地址,因为node的右指针即将被修改 let right = node.right; // 第一步挂载右孩子的节点到父节点 // 右孩子的左孩子成为node的右孩子 node.right = right.left; // 可能没有左孩子,有的话他的父指针指向node if (node.right !== sentinel) { node.right.parent = node; } // 第二步,替换node成为子树根节点 // 到达根,则right变成新的根节点,否则修改node的父节点对应指针的指向,即替换node的位置, if (node === root) { root = right; } else if (node === node.parent.left){ node.parent.left = right; } else { node.parent.right = right; } // 更新右孩子的父节点,不再是node,而是他的父节点 right.parent = node.parent; // 第三步,建立右孩子和node的连接 right.left = node; node.parent = right; } // 右旋转,node成为左节点的右孩子,node左孩子的右孩子成为node的左孩子 function rbtreeRightRotate(node) { let left = node.left; node.left = left.right; if (node.left !== sentinel) { node.left.parent = node; } if (node === root) { root = left; } else if (node === node.parent.left) { node.parent.left = left; } else { node.parent.right = right; } left.parent = node.parent; left.right = node; node.parent = left; } (function test(){ const data = []; let i = 0; while(i < 5) { rbtree_insert(new Node({value: i++})); } console.log(root) }())