本文参考海纳的两篇文章,需要补平衡二叉树知识的请看这里。
参照的文章是这篇文章。
可以直接去看这两篇文章,再回头看我这篇文章,所以我就去繁就简。
代码
package com.yubotao;
/**
* @Auther: yubt
* @Description:
* @Date: Created in 9:23 2018/6/8
* @Modified By:
*/
public class AVLNode {
public int data;
public int depth;
public int balance;
public AVLNode parent;
public AVLNode left;
public AVLNode right;
public AVLNode(int data){
this.data = data;
depth = 1;
balance = 0;
left = null;
right = null;
}
public void insert(AVLNode root, int data){
if (data < root.data){
if (root.left != null){
insert(root.left, data);
}else {
root.left = new AVLNode(data);
root.left.parent = root;
}
}else {
if (root.right != null){
insert(root.right, data);
}else {
root.right = new AVLNode(data);
root.right.parent = root;
}
}
// 从插入的过程回溯回来的时候,计算平衡因子
root.balance = calcBalance(root);
// 左子树高,应该右旋
if (root.balance >= 2){
// 右孙高,先左旋
if (root.left.balance == -1){
left_rotate(root.left);
}
right_rotate(root);
}
// 右子树高,左旋
if (root.balance <= -2){
// 左孙高,先右旋
if (root.right.balance == 1){
right_rotate(root.right);
}
left_rotate(root);
}
root.balance = calcBalance(root);
root.depth = calcDepth(root);
}
// 右旋
private void right_rotate(AVLNode p){
// 一次旋转涉及到的结点包括祖父,父亲,右儿子
AVLNode pParent = p.parent;
AVLNode pLeftSon = p.left;
AVLNode pRightGrandSon = pLeftSon.right;
// 左子变父
pLeftSon.parent = pParent;
if (pParent != null){
if (p == pParent.left){
pParent.left = pLeftSon;
}else if (p == pParent.right){
pParent.right = pLeftSon;
}
}
pLeftSon.right = p;
p.parent = pLeftSon;
// 右孙变左孙
p.left = pRightGrandSon;
if (pRightGrandSon != null){
pRightGrandSon.parent = p;
}
p.depth = calcDepth(p);
p.balance = calcBalance(p);
pLeftSon.depth = calcDepth(pLeftSon);
pLeftSon.balance = calcBalance(pLeftSon);
}
private void left_rotate(AVLNode p){
// 一次选择涉及到的结点包括祖父,父亲,左儿子
AVLNode pParent = p.parent;
AVLNode pRightSon = p.right;
AVLNode pLeftGrandSon = pRightSon.left;
// 右子变父
pRightSon.parent = pParent;
if (pParent != null){
if (p == pParent.right){
pParent.right = pRightSon;
}else if (p == pParent.left){
pParent.right = pRightSon;
}
}
pRightSon.left = p;
p.parent = pRightSon;
// 左孙变右孙
p.right = pLeftGrandSon;
if (pLeftGrandSon != null){
pLeftGrandSon.parent = p;
}
p.depth = calcDepth(p);
p.balance = calcBalance(p);
pRightSon.depth = calcDepth(pRightSon);
pRightSon.balance = calcBalance(pRightSon);
}
public int calcBalance(AVLNode p){
int left_depth;
int right_depth;
if (p.left != null){
left_depth = p.left.depth;
}else {
left_depth = 0;
}
if (p.right != null){
right_depth = p.right.depth;
}else {
right_depth = 0;
}
return left_depth - right_depth;
}
public int calcDepth(AVLNode p){
int depth = 0;
if (p.left != null){
depth = p.left.depth;
}
if (p.right != null && depth < p.right.depth){
depth = p.right.depth;
}
depth++;
return depth;
}
}
这里阐述几个概念:
平衡因子:该结点左右子树的深度(高度)差。
深度(高度):树的层数。
如图
四棵树的深度依次为:4,3,4,3.
这两个概念理解好才能更加快速的理解上面的代码。
插入原理讲解
我们先看第一种情况:
c节点也就是黄色节点是我们新插入的节点,此时树不平衡,需要进行操作。根据各节点平衡因子判断(可参照代码),我们需要先右旋后左旋。这里已经把每个节点都做了相应的标记了,相关步骤就是代码的执行顺序。
我只做一次代码解读,后面的自己看图对应一下。
首先,我们进入insert()
方法,此时应该是新建一个节点c。执行到方法体中的开头部分
if (data < root.data){
if (root.left != null){
insert(root.left, data);
}else {
root.left = new AVLNode(data);
root.left.parent = root;
}
}
此时走else分支,新建了一个结点,并指定c的parent结点。
然后我们这时需要执行到这步:
// 从插入的过程回溯回来的时候,计算平衡因子
root.balance = calcBalance(root);
计算b结点的balance,为1。后续两个if判断跳过,并计算了b结点的balance为1,depth为2.
此时怎么样了呢?
我们从a结点的insert()
方法中的开头部分
else {
if (root.right != null){
insert(root.right, data);
}else {
root.right = new AVLNode(data);
root.right.parent = root;
}
}
if分支跳出(因为我们插入的时候,a有右子树b,所以当时进入了b结点的insert()
方法)。
接下来计算a结点的balance,为-2,进入第二个if分支
// 右子树高,左旋
if (root.balance <= -2){
// 左孙高,先右旋
if (root.right.balance == 1){
right_rotate(root.right);
}
left_rotate(root);
}
并且b结点的balance为1,符合条件,开始进行右旋操作。进入方法right_rotate()
中。
这时p为b结点,所以就如上图标识,按照代码顺序执行此次右旋操作。
// 右旋
private void right_rotate(AVLNode p){
// 一次旋转涉及到的结点包括祖父,父亲,右儿子
AVLNode pParent = p.parent;
AVLNode pLeftSon = p.left;
AVLNode pRightGrandSon = pLeftSon.right;
// 左子变父
pLeftSon.parent = pParent; // 图中左1
if (pParent != null){
if (p == pParent.left){
pParent.left = pLeftSon; // 图中左2
}else if (p == pParent.right){
pParent.right = pLeftSon;
}
}
pLeftSon.right = p; // 图中左3
p.parent = pLeftSon; // 图中左4
// 右孙变左孙
p.left = pRightGrandSon; // 图中左5
if (pRightGrandSon != null){
pRightGrandSon.parent = p;
}
p.depth = calcDepth(p);
p.balance = calcBalance(p);
pLeftSon.depth = calcDepth(pLeftSon);
pLeftSon.balance = calcBalance(pLeftSon);
}
接下来进入左旋方法left_rotate()
,注意此时的p结点为a结点。
如上图右边标识,再看一下代码执行顺序:
private void left_rotate(AVLNode p){
// 一次选择涉及到的结点包括祖父,父亲,左儿子
AVLNode pParent = p.parent;
AVLNode pRightSon = p.right;
AVLNode pLeftGrandSon = pRightSon.left;
// 右子变父
pRightSon.parent = pParent; // 图中右1
if (pParent != null){
if (p == pParent.right){
pParent.right = pRightSon;
}else if (p == pParent.left){
pParent.right = pRightSon;
}
}
pRightSon.left = p; // 图中右2
p.parent = pRightSon; // 图中右3
// 左孙变右孙
p.right = pLeftGrandSon; // 图中右4
if (pLeftGrandSon != null){
pLeftGrandSon.parent = p;
}
p.depth = calcDepth(p);
p.balance = calcBalance(p);
pRightSon.depth = calcDepth(pRightSon);
pRightSon.balance = calcBalance(pRightSon);
}
此时,完成整个插入过程。
接下来我们再看两种情况。
可以看到圈出的两部分就是上面两种情况。所以就不一一赘述。
最后再看更复杂的两种情况,只贴图,可以自己跟着代码顺序捋一捋,熟悉一下这个过程,我就不一一分析了。
删除方法
这部分待续,我还没完全想好,抽个时间再来更新吧。
主要删除的时候需要考虑,如何处理待删结点有子节点的情况。
如果待删结点是叶子结点,那么很容易删除,然后向上回溯,看一下各结点的平衡因子,并执行相关的左右旋方法即可。
但是如果待删结点有左右子树时,这个时候就要考虑如何操作了,怎样将该结点删除又不影响其子节点。
我的一个想法是:将待删结点变为叶子结点,这样就好操作了,不过这个代码怎么写,如果同时有左右结点,和哪个结点互换,是不是都行呢?
先思考到这里,有兴趣的朋友可以留言,一起讨论。