平衡二叉树是二叉搜索树的一个变种,一棵完全偏向一边的搜索树在一定程度上会影响查找性能。 然而历史上总是不缺神,一些追求极致、完美(强迫症)的神就想出了平衡二叉树。
本次仅实现插入、按层级展示两个功能。
安利 此文很不错,特别是自带动画效果!! http://blog.csdn.net/eson_15/article/details/51144079
这个简明扼要!!! http://www.cnblogs.com/skywang12345/p/3577479.html
网易公开课《数据结构》陈越、何钦铭中关于平衡二叉树的讲解: http://mooc.study.163.com/course/ZJU-1000033001?tid=1000044001#/info
如果一棵树非空,其任意一个节点的左右子树之间的高度差的绝对值不超过1(<=1),那么其就是平衡二叉树。
左右子树的高度差的绝对值称为平衡因子判断是否为平衡二叉树
其他节点无需再判断,图1不是平衡二叉树。
根节点 左子树高度为1,右子树高度为2。 平衡因子为1 = 1,暂时满足要求(记住定义为任意一个节点)。
节点1 左子树高度为0,右子树高度为0, 平衡因子为0 < 1,满足要求。
节点3 左子树高度为0,右子树高度为1, 平衡因子为1 = 1,满足要求。
节点4 左子树高度为0,右子树高度为0, 平衡因子为0 < 1,满足要求。
所有节点判断完毕。可以断定,图2的树就是平衡二叉树。
在普通的二叉搜索树中插入节点时,我们只通过大小来判断位置,不管树的形态。
在平衡二叉树中,我们则需要
插入节点时,判断节点的位置插入节点后,确认任意一个节点平衡因子还是<=1如果出现了偏差,那么修正树的形态平衡因子 = |节点的左子树的高度 - 节点的右子树高度| ||:绝对值
你听说过深度优先遍历吗?
private int getHeightRecursion(TreeNode root) { if (root == null) { return 0; } int left = getHeightRecursion(root.left); int right = getHeightRecursion(root.right); return (left > right ? left : right) + 1; }先进行左优先遍历,再进行右优先遍历。
取最大值是怕出现没左节点,有右节点的情况,0:1总不能取0啊。
从最后一层回到首层,每次都自增一层。
最后结果就是深度。
下面可能是废话:
从根节点进入到最后一个左节点,到达后返回,每次自增。
再进入最后一个右节点,到达后返回,每次自增。
到达最后一层后返回时,可能会出现[有]左子节点(left=1),[无]右子节点的情况(right=0)。
因此,整个递归期间,左右的高度都有可能不同。
所以需要取最大值。
+1表示深度增加一层。
如果只有一个根节点,高度为1。
理解了递归探索子树深度,来看看AVL树中可以怎么玩。
让节点自带高度属性,插入操作时进行记录,那么每次判断平衡因子就不用进行额外的递归了。
//节点 class TreeNode<T extends Comparable<T>> { private T data; private TreeNode left; private TreeNode right; //用于记录高度 private int height; TreeNode(T data) { this.data = data; } } //获取高度 private int getHeightRecord(TreeNode tree) { if (tree != null) { return tree.height; } return 0; } //插入操作 private TreeNode insertNode(TreeNode<T> root, T data) { if (root == null) { root = new TreeNode<>(data); } else { int cmp = data.compareTo(root.data); if (cmp < 0) { root.left = insertNode(root.left, data); } else if (cmp > 0) { root.right = insertNode(root.right, data); } else { System.out.println("已存在相同节点"); } } root.height = Math.max(getHeightRecord(root.left), getHeightRecord(root.right)) + 1; return root; }原理和递归探索一样,多了记录这一操作。
插入节点时,我们从根节点开始,搜索到合适的位置插入。【即到达最深处】
插入完成后。 [记录]新插入的节点高度为1。
回退到上一层。
由于可能新增了节点,所以更新这一层的原来的记录。
通过节点的height属性获取左右节点的高度,取最大值。
加上自己本身高度1,即为新高度。重新记录。
这是AVL树中最棘手的问题,一旦平衡状态被破坏,我们通过四种旋转来修正树的形态。分别为:LL旋转、RR旋转、LR旋转、RL旋转。
其中L表示left,R表示right。
我们知道当某个节点的平衡因子到达2时,树就已经失去平衡了。 所以每当一个节点的平衡因子超过1时,我称该节点为【被破坏节点】。
以下是个人理解:
而这些LL、RR、LR、RL都是针对【被破坏节点】的。
例如LL,说的是【被破坏节点】的【左子节点】的【左子节点】 RL,说的是【被破坏节点】的【右子节点】的【左子节点】。
切记 从字面意思来理解旋转。 围着一个指定的节点旋转。 当我说提升某个节点的时候,其实就是围绕那个节点旋转。
下面进入正题,四大旋转。
围绕【被破坏节点】的【左子节点】向右旋转。
情况1:
修正方法: 将【被破坏节点】的【左子节点】提升为根节点。
情况2:
修正方法: 同样是将【被破坏节点】的【左子节点】提升为根节点。
但重点关注的是【被破坏节点】的【左子节点】的【右子节点】 如上图中的【节点8】,称为【左右节点】。
如果直接提升根节点。此时根节点会有三个分支,这是不可取的。
所以将【节点8】移动,变为【被破坏节点】的【左子节点】。 (因为【左右节点】肯定比【被破坏节点】小,又肯定比【被提升节点】大)
图片示例:
上图说明在情况2里,新插入的节点在【左左节点】的左边还是右边都不影响修正步骤。 【节点8】在修正后移动到了【被破坏节点】的左边上面提到的提升为根节点可能造成混淆,可以理解成提升为【被破坏节点】的【父节点】。
综合2种情况:
1. 先将【被破坏节点】的【左子节点】提升为成【父节点】 2. 【被破坏节点】移动到【被提升节点】的右边。(绕着父节点向右旋转) 3. 最后将原来【被提升节点】的【右子节点】移动到【被破坏节点】的左边。(指针跟着向右旋转) private TreeNode singleLeftRotation(TreeNode tree) { //将被破坏节点的左子节点提升为根节点 TreeNode root = tree.left; //如果新根节点的右边不为空,把它放进被破坏节点的左边 tree.left = root.right == null ? null : root.right; //被破坏节点放进新根节点的右边 root.right = tree; return root; }围绕【被破坏节点】的【右子节点】向左旋转。
情况1:
修正方法: 将【被破坏节点】的【右子节点】提升为根节点即可。
情况2:
修正方法: 将【被破坏节点】的【右子节点】提升为根节点。 根节点会有三个分支,这是不可取的。
所以在提升后,将【右子节点】的左分支移动到【被破坏节点】的右边。 (原因是【右左节点】肯定比【被破坏节点】大,又肯定比【被提升节点】小)
如上图,【右左节点】6被移动到【被破坏节点】5的右边综合2种情况:
1. 先将【被破坏节点】的【右子节点】提升为成【父节点】 2. 【被破坏节点】移动到【被提升节点】的左边。(绕着父节点向左旋转) 3. 最后将原来【被提升节点】的【左子节点】移动到【被破坏节点】的右边。(指针跟着向左旋转) private TreeNode singleRightRotation(TreeNode tree) { //将被破坏节点的右子节点提升为根节点 TreeNode root = tree.right; //如果新根节点的左边不为空,转移到被破坏节点的右边 tree.right = root.left == null ? null : root.left; //被破坏节点放进新根节点的左边 root.left = tree; return root; }如上图,在插入【节点7】后,【节点9】被破坏。
且【节点7】是【节点9】的【左子节点】的【右子节点】的子节点之一。
使用LR旋转修正:
private TreeNode doubleLeftRightRotation(TreeNode tree) { tree.left = singleRightRotation(tree.left); return singleLeftRotation(tree); }如上图,在插入【节点9】后,【节点7】被破坏。
且【节点9】是【节点7】的【右子节点】的【左子节点】的子节点之一。
使用RL旋转修正:
private TreeNode doubleRightLeftRotation(TreeNode tree) { tree.right = singleLeftRotation(tree.right); return singleRightRotation(tree); }测试跟实现放在一起了,纯粹就是懒。
import java.util.ArrayDeque; import java.util.ArrayList; public class AVLTree<T extends Comparable<T>> { public static void main(String[] args) { System.out.println("输入:"); int[] nodeValues = {9, 6, 10, 5, 8, 7, 11}; AVLTree<Integer> tree = initTreeAndShowData(nodeValues); System.out.println("输出:"); tree.show(tree.root); System.out.println("输入:"); nodeValues = new int[]{9}; tree = initTreeAndShowData(nodeValues); System.out.println("输出:"); tree.show(tree.root); System.out.println("输入:"); nodeValues = new int[]{9, 3, 2, 1, 13, 80, 54, 26, 90, -3, 70, 24, 27, 8, 10}; tree = initTreeAndShowData(nodeValues); System.out.println("输出:"); tree.show(tree.root); System.out.println("输入:"); nodeValues = new int[]{7, 5, 10, 8, 11, 9}; tree = initTreeAndShowData(nodeValues); System.out.println("输出:"); tree.show2(tree.root); } /** * 将数组内的数据存储进二叉树 * @param nodeValues 需要录入二叉树的数组 * @return 返回已录入数据的二叉树的根节点 */ static AVLTree<Integer> initTreeAndShowData(int[] nodeValues) { AVLTree<Integer> tree = new AVLTree<Integer>(); for (int i = 0; i < nodeValues.length; i++) { tree.insert(nodeValues[i]); System.out.print(nodeValues[i] + " "); } System.out.println(); return tree; } /****************************************************************************/ /** * 二叉树内部节点 */ class TreeNode<T extends Comparable<T>> { private T data; private TreeNode<T> left; private TreeNode<T> right; private int height; TreeNode(T data) { this.data = data; left = null; right = null; } } private TreeNode<T> root; AVLTree() { root = null; } /** * 将给定数据插入节点 * @param data 需要插入树中的节点数据 */ public void insert(T data) { root = insertNode(root, data); } /** * 插入二叉树的内部实现 * @param root 指定的根节点 * @param data 需要插入的数据 * @return 返回插入数据后的树 */ private TreeNode<T> insertNode(TreeNode<T> root, T data) { if (root == null) { root = new TreeNode<>(data); } else { //比较插入数据与当前根节点的大小 int cmp = data.compareTo(root.data); if (cmp < 0) { root.left = insertNode(root.left, data); //判断平衡因子是否超过了1 if (isLargerThanOne(root)) { if (data.compareTo(root.left.data) < 0) { //左单旋 root = singleLeftRotation(root); } else { //左-右双旋 root = doubleLeftRightRotation(root); } } } else if (cmp > 0) { root.right = insertNode(root.right, data); if (isLargerThanOne(root)) { if (data.compareTo(root.right.data) > 0) { //右单旋 root = singleRightRotation(root); } else { //右-左双旋 root = doubleRightLeftRotation(root); } } } else { System.out.println("已存在相同节点"); } } //更新节点高度 root.height = Math.max(getHeightRecord(root.left), getHeightRecord(root.right)) + 1; return root; } private boolean isLargerThanOne(TreeNode tree) { return Math.abs(getHeightRecord(tree.left) - getHeightRecord(tree.right)) > 1; } /** * 将传入节点的左子节点提升为根 * @param tree 被破坏节点 * @return 返回旋转完毕后的子树 */ private TreeNode singleLeftRotation(TreeNode tree) { //将被破坏节点的左子节点提升为根节点 TreeNode root = tree.left; //如果新根节点的右边不为空,把它放进被破坏节点的左边 tree.left = root.right == null ? null : root.right; //被破坏节点放进新根节点的右边 root.right = tree; return root; } /** * 将传入节点的右子节点提升为根 * @param tree 被破坏节点 * @return 返回旋转完毕后的子树 */ private TreeNode singleRightRotation(TreeNode tree) { //将被破坏节点的右子节点提升为根节点 TreeNode root = tree.right; //如果新根节点的左边不为空,转移到被破坏节点的右边 tree.right = root.left == null ? null : root.left; //被破坏节点放进新根节点的左边 root.left = tree; return root; } /** * 处理LR插入 * @param tree 被破坏节点 * @return 返回旋转完毕后的子树 */ private TreeNode doubleLeftRightRotation(TreeNode tree) { tree.left = singleRightRotation(tree.left); return singleLeftRotation(tree); } /** * 处理RL插入 * @param tree 被破坏节点 * @return 返回旋转完毕后的子树 */ private TreeNode doubleRightLeftRotation(TreeNode tree) { tree.right = singleLeftRotation(tree.right); return singleRightRotation(tree); } /** * 获取tree的高度 * @param tree 一个节点 * @return 如果为null,返回0,否则返回高度 */ private int getHeightRecord(TreeNode tree) { if (tree != null) { return tree.height; } return 0; } /** * 按层级显示一棵二叉树,不带高度的二叉树 * @param node 二叉树的根节点 */ public void show(TreeNode<Integer> node) { ArrayDeque<TreeNode> nodes = new ArrayDeque<>(); int i = 0, n = 0; boolean isFirst = true; TreeNode<Integer> t; nodes.add(node); while (nodes.size() != 0) { t = nodes.remove(); System.out.print(t.data + "\t"); if (++i == 2 << n || isFirst) { i = 0; n = isFirst ? n : n + 1; isFirst = false; System.out.println(); } if (t.left != null) { nodes.add(t.left); } if (t.right != null) { nodes.add(t.right); } } System.out.println(); } /** * 按层级显示一棵二叉树,带高度的二叉树 * @param node 二叉树的根节点 */ public void show2(TreeNode<Integer> node) { ArrayList<TreeNode> nodes = new ArrayList<TreeNode>(); TreeNode<Integer> t; nodes.add(0, node); int size = nodes.size(); while (size != 0) { t = nodes.remove(0); if (t.left != null) { nodes.add(t.left); } if (t.right != null) { nodes.add(t.right); } if ((size = nodes.size()) != 0 && t.height == nodes.get(0).height) { System.out.print(t.data + "\t"); } else { System.out.println(t.data); } } } /** * 属性不带高度时,根据根节点判断高度 * @param tree 根节点 * @return 树的高度 */ private int getHeightRecursion(TreeNode tree) { if (tree == null) { return 0; } int left = getHeightRecursion(tree.left); int right = getHeightRecursion(tree.right); return (left > right ? left : right) + 1; } }输出结果按层级展示。
输入: 9 6 10 5 8 7 11 输出: 8 6 10 5 7 9 11 输入: 9 输出: 9 输入: 9 3 2 1 13 80 54 26 90 -3 70 24 27 8 10 输出: 13 3 54 1 9 26 80 -3 2 8 10 24 27 70 90 输入: 7 5 10 8 11 9 输出: 8 7 10 5 9 11