不平衡的二叉排序树,可以通过旋转变成平衡二叉树。旋转的方式有左单旋、右单旋、左右双旋、右左双旋、左左双旋和右右双旋。
在进行旋转时,我自定义了一些词语来帮助理解:
旋转节点:不平衡需要旋转的节点
新根:旋转之后成为根节点的节点
变化分支:父节点发生变化的分支
不变分支:父节点没有变化的分支
1.左单旋
如果左子树高度浅,右子树高度深,可以进行左单旋。
旋转节点:当前不平衡的节点(5)
新根:旋转节点的右子节点(7)
变化分支:旋转节点的右节点的左子树(以6为根节点的左子树)
不变分支:旋转节点的右节点的右子树(以9为根节点的右子树)
变换逻辑:
1.找到新根
2.找到变化分支
3.旋转节点的右节点设置变化分支
4.新根的左节点设为旋转节点
5.返回新根节点
//左单旋
function leftRotate(root) {
//找到新根
let newRoot = root.right;
//找到变化分支
let changeBranch = root.right.left;
root.right = changeBranch;
newRoot.left = root;
return newRoot;
}
2.右单旋
如果右子树高度浅,左子树高度深,可以进行右单旋。
旋转节点:不平衡的节点(9)
新根:旋转节点的左子节点(6)
变化分支:旋转节点的左节点的右子树(以7为根节点的右子树)
不变分支:旋转节点的左节点的左子树(以5为根节点的左子树)
变换逻辑:
1.找到新根
2.找到变化分支
3.旋转节点的左节点设为变化分支
4.新根的右节点设为旋转节点
5.返回新根节点
//右单选
function rightRotate(root) {
//找到新根
let newRoot = root.left;
//找到变化分支
let changeBranch = root.left.right;
root.left = changeBranch ;
newRoot.right = root;
return newRoot;
}
3.左右双旋和右左双旋
左单旋和右单旋无法将所有不平衡的二叉排序树转换为平衡二叉树。当变化分支是唯一的最深分支时,左单旋和右单旋都不能得到平衡二叉树,需要进行双旋。
注意:平衡二叉树的转换中一般使用后序遍历。
4.左右双旋
当对某个节点进行右单旋时,则该节点为旋转节点。如果变化分支是唯一最深分支,那么我们需要对新根先进行左单旋,然后对旋转节点进行右单旋,这样的旋转叫做左右双旋。
节点9不是平衡节点,左深右浅,需要对它进行右单旋。
当以9为旋转节点进行右单旋时,变化分支根节点为8,是唯一最深分支。需要对新根6先进行左单旋,然后再对节点9进行右单旋,最终达到平衡。
5.右左双旋
当对某个节点进行左单旋时,则该节点为旋转节点。如果变化分支是唯一最深分支,那么我们需要对新根先进行右单旋,然后对旋转节点进行左单旋,这样的旋转叫做右左双旋。
//左右双旋&&右左双旋
function changeRotate(root) {
if (isBlance(root)) {
return root;
}
if (root.left) {
root.left = changeRotate(root.left);
}
if (root.right) {
root.right = changeRotate(root.right);
}
let leftDeep = getDeep(root.left);
let rightDeep = getDeep(root.right);
if (Math.abs(leftDeep - rightDeep) < 2) {
//左子树与右子树深度差不超过1,是平衡的
return root;
} else if (leftDeep > rightDeep) {
//不平衡,左深右浅,需要进行右旋
//不变分支的深度
let noChangeBranchDeep = getDeep(root.left.left);
//变化分支的深度
let changeBranchDeep = getDeep(root.left.right);
if (changeBranchDeep > noChangeBranchDeep) {
//变化分支的深度为最高深度,先对新根左旋
root.left = leftRotate(root.left);
}
return rightRotate(root);
} else {
//不平衡,右深左前,需要左旋
let changeBranchDeep = getDeep(root.right.left);
let noChangeBranchDeep = getDeep(root.right.right);
if (changeBranchDeep > noChangeBranchDeep) {
root.right = rightRotate(root.right);
}
return leftRotate(root);
}
}
let arr2 = [9, 6, 8, 5, 7];
let root2 = createSearchTree(arr2);
//console.log(root2);
console.log(isBlance(root2));
let newRoot2 = changeRotate(root2);
//console.log(newRoot2);
console.log(isBlance(newRoot2));
6.右右双旋和左左双旋
当变化分支不是唯一最深分支时,单旋后可能依旧不平衡,这时需要进行右右双旋或者左左双旋。
function change(root) {
//返回平衡之后的根节点
if (isBalance(root)) {
return root;
}
if (root.left != null) root.left = change(root.left);
if (root.right != null) root.right = change(root.right);
var leftDeep = getDeep(root.left);
var rightDeep = getDeep(root.right);
if (Math.abs(leftDeep - rightDeep) < 2) {
return root;
} else if (leftDeep > rightDeep) {
//不平衡,左边深,需要右旋
var changeTreeDeep = getDeep(root.left.right);
var noChangeTreeDeep = getDeep(root.left.left);
if (changeTreeDeep > noChangeTreeDeep) {
root.left = leftRotate(root.left);
}
var newRoot = rightRotate(root);
//右旋后,新根的右子树可能依旧不平衡
newRoot.right = change(newRoot.right);
//改变右子树后,可能根节点又不平衡了, 再次对根节点进行判断,
//如果平衡则直接返回,不平衡则继续进行改变
newRoot = change(newRoot);
return newRoot;
} else {
//不平衡,右边深,需要左旋
var changeTreeDeep = getDeep(root.right.left);
var noChangeTreeDeep = getDeep(root.right.right);
if (changeTreeDeep > noChangeTreeDeep) {
root.right = rightRotate(root.right);
}
var newRoot = leftRotate(root);
newRoot.left = change(newRoot.left);
newRoot = change(newRoot);
return newRoot;
}
}
let arr3 = [9, 6, 7, 5, 4, 8];
let root3 = createSearchTree(arr3);
console.log(root3);
let newRoot3 = change(root3);
console.log(newRoot3);
console.log(isBlance(newRoot3));
完结!!
注意:
该文章代码部分使用的方法 isBlance,createSearchTree,具体实现参考我的另一篇文章:【JavaScript】二叉排序树