一、结构定义
public class BalanceTree {
//根节点
private Node root;
/**
* 静态内部类,定义节点。
*/
private static class Node {
//数据域
private int data;
//父节点
private Node parent;
//右节点
private Node right;
//左节点
private Node left;
//相对于根节点的高度,用于判断是否失衡
private int height;
public Node(int data, Node right, Node left, Node parent) {
this.data = data;
this.parent = parent;
this.right = right;
this.left = left;
}
}
}
平衡二叉树的结构定义和二叉搜索树类似,多了个高度。
注意:下面代码插入删除内容部分,请结合上一篇博文平衡二叉树的详解一起看,更利于理解。
二、插入
insert方法:
public void insert(int data) {
//插入节点
Node node = new Node(data, null, null, null);
//根节点为空,则把插入节点设为根节点
if (root == null) {
root = node;
//根节点高度是1
root.height = 1;
} else { //根节点不为空
//1.先插入节点
Node currentNode = root;
//循环遍历找到合适的插入位置
while (true) {
//如果当前节点等于data则为存在该节点,结束
if (currentNode.data == data) {
return;
} else if (data < currentNode.data) { //小于当前节点
//左节点为空则插入到该节点下的左节点
if (currentNode.left == null) {
currentNode.left = node;
node.parent = currentNode;
break;
} else {
currentNode = currentNode.left;
}
} else { //大于当前节点
//右节点为空则插入到该节点下的右节点
if (currentNode.right == null) {
currentNode.right = node;
node.parent = currentNode;
break;
} else {
currentNode = currentNode.right;
}
}
}
//插入时候设置高度
node.height = currentNode.height + 1;
//2.判断是否平衡
Node destroyedNode = isBalance(node);
if (destroyedNode != null) {
//3.进行平衡操作
insertBalance(node, destroyedNode);
}
}
}
insert大致流程:先找到合适的插入位置,然后进行插入,再判断是否失衡,失衡则进行平衡操作。
isBalance方法:
private Node isBalance(Node node) {
//被破坏节点
Node destroyedNode = null;
//当前节点,即为新插入的节点
Node currentNode = node;
//向上遍历,判断向上每一个节点是否失衡
while (currentNode != null) {
//拿到右节点的最大高度
int right = nodeMaxHeight(currentNode.right);
//如果高度为-1则表示该节点不存在右节点,最大高度应该为当前节点的高度
right = right == -1 ? currentNode.height : right;
//拿到左节点的最大高度
int left = nodeMaxHeight(currentNode.left);
//如果高度为-1则表示该节点不存在左节点,最大高度应该为当前节点的高度
left = left == -1 ? currentNode.height : left;
//左节点-右节点,如果高度差大于1,则为失衡。
if (Math.abs(right - left) > 1) {
destroyedNode = currentNode;
break;
}
currentNode = currentNode.parent;
}
//如果被破坏的节点
return destroyedNode;
}
isBalance方法是判断从该节点向上的节点是否有失衡,如果没有失衡,则返回NULL,失衡则返回被破坏节点。
nodeMaxHeight方法:
private int nodeMaxHeight(Node node) {
if (node != null) {
//当前节点的高度
int height = node.height;
//左节点的最大高度
int leftMaxHeight = nodeMaxHeight(node.left);
//右节点的最大高度
int rightMaxHeight = nodeMaxHeight(node.right);
//返回最大的一个高度值
return Math.max(Math.max(leftMaxHeight, rightMaxHeight), height);
}
return -1;
}
nodeMaxHeight方法是递归查找某个节点下最大的高度,最后返回一个高度值,如果该节点不存在,则返回-1。
insertBalance方法:
private void insertBalance(Node node, Node destroyedNode) {
//先进行类型判断,LL型,RR型,LR型或RL型,只需要判断2次即可
//如果当前节点小于被破坏节点且小于被破坏节点的左节点,则为LL型,需要右旋
if (node.data < destroyedNode.data && node.data < destroyedNode.left.data) {
rightRotate(destroyedNode);
} else if (node.data > destroyedNode.data && node.data > destroyedNode.right.data){
//如果当前节点大于被破坏节点且大于被破坏节点的右节点,则为RR型,需要左旋
leftRotate(destroyedNode);
} else if (node.data > destroyedNode.data && node.data < destroyedNode.right.data){
//如果当前节点大于被破坏节点且小于被破坏节点的右节点,则为RL型,
// 需要先对被破坏节点的右节点进行右旋然后对被破坏节点进行左旋
rightRotate(destroyedNode.right);
leftRotate(destroyedNode);
} else if (node.data < destroyedNode.data && node.data > destroyedNode.left.data) {
//如果当前节点小于被破坏节点且大于被破坏节点的左节点,则为LR型,
// 需要先对被破坏节点的左节点进行左旋然后对被破坏节点进行右旋
leftRotate(destroyedNode.left);
rightRotate(destroyedNode);
}
}
insertBalance方法为插入导致不平衡时的平衡调增,如果对此不理解,请与我的上篇博文平衡二叉树详解对着看。
rightRotate方法:
private void rightRotate(Node rotateNode) {
//旋转节点的父节点
Node parent = rotateNode.parent;
//判断旋转节点是右边还是左边
boolean isRight = isRight(rotateNode);
//左节点
Node left = rotateNode.left;
//右节点
Node right = rotateNode.right;
//转换节点,即旋转后作为旋转节点的子节点的节点
Node changeNode = left.right;
//左边节点高度减一
heightModify(left, false);
//右边节点高度加一
heightModify(right, true);
//如果旋转节点不为root节点,则把左节点设置到旋转节点的位置
if (rotateNode != root) {
if (isRight) {
parent.right = left;
} else {
parent.left = left;
}
} else { //如果旋转节点为root节点,则把左节点设为root
root = left;
}
left.parent = parent;
left.right = rotateNode;
//处理转换节点,把该节点放到旋转节点的左边
if (changeNode != null) {
changeNode.parent = rotateNode;
rotateNode.left = changeNode;
//转换节点高度要加1,因为旋转节点本身高度是不变的,但是在上面为了集体处理,高度减1了
//需要所以+1
heightModify(changeNode, true);
} else {
//如果旋转节点不存在,则把旋转节点的左节点设为null
rotateNode.left = null;
}
//设置旋转节点父节点为原来的左节点
rotateNode.parent = left;
//旋转节点高度+1
rotateNode.height += 1;
}
rightRotate方法即为右旋操作,如下图,代码中的被破坏节点为该图中的S节点,转换节点为该图中的E节点下的右节点,即下图中E and S标识的节点。
isRight方法:
private boolean isRight(Node node) {
if (node == null || node.parent == null) {
return false;
}
return node == node.parent.right ? true : false;
}
isRight方法主要是判断该节点是否是右节点。
heightModify方法:
private void heightModify(Node node, boolean isAdd) {
if (node != null) {
if (isAdd) {
node.height += 1;
} else {
node.height -= 1;
}
//递归增加左边高度
heightModify(node.left, isAdd);
//递归增加右边高度
heightModify(node.right, isAdd);
}
}
heightModify方法是为了方便的处理高度值,即某个节点下的所有高度+1或者-1(包括自己),isAdd为true则为高度+1,为false则为高度-1.
leftRotate方法:
private void leftRotate(Node rotateNode) {
//旋转节点的父节点
Node parent = rotateNode.parent;
//判断旋转节点是右边还是左边
boolean isRight = isRight(rotateNode);
//左节点
Node left = rotateNode.left;
//右节点
Node right = rotateNode.right;
//转换节点,即旋转后作为旋转节点的子节点的节点
Node changeNode = right.left;
//左边节点高度加一
heightModify(left, true);
//右边节点高度减一
heightModify(right, false);
//如果旋转节点不为root节点,则把右节点设置到旋转节点的位置
if (rotateNode != root) {
if (isRight) {
parent.right = right;
} else {
parent.left = right;
}
} else { //如果旋转节点为root节点,则把左节点设为root
root = right;
}
right.parent = parent;
right.left = rotateNode;
//处理转换节点,把该节点放到旋转节点的左边
if (changeNode != null) {
changeNode.parent = rotateNode;
rotateNode.right = changeNode;
//转换节点高度要加1,因为旋转节点本身高度是不变的,但是在上面为了集体处理,高度减1了
//需要所以+1
heightModify(changeNode, true);
} else {
//如果旋转节点不存在,则把旋转节点的右节点设为null
rotateNode.right = null;
}
//设置旋转节点父节点为原来的右节点
rotateNode.parent = right;
//旋转节点高度+1
rotateNode.height += 1;
}
leftRotate方法,即左旋,与右旋操作类似,具体流程如下图,代码中的被破坏节点为该图中的E节点,转换节点为该图中的S节点下的左节点,即下图中E and S标识的节点。
以上代码为插入操作代码。
三、删除
remove方法:
public boolean remove(int data) {
//找到要删除数据的节点
Node node = findNode(data);
if (root == null || node == null) {
return false;
}
//删除节点,返回一个向上追溯的起点
Node parent = deleteNode(node);
//判断是否平衡
Node destroyedNode = isBalance(parent);
if (destroyedNode != null) {
//删除平衡调整
deleteBalance(destroyedNode);
}
return true;
}
remove方法即为删除某个数据的节点,大致流程如下,先找到该节点,如果存在则进行过删除操作,删除之后判断是否平衡,如果不平衡则进行调整。
findNode方法:
private Node findNode(int data) {
Node currentNode = root;
while (currentNode != null) {
if (currentNode.data == data) {
return currentNode;
} else if (data < currentNode.data) {
currentNode = currentNode.left;
} else {
currentNode = currentNode.right;
}
}
return currentNode;
findNode方法根据对应的数据查找到对应的节点,从根节点向下遍历查找。
deleteNode方法:
private Node deleteNode(Node node) {
Node parent = null;
//判断该节点有几个子节点
int num = hasNodeNum(node);
switch (num) {
case 0:
//进行没有子节点的删除操作
parent = deleteNoneHasNode(node);
break;
case 1:
//进行有一个子节点的删除操作
parent = deleteHasOneNode(node);
break;
case 2:
//进行有两个个子节点的删除操作
parent = deleteHasTwoNode(node);
break;
default:
break;
}
return parent;
}
deleteNode方法即通过判断有几个子节点,从而进行对应的删除操作。
hasNodeNum方法:
private int hasNodeNum(Node node) {
if (node.right != null && node.left != null) {
return 2;
} else if (node.right != null || node.left != null) {
return 1;
} else {
return 0;
}
}
hasNodeNum方法,判断存在几个子节点。
deleteNoneHasNode方法:
private Node deleteNoneHasNode(Node node) {
Node parent = null;
//如果为根节点,根节点直接置null
if (node == root) {
root = null;
} else {
//当前节点的父节点
Node p = node.parent;
if (isRight(node)) {
p.right = null;
} else {
p.left = null;
}
parent = p;
}
return parent;
}
deleteNoneHasNode方法,删除不存在子节点的节点,比较简单,从父节点把该节点设为null即可。
deleteHasOneNode方法:
private Node deleteHasOneNode(Node node) {
//父节点
Node parent = null;
//如果为根节点,把该节点设为根节点,父节点设为null,树下高度-1
if (node == root) {
Node replaceNode = root.right == null ? root.left : root.right;
replaceNode.parent = null;
heightModify(replaceNode, false);
root = replaceNode;
} else { //用存在的子节点替换要删除的节点
//当前节点的父节点
Node p = node.parent;
Node replaceNode = null;
//找到替换当前节点的节点
if (node.left != null) {
replaceNode = node.left;
} else {
replaceNode = node.right;
}
//设置替代节点为父节点的左或右节点
if (isRight(node)) {
p.right = replaceNode;
} else {
p.left = replaceNode;
}
//更改替代节点的父节点为被删除节点的父节点
replaceNode.parent = p;
//高度减一
heightModify(replaceNode, false);
parent = p;
}
return parent;
}
deleteHasOneNode方法,删除存在一个子节点的节点方法,也比较简单,通过用存在的子节点替代被删除的节点即可。
deleteHasTwoNode方法:
private Node deleteHasTwoNode(Node node) {
Node parent;
//先找到替代节点,即右子树下最小的节点或左子树下最大的节点
Node replaceNode = findMin(node.right);
//设置返回被删除的真正父节点为替代节点的父节点,
parent = replaceNode.parent;
//替代节点的左节点必定为null,右节点未知,查找子节点数量
int num = hasNodeNum(replaceNode);
//删除替代节点
if (num == 0) {
deleteNoneHasNode(replaceNode);
} else {
deleteHasOneNode(replaceNode);
}
//把原来要删除的节点的值设置为替代节点的值
node.data = replaceNode.data;
return parent;
}
deleteHasTwoNode方法,删除存在两个子节点的节点方法,该方法需要找到后继节点,本文使用右子树下的最小节点来替代被删除的节点,注意此处部分代码的逻辑,本没有删除原来要删除的节点,而是把替代的节点删除了,最后把原来要删除节点的值改为替代节点的值即可;还有一个注意点,即该代码中的parent属性,它应该为替代节点的父节点。
注意:上面分别删除存在0,1,2个子节点的节点方法中,有一个parent属性,该属性其实为删除后向上追溯的起点,即递归向上查看每个节点是否平衡的起点。对于删除0,1个节点的来说,起点就是被删除节点的父节点,而对于被删除节点存在2个子节点的来说,起点应该为替代节点的父亲。
findMin方法:
private Node findMin(Node node) {
Node currentNode = node;
while (currentNode != null) {
Node left = currentNode.left;
if (left == null) {
break;
} else {
currentNode = left;
}
}
return currentNode;
}
findMin方法,即找到该节点中最小的,一直向左找,如果为空时,则当前节点是最小的。
deleteBalance方法:
private void deleteBalance(Node destroyedNode) {
//左节点的最大高度
int leftMaxHeight = nodeMaxHeight(destroyedNode.left);
//右节点的最大高度
int rightMaxHeight = nodeMaxHeight(destroyedNode.right);
//用于判断的节点
Node judgeNode;
//如果右边高度大于左边,则为R开头的旋转型
if(rightMaxHeight > leftMaxHeight) {
//设右节点为判断节点
judgeNode = destroyedNode.right;
//右节点的右边最大高度
int judgeNodeRightMaxHeight = nodeMaxHeight(judgeNode.right);
//右节点的左边最大高度
int judgeNodeLeftMaxHeight = nodeMaxHeight(judgeNode.left);
//删除左子树的节点则相当于在右子树插入节点
//如果右节点的高度大于等左节点
// 则为RR型,需要进行对被破坏节点进行左旋
if(judgeNodeRightMaxHeight >= judgeNodeLeftMaxHeight) {
leftRotate(destroyedNode);
}else {
//如果右节点的高度小于等左节点,则为RL型
//先对被破坏节点右节点进行右旋,然后再被被破坏节点进行左旋
rightRotate(destroyedNode.right);
leftRotate(destroyedNode);
}
}else { //如果左边高度大于右边,则为L开头的旋转型
//设左节点为判断节点
judgeNode = destroyedNode.left;
//右节点的右边最大高度
int judgeNodeRightMaxHeight = nodeMaxHeight(judgeNode.right);
//右节点的左边最大高度
int judgeNodeLeftMaxHeight = nodeMaxHeight(judgeNode.left);
//左边高,则相当于在右子树插入节点
//如果左节点的高度大于等右节点
// 则为LL型,需要进行对被破坏节点进行右旋
if(judgeNodeLeftMaxHeight >= judgeNodeRightMaxHeight) {
rightRotate(destroyedNode);
}else {
//如果左节点的高度小于等右节点,则为LR型
//先对被破坏节点左节点进行左旋,然后再被被破坏节点进行右旋
leftRotate(destroyedNode.left);
rightRotate(destroyedNode);
}
}
}
deleteBalance方法,删除操作的平衡调整,判断类型,然后进行旋转,此方法较为复杂,请结合上一篇博文参考,注意代码中的判断节点,如果左边高度大,则判断节点为被破坏节点的左子节点,如果右边高度大,则判断节点为被破坏节点的右子节点。
总结:
以上内容为平衡二叉树的代码部分,还有遍历代码没有写,因为遍历和二叉树的遍历一致,所以参考二叉搜索树的遍历即可,在提醒一句,二叉平衡树的添加和删除操作最好结合我的上一篇平衡二叉树内容详解一起看。