这一篇将分享我理解并整理的非平衡二叉树到平衡二叉树的原理及代码。
目录
对于非平衡二叉树不太理解的可以参照我的上一篇文章,https://blog.csdn.net/leq3915/article/details/81429644
非平衡二叉树转成平衡二叉树,只要在每次添加节点的时候,如果不平衡,将该树进行左旋转(逆时针)/右旋转(顺时针)即可。
一、左旋转
1.LL树
所谓LL树,如下的左图,13节点的左子节点深度是3,右子节点的深度是1,左子节点深度比右子节点深度大,所以不平衡节点在左子节点上,这是第一个L。
左子节点8的左子节点深度比右子节点深度大,这是第二个L。
LL树可以直接右旋转(顺时针),从图一旋转成图二即可。
2.LR树
LR树的定义同LL树一样,L表示节点13的左子节点深度比右子节点大,R表示节点8的右子节点深度比左子节点大。
LR树不能直接右旋转(顺时针),需要先将LR树旋转城LL树,如图一先旋转城图二,图二再右旋转城图三(图二旋转成图三的步骤,和上面的LL树相同)。
二、右旋转
3.RR树
第一个R表示节点13的右子节点深度比左子节点大,第二个R表示节点15的右子节点深度比左子节点大。
这种情况也是可以直接进行左旋转(逆时针),得到平衡二叉树。
4.RL树
同理,R表示13节点的右子节点深度比左子节点大,L表示节点18的左子节点深度比右子节点大。
这种情况需要先将节点18旋转城RR树(图一旋转城图二),然后再进行左旋转(图二旋转城图三),
总结一下:
LL树:可以直接右旋转。
LR树:需要先将左子节点左旋转(变成LL树),再将当前节点右旋转。
RR树:可以直接进行左旋转。
RL树:需要先将右子节点右旋转(变成RR树),再将当前节点左旋转。
惊人的相似啊,理解了之后,很简单,想不通为啥当时折磨了我两天多。(┬_┬)
好了,下面给出平衡二叉树的生成逻辑。其中MyNode.java类就不展示类,需要的小伙伴可以到我的上一篇文章copy过来。
BalanceTreeDemo.java
PS:有部分方法在非平衡二叉树demo里面也有,是一样的。
import java.util.ArrayList;
import java.util.List;
/**
* 二叉树demo(平衡)
* @author 刘
* @date 2018年8月5日
*/
public class BalanceTreeDemo {
public int maxDepth = 0;
public MyNode[] inits = {new MyNode(6),new MyNode(7),new MyNode(4),new MyNode(2),new MyNode(5),
new MyNode(1),new MyNode(3),new MyNode(20),new MyNode(15),new MyNode(11),
new MyNode(17),new MyNode(16)};
public static void main(String[] args) {
BalanceTreeDemo demo = new BalanceTreeDemo();
demo.sortingBalanceTree();
}
/**
* 生成平衡二叉树入口
* @author 刘
* @author 2018年8月6日
* @parameter
* @return
*/
public void sortingBalanceTree(){
MyNode node = inits[0];
for(int i = 1; i < inits.length; i++){
interateCreateNodeNew(node, inits[i]);
node = rotateNodeNew(node);
printNode(node);
}
}
/**
* 右旋转(顺时针)
* @author 刘
* @author 2018年8月10日
* ② ③
* ,,,,,,,13,,,,,,,, | ,,,,,,,9,,,,,,,,
* ,,,9,,,,,,,,15,,,, | ,,,8,,,,,,,,13,,,,
* ,8,,,,10,,,,#,,,,,#, | ,5,,,,#,,,,10,,,,,15,
* 5,,#,,#,,#,,#,,#,,,#,,# | #,,#,,#,,#,,#,,#,,,#,,#
*/
public MyNode rotateRight(MyNode node){
//节点9
MyNode tmpNode = node.getLeftNode();
//节点10(先L,再R)
MyNode lRNode = node.getLeftNode().getRightNode();
//节点13和9要互换位置,这里判断node是父节点的左子节点还是右子节点,然后将父节点的左子/右子节点设置为节点9
if(node.getParentNode() != null){
if(node.getParentNode().getLeftNode() == node){
node.getParentNode().setLeftNode(tmpNode);
}else{
node.getParentNode().setRightNode(tmpNode);
}
}
//同上,将节点9的父节点设置为节点13的原父节点
tmpNode.setParentNode(node.getParentNode());
//节点13变为节点9的右子节点
node.setParentNode(tmpNode);
tmpNode.setRightNode(node);
//节点10变为节点13的左子节点
node.setLeftNode(lRNode);
//如果LR节点不为空(节点10),那么他的父节点是13
if(lRNode != null){
lRNode.setParentNode(node);
}
node = tmpNode;
return node;
}
/**
* 左旋转(逆时针)(逻辑和右旋转类似,就不写注释了)
* @author 刘
* @author 2018年8月10日
*/
public MyNode rotateLeft(MyNode node){
MyNode tmpNode = node.getRightNode();
MyNode rLNode = node.getRightNode().getLeftNode();
if(node.getParentNode() != null){
if(node.getParentNode().getLeftNode() == node){
node.getParentNode().setLeftNode(tmpNode);
}else{
node.getParentNode().setRightNode(tmpNode);
}
}
tmpNode.setParentNode(node.getParentNode());
node.setParentNode(tmpNode);
node.setRightNode(rLNode);
tmpNode.setLeftNode(node);
if(rLNode != null){
rLNode.setParentNode(node);
}
node = tmpNode;
return node;
}
/**
* 坐旋转、右旋转,用于将二叉树平衡
* @author 刘
* @author 2018年8月6日
* @parameter MyNode对象
* @return 平衡MyNode对象
*/
public MyNode rotateNodeNew(MyNode node){
if(isBalanceTree(node)){
return node;
}
//这里是递归方法,获取到最小不平衡树,然后进行旋转处理
if(!isBalanceTree(node.getLeftNode())){
node.setLeftNode(rotateNodeNew(node.getLeftNode()));
}
if(!isBalanceTree(node.getRightNode())){
node.setRightNode(rotateNodeNew(node.getRightNode()));
}
if(isBalanceTree(node)){
return node;
}
int leftNodeDepth = 0;
int rightNodeDepth = 0;
leftNodeDepth = getTreeDepth(node.getLeftNode());
rightNodeDepth = getTreeDepth(node.getRightNode());
/**
* 左子节点的深度>右子节点的深度,所以要顺时针旋转(右旋转),现在可以确定节点类型为LL型或者LR型(RR和RL型就不列举了)
* ① ② ③
* ,,,,,,,13,,,,,,,, | ,,,,,,,13,,,,,,,, | ,,,,,,,9,,,,,,,,
* ,,,8,,,,,,,,15,,,, | ,,,9,,,,,,,,15,,,, | ,,,8,,,,,,,,13,,,,
* ,5,,,,9,,,,#,,,,,#, | ,8,,,,10,,,,#,,,,,#, | ,5,,,,#,,,,10,,,,,15,
* #,,#,,#,,10,,#,,#,,,#,,# | 5,,#,,#,,#,,#,,#,,,#,,# | #,,#,,#,,#,,#,,#,,,#,,#
*/
if(leftNodeDepth > rightNodeDepth){
//用左子节点的左右子节点深度来确定是LL型还是LR型。(即,用节点8的左右子节点的深度来判断是LL型还是LR型,节点8的右子节点深度大,所以是LR型)
leftNodeDepth = getTreeDepth(node.getLeftNode().getLeftNode());
rightNodeDepth = getTreeDepth(node.getLeftNode().getRightNode());
//LL型,直接右旋转(顺时针)
if(leftNodeDepth > rightNodeDepth){
node = rotateRight(node);
}else if(leftNodeDepth < rightNodeDepth){
//LR型,需要先将左子节点进行左旋转(逆时针),转换成LL型(即上面注释中,将节点9进行左旋转,从①旋转得到②。)
node.setLeftNode(rotateLeft(node.getLeftNode()));
//转换成LL型后,再右旋转。(将节点13金子那个右旋转,从②得到③)
node = rotateRight(node);
}
}else{
leftNodeDepth = getTreeDepth(node.getRightNode().getLeftNode());
rightNodeDepth = getTreeDepth(node.getRightNode().getRightNode());
//RL型,需要将右子节点进行右旋转,转换成RR型,然后再左旋转。
if(leftNodeDepth > rightNodeDepth){
node.setRightNode(rotateRight(node.getRightNode()));
node = rotateLeft(node);
}else if(leftNodeDepth < rightNodeDepth){
//RR型,直接左旋转。
node = rotateLeft(node);
}
}
return node;
}
/**
* 判断node对象是否是平衡二叉树
* @author 刘
* @author 2018年8月6日
* @parameter node:需要验证的对象(任意二叉树)
* @return true:是平衡二叉树;false:非平衡二叉树
*/
public boolean isBalanceTree(MyNode node){
//如果节点的深度<=2,那么肯定是平衡二叉树(非空MyNode对象)
int depth = getTreeDepth(node);
if(depth <= 2){
return true;
}
/**
* 二叉树的倒数第二层的有效节点数 = 倒数该层的最大节点数,则为平衡二叉树;否则为非平衡二叉树
*
*/
if(Math.pow(2, depth - 2) == getLevelNodeList(node, depth - 1).size()){
return true;
}
return false;
}
/**
* 获取一个二叉树的深度
* @author 刘
* @author 2018年8月5日
* @parameter
* @return
*/
public int getTreeDepth(MyNode node){
int depth = 0;//定义二叉树的深度
if(node == null){
return 0;
}
List<MyNode> list = new ArrayList<MyNode>();
List<MyNode> tmpList = new ArrayList<MyNode>();
list.add(node);
//如果第L层有节点,则depth++,继续获取L+1层的所有子节点
while(true){
if(list == null || list.size() <= 0){
break;
}
depth++;
for(int j = 0; j < list.size(); j++){
if(list.get(j).getLeftNode() != null){
tmpList.add(list.get(j).getLeftNode());
}
if(list.get(j).getRightNode() != null){
tmpList.add(list.get(j).getRightNode());
}
}
list.clear();
list.addAll(tmpList);
tmpList.clear();
}
return depth;
}
/**
* 获取二叉树某一层有效节点数
* @param node 二叉树
* @param level 第几层
* @return List<MyNode>
*/
public List<MyNode> getLevelNodeList(MyNode node, int level){
List<MyNode> list = new ArrayList<MyNode>();
if(level <= 0 || node == null){
return list;
}
list.add(node);
List<MyNode> tmpList = new ArrayList<MyNode>();
//因为层数是从1开始计算,所以这里需要将level-1。
for(int i = 0; i < level - 1; i++){
for(int j = 0; j < list.size(); j++){
if(list.get(j).getLeftNode() != null){
tmpList.add(list.get(j).getLeftNode());
}
if(list.get(j).getRightNode() != null){
tmpList.add(list.get(j).getRightNode());
}
}
list.clear();
list.addAll(tmpList);
tmpList.clear();
}
return list;
}
/**
* 递归方法生成有序二叉树(平衡)
* @author 刘
* @author 2018年8月6日
* @parameter node:已存在的二叉树;newNode:将要添加到二叉树的节点
* @return
*/
private void interateCreateNodeNew(MyNode node,MyNode newNode){
/**
* 如果新节点的value值<=旧节点的value值
* ①:如果旧节点的左子节点不为空,则用旧节点的左子节点和新节点比较
* ②:如果旧节点的左子节点为空,则将新节点设置为旧节点的左子节点
*/
if(newNode.getValue() <= node.getValue()){
if(node.getLeftNode() != null){
interateCreateNodeNew(node.getLeftNode(), newNode);
}else{
newNode.setParentNode(node);
node.setLeftNode(newNode);
}
}else{
/**
* 如果新节点的value值>旧节点的value值
* ①:如果旧节点的右子节点不为空,则用旧节点的右子节点和新节点比较
* ②:如果旧节点的右节点为空,则将新节点设置为旧节点的右子节点
*/
if(node.getRightNode() != null){
interateCreateNodeNew(node.getRightNode(), newNode);
}else{
newNode.setParentNode(node);
node.setRightNode(newNode);
}
}
}
/**
* 将目标二叉树结构以value值的形式打印出来,不存在节点用#代替,打印的结果放到txt文件,然后改成csv,会看到二叉树的结构,如下
* @param node 目标二叉树
* ,,,,,,,6,,,,,,,
,,,4,,,,,,,,7,,,
,2,,,,5,,,,#,,,,#,
1,,3,,#,,#,,#,,#,,#,,#
*/
public void printNode(MyNode node){
StringBuffer sb = new StringBuffer();
int maxDepth = getTreeDepth(node);
if(maxDepth > 0){
for(int i = 0; i < maxDepth; i++){
//i层第一个节点的左边/最后一个节点的右边需要几个逗号分隔符
int maxJ = (int)Math.pow(2, maxDepth - i - 1) - 1;
for(int j = 0; j < maxJ; j++){
sb.append(",");
}
//i层的最大节点数
int levelCount = (int)Math.pow(2, i);
//i层相邻两个节点中间的间隔数
int gapCount = (int)(Math.pow(2, maxDepth - i) - 1);
for(int j = 0; j < levelCount; j++){
sb.append(getNodeValue(node, i + 1, j + 1, maxDepth));
if(levelCount > 1 && j < levelCount - 1){
for(int k = 0; k <= gapCount; k++){
sb.append(",");
}
}
}
for(int j = 0; j < maxJ; j++){
sb.append(",");
}
sb.append("\n");
}
System.out.println(sb.toString());
}
}
/**
* 根据层数和序号,获取二叉树对应节点的value值
* @param node 二叉树
* @param level 层数(根节点level=1)
* @param index 序号(从左到右,从1开始)
* @return 对应节点的value值,如果不存在该节点,则返回#
*/
public String getNodeValue(MyNode node, int level, int index, int maxDepth){
if(maxDepth == 0){
return "请先计算深度";
}
if(level > maxDepth){
return "超出最大层数!";
}
if(index < 1 || index > Math.pow(2, level - 1)){
return "超出序号范围!";
}
if(node == null){
return "树不能为空!";
}
return this.interateGetNodeValue(node, level, index);
}
/**
* 根据层数和序号,获取二叉树对应节点的value值
* @param node 二叉树
* @param level 层数(根节点level=1)
* @param index 序号(从左到右,从1开始)
* @return 对应节点的value值,如果不存在该节点,则返回#
*/
private String interateGetNodeValue(MyNode node, int level, int index){
//如果只有一层,则直接返回node的value值
if(level == 1){
return String.valueOf(node == null ? "#" : node.getValue());
}
//如果是取第二层的节点,则判断是否为空后,直接返回value值。
if(level == 2){
if(index == 1){
return String.valueOf(node.getLeftNode() == null ? "#" : node.getLeftNode().getValue());
}else{
return String.valueOf(node.getRightNode() == null ? "#" : node.getRightNode().getValue());
}
}
/**
* 当层数>=3时,需要迭代执行本方法,将层数执行到第二层,用level=2返回value值
* ①:先判断目标节点在二叉树的左边还是右边(以根节点位置为竖轴,判断目标节点在竖轴的左边还是右边)
* ②:如果在左边,则将【根节点的左子节点,level-1,newIndex】座位参数,调用本方法。
*/
//目标层的最大节点数
int levelCount = (int)(Math.pow(2, level - 1));
//level-1后,新的序号(从左到右,从1开始)
int newIndex = 0;
//level-1后,新的根节点
MyNode newNode = null;
if(index > levelCount / 2){
newIndex = index - levelCount / 2;
newNode = node.getRightNode();
}else{
newIndex = index;
newNode = node.getLeftNode();
}
if(newNode == null){
return "#";
}
return interateGetNodeValue(newNode, level - 1, newIndex);
}
}