一、二叉平衡树
上一章所示的同一组元素按照不同顺序插入到二叉排序树中可能会产生两种形状不同的二叉排序树。
当出现右边的情况时,树的高度过高,如果要查找值为“70”的节点需要查找7次,其查找次数已经接近于链表了,这样会导致查找效率过低。为了防止右图这种较为极端的情况出现,一种新的二叉树-二叉平衡树(AVL树)被提出了。
AVL树得名于它的发明者格奥尔吉·阿杰尔松-韦利斯基(Adelson-Velsky)和叶夫根尼·兰迪斯(E.M. Landis),是他们名字的缩写。Adelson-Velsky和E.M. Landis在1962年的论文《An algorithm for the organization of information》中公开了这一数据结构。
介绍这一数据结构之前,首先介绍一下平衡因子的概念:
平衡因子=左子树高-右子树高。
AVL树本质上是一棵空树或左右两个子树的高度差的绝对值不超过1的二叉查找树,同时它的左右两个子树也都是一棵平衡二叉树。
查找、插入和删除在平均和最坏情况下的时间复杂度都是 O(log n)。增加和删除元素的操作则可能需要借由一次或多次树旋转,以实现树的重新平衡。
其特点如下:
1.它和它的子树必须是一棵空树或左右两个子树的高度差的绝对值不超过1,即平衡因子的绝对值小于等于1。平衡二叉树结点的平衡因子的值只可能是−1、0或1。
2.它必须是一颗二叉查找树
由平衡二叉树的特点可以得到,上图中左侧为平衡二叉树,右侧不是平衡二叉树。
二、二叉平衡树的操作
1.查找
可以像普通二叉查找树一样的进行,所以耗费O(log n)时间,因为AVL树总是保持平衡的。不需要特殊的准备,树的结构不会由于查找而改变。
// 查找与val数值相等的节点public TreeNode get(int val) {return get(root, val);}public TreeNode get(TreeNode currentNode, int val) {TreeNode findTreeNode = new TreeNode();while (currentNode != null) {if (currentNode.val > val) { // 当前节点比将要插入的值大currentNode = currentNode.left; // 去根节点左子树中继续寻找} else if (currentNode.val < val) { // 当前节点比将要插入的值小currentNode = currentNode.right; // 去根节点右子树中继续寻找} else if (currentNode.val == val) {findTreeNode = currentNode; //相等,则currentNode是要寻找的节点。System.out.println(findTreeNode.val + " has been found!");return findTreeNode;}}System.out.println("Error! " + val + " does not exist!");return null;}
2.插入
二叉平衡树的插入可以像二叉查找树一样的进行,但是插入新节点后新的二叉树可能会失去平衡。如下图所示,插入节点67后,二叉树进入失衡状态。如何继续保持平衡?
想要对插入后的二叉树进行平衡,首先要了解以下概念:
最小不平衡子树:在新插入的结点向上查找,以第一个平衡因子的绝对值超过1的结点为根的子树称为最小不平衡子树。
在上图中,以70节点作为根节点的子树,即为最小不平衡子树。
在插入操作中,只要将最小不平衡子树调整平衡,则其他祖先结点都会恢复平衡。如下所示:
public void insert(int val) {root = insert(root, val);}// 将结点插入到AVL树中,并返回根节点private TreeNode insert(TreeNode tree, int val) {if (tree == null) {// 新建节点tree = new TreeNode(val);if (tree==null) {System.out.println("ERROR: create avltree node failed!");return null;}} else {if (val< tree.val) { // 应该将key插入到"tree的左子树"的情况tree.left = insert(tree.left, val);// 插入节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.left) - height(tree.right) == 2) {if (val < tree.left.val)tree = leftLeftRotation(tree);elsetree = leftRightRotation(tree);}} else if (val > tree.val) { // 应该将key插入到"tree的右子树"的情况tree.right = insert(tree.right, val);// 插入节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.right) - height(tree.left) == 2) {if (val > tree.right.val)tree = rightRightRotation(tree);elsetree = rightLeftRotation(tree);}} else { // val == tree.valSystem.out.println("添加失败: 不允许添加相同的节点!");}}tree.height = Math.max( height(tree.left), height(tree.right)) + 1;return tree;}
3.调整最小不平衡子树
调整最小不平衡子树即可使二叉树恢复平衡,可以通过四种旋转操作调整不平衡子树。
1)LL平衡旋转(右单旋转)
LL 是指在节点A 左孩子(L)的左子树(L)插入了新节点,导致A节点失衡的节点是A节点的左子树的左子树。
A的平衡因子由1增至2,导致以A为根的子树失去平衡,对于 LL 型,需要执行右旋转操作。
将A的左孩子B向右上旋转代替A成为根结点,将A结点向右下旋转成为B的右子树的根结点,而B的原右子树则作为A结点的左子树。
如上图所示,左中右三个二叉树依次是:插入节点前的平衡二叉树、插入节点后失衡的二叉树、旋转后恢复平衡的二叉平衡树。
// 左旋转public TreeNode leftRotation(TreeNode k2) {TreeNode k1;k1 = k2.left;k2.left = k1.right;k1.right = k2;k2.height = Math.max(height(k2.left), height(k2.right)) + 1;k1.height = Math.max(height(k1.left), k2.height) + 1;return k1;}// LL: 左左对应的情况(左单旋转)。public TreeNode leftLeftRotation(TreeNode k1) {return leftRotation(k1);}
2)RR平衡旋转(左单旋转)
RR是指在节点A 右孩子(R)的右子树(R)插入了新节点,导致A节点失衡的节点是A节点的右子树的右子树。
A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,对于 RR 型,需要执行左旋转操作。
将A的右孩子B向左上旋转代替A成为根结点,将A结点向左下旋转成为B的左子树的根结点,而B的原左子树则作为A结点的右子树。
如上图所示,左中右三个二叉树依次是:插入节点前的平衡二叉树、插入节点后失衡的二叉树、旋转后恢复平衡的二叉平衡树。
// 右旋转public TreeNode rightRotation(TreeNode k1) {TreeNode k2;k2 = k1.right;k1.right = k2.left;k2.left = k1;k1.height = Math.max(height(k1.left), height(k1.right)) + 1;k2.height = Math.max(height(k2.right), k1.height) + 1;return k2;}// RR: 右右对应的情况(右单旋转)。public TreeNode rightRightRotation(TreeNode k1) {return rightRotation(k1);}
3)LR平衡旋转(先左后右旋转)
LR 是指在节点A 左孩子(L)的右子树(R)插入了新节点,导致A节点失衡的节点是A节点的左子树的右子树。
A的平衡因子由1增至2,导致以A为根的子树失去平衡,对于 LR 型,需要先执行左旋转再执行右旋转操作。
先将A结点的左孩子B的右子树的根结点C向左上旋转提升到B结点的位置,然后再把该C结点向右上旋转提升到A结点的位置。
如上图所示,左中右三个二叉树依次是:插入节点前的平衡二叉树、插入节点后失衡的二叉树、旋转后恢复平衡的二叉平衡树。
两次旋转的拆解过程如下:
// 左旋转public TreeNode leftRotation(TreeNode k2) {TreeNode k1;k1 = k2.left;k2.left = k1.right;k1.right = k2;k2.height = Math.max(height(k2.left), height(k2.right)) + 1;k1.height = Math.max(height(k1.left), k2.height) + 1;return k1;}// 右旋转public TreeNode rightRotation(TreeNode k1) {TreeNode k2;k2 = k1.right;k1.right = k2.left;k2.left = k1;k1.height = Math.max(height(k1.left), height(k1.right)) + 1;k2.height = Math.max(height(k2.right), k1.height) + 1;return k2;}// LR: 左右对应的情况(左双旋转)。public TreeNode leftRightRotation(TreeNode k1) {k1.left = rightRotation(k1.left);return leftRotation(k1);}
4)RL平衡旋转(先右后左旋转)
RL 是指在节点A 右孩子(R)的左子树(L)插入了新节点,导致A节点失衡的节点是A节点的右子树的左子树。
A的平衡因子由-1减至-2,导致以A为根的子树失去平衡,对于RL 型,需要先执行右旋转再执行左旋转操作。
先将A结点的右孩子B的左子树的根结点C向右上旋转提升到B结点的位置,然后再把该C结点向左上旋转提升到A结点的位置。
如上图所示,左中右三个二叉树依次是:插入节点前的平衡二叉树、插入节点后失衡的二叉树、旋转后恢复平衡的二叉平衡树。
两次旋转的拆解过程如下:
// 左旋转public TreeNode leftRotation(TreeNode k2) {TreeNode k1;k1 = k2.left;k2.left = k1.right;k1.right = k2;k2.height = Math.max(height(k2.left), height(k2.right)) + 1;k1.height = Math.max(height(k1.left), k2.height) + 1;return k1;}// 右旋转public TreeNode rightRotation(TreeNode k1) {TreeNode k2;k2 = k1.right;k1.right = k2.left;k2.left = k1;k1.height = Math.max(height(k1.left), height(k1.right)) + 1;k2.height = Math.max(height(k2.right), k1.height) + 1;return k2;}// RL: 右左对应的情况(右双旋转)。public TreeNode rightLeftRotation(TreeNode k1) {k1.right = leftRotation(k1.right);return rightRotation(k1);}
4.删除
从AVL树中删除,可以通过把要删除的节点向下旋转成一个叶子节点,接着直接移除这个叶子节点来完成。因为在旋转成叶子节点期间最多有log n个节点被旋转,而每次AVL旋转耗费固定的时间,所以删除处理在整体上耗费O(log n) 时间。
// 删除结点(z),返回根节点 public void remove(int val) {TreeNode z; if ((z = get(root, val)) != null)root = remove(root, z);}private TreeNode remove(TreeNode tree, TreeNode z) {// 根为空 或者 没有要删除的节点,直接返回null。if (tree==null || z==null)return null;if (z.val < tree.val) { // 待删除的节点在"tree的左子树"中tree.left = remove(tree.left, z);// 删除节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.right) - height(tree.left) == 2) {TreeNode r = tree.right;if (height(r.left) > height(r.right))tree = rightLeftRotation(tree);elsetree = rightRightRotation(tree);}} else if (z.val > tree.val) { // 待删除的节点在"tree的右子树"中tree.right = remove(tree.right, z);// 删除节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.left) - height(tree.right) == 2) {TreeNode l = tree.left;if (height(l.right) > height(l.left))tree = leftRightRotation(tree);elsetree = leftLeftRotation(tree);}} else { // tree是对应要删除的节点。// tree的左右孩子都非空if ((tree.left!=null) && (tree.right!=null)) {if (height(tree.left) > height(tree.right)) {// 如果tree的左子树比右子树高;// 则(01)找出tree的左子树中的最大节点(直接前驱)// (02)将该最大节点的值赋值给tree。// (03)删除该最大节点。// 这类似于用"tree的左子树中最大节点"做"tree"的替身;TreeNode max = getMaxNode(tree.left);tree.val = max.val;tree.left = remove(tree.left, max);} else {// 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)// 则(01)找出tree的右子树中的最小节点(直接后继)// (02)将该最小节点的值赋值给tree。// (03)删除该最小节点。// 这类似于用"tree的右子树中最小节点"做"tree"的替身;TreeNode min = getMinNode(tree.right);tree.val = min.val;tree.right = remove(tree.right, min);}} else {TreeNode tmp = tree;tree = (tree.left!=null) ? tree.left : tree.right;tmp = null;}}return tree;}// 查找最小结点: 返回tree为根结点的AVL树的最小结点。private TreeNode getMinNode(TreeNode tree) {if (tree == null)return null;while(tree.left != null)tree = tree.left;return tree;}// 查找最大结点: 返回tree为根结点的AVL树的最大结点。private TreeNode getMaxNode(TreeNode tree) {if (tree == null)return null;while(tree.right != null)tree = tree.right;return tree;}
三、完整代码
package tree;import java.util.LinkedList;
import java.util.Queue;public class AVLTree {TreeNode root; // 新建根节点public class TreeNode {int val;TreeNode left;TreeNode right;TreeNode() {}int height;TreeNode(int val) {this.val = val;}TreeNode(int val, TreeNode left, TreeNode right) {this.val = val;this.left = left;this.right = right;}}/** 获取树的高度*/private int height(TreeNode tree) {if (tree != null)return tree.height;return 0;}/** 左旋转** 返回值: 旋转后的根节点*/public TreeNode leftRotation(TreeNode k2) {TreeNode k1;k1 = k2.left;k2.left = k1.right;k1.right = k2;k2.height = Math.max(height(k2.left), height(k2.right)) + 1;k1.height = Math.max(height(k1.left), k2.height) + 1;return k1;}/** 右旋转** 返回值: 旋转后的根节点*/public TreeNode rightRotation(TreeNode k1) {TreeNode k2;k2 = k1.right;k1.right = k2.left;k2.left = k1;k1.height = Math.max(height(k1.left), height(k1.right)) + 1;k2.height = Math.max(height(k2.right), k1.height) + 1;return k2;}/** LL: 左左对应的情况(左单旋转)。** 返回值: 旋转后的根节点*/public TreeNode leftLeftRotation(TreeNode k1) {return leftRotation(k1);}/** RR: 右右对应的情况(右单旋转)。** 返回值: 旋转后的根节点*/public TreeNode rightRightRotation(TreeNode k1) {return rightRotation(k1);}/** LR: 左右对应的情况(左双旋转)。** 返回值: 旋转后的根节点*/public TreeNode leftRightRotation(TreeNode k1) {k1.left = rightRotation(k1.left);return leftRotation(k1);}/** RL: 右左对应的情况(右双旋转)。* 返回值: 旋转后的根节点*/public TreeNode rightLeftRotation(TreeNode k1) {k1.right = leftRotation(k1.right);return rightRotation(k1);}public void insert(int val) {root = insert(root, val);}/* * 将结点插入到AVL树中,并返回根节点** 参数说明: * tree AVL树的根结点* key 插入的结点的键值* 返回值: * 根节点*/private TreeNode insert(TreeNode tree, int val) {if (tree == null) {// 新建节点tree = new TreeNode(val);if (tree==null) {System.out.println("ERROR: create avltree node failed!");return null;}} else {if (val< tree.val) { // 应该将key插入到"tree的左子树"的情况tree.left = insert(tree.left, val);// 插入节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.left) - height(tree.right) == 2) {if (val < tree.left.val)tree = leftLeftRotation(tree);elsetree = leftRightRotation(tree);}} else if (val > tree.val) { // 应该将key插入到"tree的右子树"的情况tree.right = insert(tree.right, val);// 插入节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.right) - height(tree.left) == 2) {if (val > tree.right.val)tree = rightRightRotation(tree);elsetree = rightLeftRotation(tree);}} else { // val == tree.valSystem.out.println("添加失败: 不允许添加相同的节点!");}}tree.height = Math.max( height(tree.left), height(tree.right)) + 1;return tree;}// 查找与val数值相等的节点public TreeNode get(int val) {return get(root, val);}public TreeNode get(TreeNode currentNode, int val) {TreeNode findTreeNode = new TreeNode();while (currentNode != null) {if (currentNode.val > val) { // 当前节点比将要插入的值大currentNode = currentNode.left; // 去根节点左子树中继续寻找} else if (currentNode.val < val) { // 当前节点比将要插入的值小currentNode = currentNode.right; // 去根节点右子树中继续寻找} else if (currentNode.val == val) {findTreeNode = currentNode; //相等,则currentNode是要寻找的节点。System.out.println(findTreeNode.val + " has been found!");return findTreeNode;}}System.out.println("Error! " + val + " does not exist!");return null;}public void remove(int val) {TreeNode z; if ((z = get(root, val)) != null)root = remove(root, z);}/* * 删除结点(z),返回根节点*/private TreeNode remove(TreeNode tree, TreeNode z) {// 根为空 或者 没有要删除的节点,直接返回null。if (tree==null || z==null)return null;if (z.val < tree.val) { // 待删除的节点在"tree的左子树"中tree.left = remove(tree.left, z);// 删除节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.right) - height(tree.left) == 2) {TreeNode r = tree.right;if (height(r.left) > height(r.right))tree = rightLeftRotation(tree);elsetree = rightRightRotation(tree);}} else if (z.val > tree.val) { // 待删除的节点在"tree的右子树"中tree.right = remove(tree.right, z);// 删除节点后,若AVL树失去平衡,则进行相应的调节。if (height(tree.left) - height(tree.right) == 2) {TreeNode l = tree.left;if (height(l.right) > height(l.left))tree = leftRightRotation(tree);elsetree = leftLeftRotation(tree);}} else { // tree是对应要删除的节点。// tree的左右孩子都非空if ((tree.left!=null) && (tree.right!=null)) {if (height(tree.left) > height(tree.right)) {// 如果tree的左子树比右子树高;// 则(01)找出tree的左子树中的最大节点// (02)将该最大节点的值赋值给tree。// (03)删除该最大节点。// 这类似于用"tree的左子树中最大节点"做"tree"的替身;// 采用这种方式的好处是: 删除"tree的左子树中最大节点"之后,AVL树仍然是平衡的。TreeNode max = getMaxNode(tree.left);tree.val = max.val;tree.left = remove(tree.left, max);} else {// 如果tree的左子树不比右子树高(即它们相等,或右子树比左子树高1)// 则(01)找出tree的右子树中的最小节点// (02)将该最小节点的值赋值给tree。// (03)删除该最小节点。// 这类似于用"tree的右子树中最小节点"做"tree"的替身;// 采用这种方式的好处是: 删除"tree的右子树中最小节点"之后,AVL树仍然是平衡的。TreeNode min = getMinNode(tree.right);tree.val = min.val;tree.right = remove(tree.right, min);}} else {TreeNode tmp = tree;tree = (tree.left!=null) ? tree.left : tree.right;tmp = null;}}return tree;}// 查找最小结点: 返回tree为根结点的AVL树的最小结点。private TreeNode getMinNode(TreeNode tree) {if (tree == null)return null;while(tree.left != null)tree = tree.left;return tree;}// 查找最大结点: 返回tree为根结点的AVL树的最大结点。private TreeNode getMaxNode(TreeNode tree) {if (tree == null)return null;while(tree.right != null)tree = tree.right;return tree;}// 树的先序遍历public void preOrderTraversal (TreeNode root) {if (root == null) {return;}System.out.print(root.val + " "); //先输出当前节点(初始的时候是root节点)preOrderTraversal(root.left); // 如果左子节点不为空,则递归继续前序遍历preOrderTraversal(root.right); // 如果右子节点不为空,则递归继续前序遍历}// 树的中序遍历public void inOrderTraversa (TreeNode root) {if (root == null) {return;}inOrderTraversa(root.left); // 如果当前节点的左子节点不为空,则递归中序遍历System.out.print(root.val + " "); // 输出当前节点inOrderTraversa(root.right); // 如果当前的右子节点不为空,则递归中序遍历 }// 树的后序遍历public void postOrderTraversal (TreeNode root) {if (root == null) {return;}postOrderTraversal(root.left); // 如果当前节点的左子节点不为空,则递归后序遍历postOrderTraversal(root.right); // 如果当前节点的右子节点不为空,则递归后序遍历System.out.print(root.val + " "); // 输出当前节点 }// 广度优先遍历,即树的层次遍历,借用队列实现public void levelOrderTraversal(TreeNode root) {if(root == null) {return;} Queue<TreeNode> queue = new LinkedList<TreeNode>(); // 存放每层操作的根节点queue.offer(root); while (!queue.isEmpty()) {int queueSize = queue.size();for (int i = 0; i < queueSize; i++) { // 用for循换可以隔离开每一层的遍历TreeNode rootNode = queue.poll(); // 开始操作后将其从队列移除System.out.print(rootNode.val + " ");if (rootNode.left != null) {TreeNode leftNode = rootNode.left; // 左节点存入队列,下一层遍历它就成了新根节点 queue.offer(leftNode);}if (rootNode.right != null) {TreeNode rightNode = rootNode.right; // 右节点存入队列,下一层遍历它就成了新根节点queue.offer(rightNode);}}}}public static void main(String[] args) {AVLTree avlTree = new AVLTree();int arr[]= {3,2,1,4,5,6,7,16,15,14,13,12,11,10,8,9};for (int i = 0; i < arr.length; i++) {avlTree.insert(arr[i]);}avlTree.preOrderTraversal(avlTree.root);System.out.println();avlTree.inOrderTraversa(avlTree.root);System.out.println();avlTree.postOrderTraversal(avlTree.root);System.out.println();avlTree.levelOrderTraversal(avlTree.root);System.out.println();avlTree.get(3);System.out.println();avlTree.remove(1);System.out.println();avlTree.inOrderTraversa(avlTree.root);}
}