在上一篇文章中回顾了BST的相关操作,而AVL树其实就是BST的基础上加上了平衡的条件限制。也就是针对把每个节点作为根节点的树中的左右子树的高度差不能相差超过一。
首先贴出整个程序的代码:
class TreeNode {
int value;
int height;
TreeNode left;
TreeNode right;
TreeNode(int val) {
value = val;
left = null;
right = null;
}
}
public class Solution {
private static TreeNode root = null;
public static void main(String[] args) {
while (true) {
System.out.println();
System.out.println("*******************");
System.out.println("请输入序号:");
System.out.println("1、插入元素");
System.out.println("2、删除元素");
System.out.println("3、查找元素");
System.out.println("4、先序遍历");
System.out.println("5、中序遍历");
System.out.println("6、后序遍历");
System.out.println("*******************");
Scanner sc = new Scanner(System.in);
int num1 = sc.nextInt();
switch (num1) {
case 1:
System.out.println("请输入需要插入的节点的值:");
int val1 = sc.nextInt();
TreeNode node1 = new TreeNode(val1);
root = insert(node1, root);
recurPerOrder(root);
break;
case 2:
System.out.println("请输入需要删除的节点的值:");
int val2 = sc.nextInt();
TreeNode node2 = new TreeNode(val2);
delete(node2, root);
System.out.print("删除成功!");
break;
case 3:
System.out.println("请输入需要查找的节点的值:");
int val3 = sc.nextInt();
TreeNode node3 = new TreeNode(val3);
boolean flag2 = find(node3, root);
if (flag2) System.out.println("找到该节点!");
else System.out.println("找不到该节点!");
break;
case 4:
System.out.println("1、递归遍历");
System.out.println("2、非递归遍历");
int num2 = sc.nextInt();
switch (num2) {
case 1:
recurPerOrder(root);
break;
case 2:
unrecurPreOrder(root);
break;
default:
break;
}
break;
case 5:
System.out.println("1、递归遍历");
System.out.println("2、非递归遍历");
int num3 = sc.nextInt();
switch (num3) {
case 1:
recurInOrder(root);
break;
case 2:
unrecurInOrder(root);
break;
default:
break;
}
break;
case 6:
System.out.println("1、递归遍历");
System.out.println("2、非递归遍历");
int num4 = sc.nextInt();
switch (num4) {
case 1:
recurPostOrder(root);
break;
case 2:
unrecurPostOrder(root);
break;
default:
break;
}
break;
default:
break;
}
}
}
private static int height(TreeNode root) {
if (root != null) {
return root.height;
} else {
return 0;
}
}
private static int max(int x, int y) {
return x > y ? x : y;
}
private static TreeNode leftleftRotation(TreeNode node) {
TreeNode pnode = node.left;
node.left = pnode.right;
pnode.right = node;
node.height = max(height(node.left), height(node.right)) + 1;
pnode.height = max(node.height, height(pnode.left)) + 1;
return pnode;
}
private static TreeNode rightrightRotation(TreeNode node) {
TreeNode pnode = node.right;
node.right = pnode.left;
pnode.left = node;
node.height = max(height(node.left), height(node.right)) + 1;
pnode.height = max(node.height, height(pnode.right)) + 1;
return pnode;
}
private static TreeNode leftrightRotation(TreeNode node) {
node.left = rightrightRotation(node.left);
return leftleftRotation(node);
}
private static TreeNode rightleftRotation(TreeNode node) {
node.right = leftleftRotation(node.left);
return rightrightRotation(node);
}
private static TreeNode insert(TreeNode node1, TreeNode root) {
if (root == null) {
root = node1;
if (root == null) {
System.out.println("创建节点错误!");
return null;
}
} else {
int cmp = node1.value - root.value;
if (cmp < 0) {
root.left = insert(node1, root.left);
if (height(root.left) - height(root.right) == 2) {
if (node1.value - root.left.value < 0) {
root = leftleftRotation(root);
} else {
root = leftrightRotation(root);
}
}
} else if (cmp > 0) {
root.right = insert(node1, root.right);
if (height(root.right) - height(root.left) == 2) {
if (node1.value - root.right.value > 0) {
root = rightrightRotation(root);
} else {
root = rightleftRotation(root);
}
}
} else {
System.out.print("无法插入值相同的节点!");
}
}
root.height = max(height(root.left), height(root.right)) + 1;
return root;
}
private static TreeNode delete(TreeNode node2, TreeNode root) {
if (root == null || node2 == null) {
return null;
}
int cmp = node2.value - root.value;
if (cmp < 0) {
root.left = delete(node2, root.left);
if (height(root.right) - height(root.left) == 2) {
TreeNode pnode = root.right;
if (height(pnode.left) < height(pnode.right)) {
root = rightrightRotation(root);
} else {
root = rightleftRotation(root);
}
}
} else if (cmp > 0) {
root.right = delete(node2, root.right);
if (height(root.left) - height(root.right) == 2) {
TreeNode pnode = root.left;
if (height(pnode.left) < height(pnode.right)) {
root = leftrightRotation(root);
} else {
root = leftleftRotation(root);
}
}
} else {
/**
* 这里是找到了需要删除的节点,首先判断是否左右子树都不为空,如果是,则判断左右子树的高度,然后删除高的
* 子树中的最大(最小)节点,同时替代需要删除的节点,若左右子树有一个不为空或者都为空,则直接赋值即可
*/
if (root.left != null && root.right != null) {
if (height(root.left) > height(root.right)) {
/**
* 找到左子树的中的最大节点
*/
TreeNode pnode = root.left;
while (pnode.right != null) {
pnode = pnode.right;
}
root.value = pnode.value;
root.left = delete(pnode,root.left);
}else{
/**
* 找到右子树中的最小节点
*/
TreeNode pnode = root.right;
while (pnode.left != null) {
pnode = pnode.left;
}
root.value = pnode.value;
root.right = delete(pnode,root.right);
}
}else{
root = (root.left==null)?root.right:root.left;
}
}
return root;
}
private static boolean find(TreeNode node3, TreeNode root) {
if (root == null) {
System.out.print("树为空!");
return false;
}
TreeNode curnode = root;
while (curnode != null) {
if (curnode.value < node3.value) {
curnode = curnode.right;
}else if(curnode.value > node3.value){
curnode = curnode.left;
}else{
System.out.print("查找成功!");
return true;
}
}
System.out.print("找不到该节点!");
return false;
}
private static void unrecurPostOrder(TreeNode root) {
/**
* 后序迭代遍历和先序,中序不一样,需要判断什么时候读取根节点,同时需要考虑将根节点重新放入栈中的情况
*/
Stack<TreeNode> snode = new Stack<>();
TreeNode lastnode = null;
TreeNode curnode = root;
while (curnode != null) {
snode.push(curnode);
curnode = curnode.left;
}
while (!snode.isEmpty()) {
/**
* 只有当右节点不存在或者右节点是上一个被访问的节点的时候才会访问根节点
*/
curnode = snode.pop();
if (curnode.right == null || curnode.right == lastnode) {
System.out.print(curnode.value + " ");
lastnode = curnode;
} else {
/**
* 当右子树存在,并且不是上一个访问的元素的时候,将根节点重新入栈,同时进入右子树,将左子树入栈
*/
snode.push(curnode);
curnode = curnode.right;
while (curnode != null) {
snode.push(curnode);
curnode = curnode.left;
}
}
}
}
private static void recurPostOrder(TreeNode root) {
if (root != null) {
recurPostOrder(root.left);
recurPostOrder(root.right);
System.out.print(root.value + " ");
}
}
private static void unrecurInOrder(TreeNode root) {
/**
* 类似于先序遍历,将每个节点看做一个根节点处理
*/
Stack<TreeNode> snode = new Stack<>();
TreeNode p = root;
while (!snode.isEmpty() || p != null) {
if (p != null) {
snode.push(p);
p = p.left;
} else {
TreeNode q = snode.pop();
System.out.print(q.value + " ");
p = q.right;
}
}
}
private static void recurInOrder(TreeNode root) {
if (root != null) {
recurInOrder(root.left);
System.out.print(root.value + " ");
recurInOrder(root.right);
}
}
private static void unrecurPreOrder(TreeNode root) {
/**
* 直接利用栈将每个节点都当做一个根节点
*/
/*Stack<TreeNode> snode = new Stack<>();
TreeNode p = root;
while (!snode.isEmpty() || p != null) {
if (p!=null) {
System.out.print(p.value+" ");
snode.push(p);
p = p.left;
}else{
TreeNode q = snode.pop();
p = q.right;
}
}*/
/**
* 利用栈的后进先出的特性,将左右节点反过来放入栈中
*/
Stack<TreeNode> snode = new Stack<>();
snode.push(root);
while (!snode.isEmpty()) {
TreeNode q = snode.pop();
System.out.print(q.value + " ");
if (q.right != null) {
snode.push(q.right);
}
if (q.left != null) {
snode.push(q.left);
}
}
}
private static void recurPerOrder(TreeNode root) {
if (root != null) {
System.out.print(root.value + " ");
recurPerOrder(root.left);
recurPerOrder(root.right);
}
}
}
以上的代码总共实现了三个操作: 插入,删除,查找。
下面让我们一步步分析相关操作的步骤和方法:
1、插入操作
当我们完成插入操作的时候,二叉树可能是不平衡的,所以要求我们需要进行相应的旋转操作。
而旋转操作总共分为四种情况:
1、LL(指的是插入节点后,根的左子树的左子树还有非空节点,导致根的左子树比右子树高)(这里不像画图,就借用别人画的图了)
private static TreeNode leftleftRotation(TreeNode node) {
TreeNode pnode = node.left;
node.left = pnode.right;
pnode.right = node;
node.height = max(height(node.left), height(node.right)) + 1;
pnode.height = max(node.height, height(pnode.left)) + 1;
return pnode;
}
代码中的node就是k2,pnode也就是k1了,思路就是首先将k1的右子树赋给k2的左子树,将k2赋给k1的右子树,完成旋转,接着就是对于每个k1,k2节点的height的属性的更新了,首先计算k2的height,然后计算k1的height,最后返回k1.
2、RR(指的是插入节点后,根的右子树的右子树还有非空节点,导致根的右子树比左子树高)
private static TreeNode rightrightRotation(TreeNode node) {
TreeNode pnode = node.right;
node.right = pnode.left;
pnode.left = node;
node.height = max(height(node.left), height(node.right)) + 1;
pnode.height = max(node.height, height(pnode.right)) + 1;
return pnode;
}
RR的旋转思路和LL的一样,只不过反过来。
3、LR(指的是插入节点后,根的左子树的右子树还有非空节点,导致根的左子树比右子树高)
private static TreeNode leftrightRotation(TreeNode node) {
node.left = rightrightRotation(node.left);
return leftleftRotation(node);
}
首先,在这里node就是k3,LR的思路就是首先针对k1进行RR旋转,转换为LL的情况,然后针对k3,进行LL旋转即可。
4、RL(指的是插入节点后,根的右子树的左子树还有非空节点,导致根的右子树比左子树高)
private static TreeNode rightleftRotation(TreeNode node) {
node.right = leftleftRotation(node.left);
return rightrightRotation(node);
}
RL的思路和RL的思路相似。
到这里我们的四种旋转操作就介绍完毕了,可以进行插入操作的分析了:
插入操作代码:
private static TreeNode insert(TreeNode node1, TreeNode root) {
if (root == null) {
root = node1;
if (root == null) {
System.out.println("创建节点错误!");
return null;
}
} else {
int cmp = node1.value - root.value;
if (cmp < 0) {
root.left = insert(node1, root.left);
if (height(root.left) - height(root.right) == 2) {
if (node1.value - root.left.value < 0) {
root = leftleftRotation(root);
} else {
root = leftrightRotation(root);
}
}
} else if (cmp > 0) {
root.right = insert(node1, root.right);
if (height(root.right) - height(root.left) == 2) {
if (node1.value - root.right.value > 0) {
root = rightrightRotation(root);
} else {
root = rightleftRotation(root);
}
}
} else {
System.out.print("无法插入值相同的节点!");
}
}
root.height = max(height(root.left), height(root.right)) + 1;
return root;
}
上面的代码的主要思路是:针对需要插入的节点node1,我们通过递归,一直找到空的叶子节点,创建节点,然后递归返回,在返回的过程中,我们检测当前节点的左右子树的高度差是否大于2,如果是的,我们就判断是四种旋转情况的哪一种,然后调用相应的函数进行旋转即可,同时记得在每一次返回的过程中要更新当前节点的高度,并返回。总体来说不难。
2、删除操作
首先删除操作其实分为三种情况:
1、当需要删除的节点左右子树都不为空的时候
2、当需要删除的节点左子树或者右子树不为空的时候
3、当需要删除的节点左右子树都为空的时候
在这里我们可以将第二种情况和第三种情况合并在一起操作,同时主要针对第一种情况进行分析
首先我们针对第二,三种情况进行分析:其实,只需要将不为空的那一个节点赋给当前需要删除的节点就可以了。如果左右子树都为空,那么我们就将当前节点赋为null即可。
下面我们主要针对第一种情况进行分析:
首先我们贴出代码:
if (root.left != null && root.right != null) {
if (height(root.left) > height(root.right)) {
/**
* 找到左子树的中的最大节点
*/
TreeNode pnode = root.left;
while (pnode.right != null) {
pnode = pnode.right;
}
root.value = pnode.value;
root.left = delete(pnode, root.left);
} else {
/**
* 找到右子树中的最小节点
*/
TreeNode pnode = root.right;
while (pnode.left != null) {
pnode = pnode.left;
}
root.value = pnode.value;
root.right = delete(pnode, root.right);
}
} else {
root = (root.left == null) ? root.right : root.left;
}
在这里root即我们需要删除的节点,如果他的左右子树都不为空的话,我们就找左右子树中高度大的那一边,如果左子树高度高于右子树,那么我们就在左子树中不断向右循环找到左子树中的最大节点,然后将该节点的值赋给root,然后删除该节点。如果右子树高度高于左子树,那么我们就在右子树中不断向左循环,找到右子树中的最小节点,然后将该节点的值赋给root,然后删除该节点。
接着当我们完成删除操作后,此时AVL树可能不再平衡,所以这时候我们需要通过旋转操作将它重新编程一个平衡二叉树。
这其实就和插入操作的时候很相似了。其实就是针对删除后的情况,我们判断属于4种旋转操作的哪一种就可以了,然后进行相应的旋转操作。
下面贴出代码:
int cmp = node2.value - root.value;
if (cmp < 0) {
root.left = delete(node2, root.left);
if (height(root.right) - height(root.left) == 2) {
TreeNode pnode = root.right;
if (height(pnode.left) < height(pnode.right)) {
root = rightrightRotation(root);
} else {
root = rightleftRotation(root);
}
}
} else if (cmp > 0) {
root.right = delete(node2, root.right);
if (height(root.left) - height(root.right) == 2) {
TreeNode pnode = root.left;
if (height(pnode.left) < height(pnode.right)) {
root = leftrightRotation(root);
} else {
root = leftleftRotation(root);
}
}
} else{...}
这里cmp是用来表示需要删除的节点是在左子树还是在右子树中,最后一个else表示root即是当前需要删除的节点。而在之前的if中,可以看到cmp<0的时候,表示需要删除的节点在当前节点的左边,当删除之后,这时候判断root节点的右子树的高度是否比左子树的高度>=2,如果是的,则判断root.right的左右子树的高度,然后进行相应的旋转。同理,在cmp>0的时候,判断左子树是否比右子树高>=2,然后判断root.left的左右子树的高度,接着进行相应的旋转。
最后贴出整个删除操作的代码:
private static TreeNode delete(TreeNode node2, TreeNode root) {
if (root == null || node2 == null) {
return null;
}
int cmp = node2.value - root.value;
if (cmp < 0) {
root.left = delete(node2, root.left);
if (height(root.right) - height(root.left) == 2) {
TreeNode pnode = root.right;
if (height(pnode.left) < height(pnode.right)) {
root = rightrightRotation(root);
} else {
root = rightleftRotation(root);
}
}
} else if (cmp > 0) {
root.right = delete(node2, root.right);
if (height(root.left) - height(root.right) == 2) {
TreeNode pnode = root.left;
if (height(pnode.left) < height(pnode.right)) {
root = leftrightRotation(root);
} else {
root = leftleftRotation(root);
}
}
} else {
/**
* 这里是找到了需要删除的节点,首先判断是否左右子树都不为空,如果是,则判断左右子树的高度,然后删除高的
* 子树中的最大(最小)节点,同时替代需要删除的节点,若左右子树有一个不为空或者都为空,则直接赋值即可
*/
if (root.left != null && root.right != null) {
if (height(root.left) > height(root.right)) {
/**
* 找到左子树的中的最大节点
*/
TreeNode pnode = root.left;
while (pnode.right != null) {
pnode = pnode.right;
}
root.value = pnode.value;
root.left = delete(pnode,root.left);
}else{
/**
* 找到右子树中的最小节点
*/
TreeNode pnode = root.right;
while (pnode.left != null) {
pnode = pnode.left;
}
root.value = pnode.value;
root.right = delete(pnode,root.right);
}
}else{
root = (root.left==null)?root.right:root.left;
}
}
return root;
}
3、查找操作
查找操作比较简单,就直接贴代码了,相信大家都可以看得懂:
private static boolean find(TreeNode node3, TreeNode root) {
if (root == null) {
System.out.print("树为空!");
return false;
}
TreeNode curnode = root;
while (curnode != null) {
if (curnode.value < node3.value) {
curnode = curnode.right;
}else if(curnode.value > node3.value){
curnode = curnode.left;
}else{
System.out.print("查找成功!");
return true;
}
}
System.out.print("找不到该节点!");
return false;
}
总结
其实AVL树的相关操作不难,主要的就是那四种旋转操作需要记住,然后在插入和删除之后,如果发现某个节点的左右子树的高度差相差>=2,那么就要接着判断高的那一边的左右子树的高度哪边高,然后对应旋转即可。