手撕 – AVL树、红黑树
个人主页:顾漂亮
文章专栏:Java数据结构
文章目录
- 手撕 -- AVL树、红黑树
- 1.AVL树
- 1.1AVL树的概念
- 1.2AVL树的性质
- 1.3AVL树的实现 -- Java代码
- 1.4AVL树的性能分析
- 2.红黑树
- 2.1概念
- 2.2红黑树的性质
- 2.3红黑树的实现
- 2.4AVL树和红黑树的比较
- 2.5红黑树的应用
- 2.5红黑树的应用
1.AVL树
1.1AVL树的概念
二叉搜索树虽然可以缩短查找的效率,但是如果**数据有序或者接近有序将退化为单支树,查找元素相当于在顺序表中搜索元素,**效率低下。因此:当向二叉搜索树中插入新节点后,如果能保证每一个节点的左右子树高度只差的绝对值不超过1,即通过旋转降低树的高度,从而减少平均搜索长度
1.2AVL树的性质
- 左右子树都是AVL树
- 左右子树高度差(简称平衡因子)的绝对值不超过1
- AVL是一颗高度平衡的二叉平衡树,如果其有n个节点,其高度可以保持在
logn
,搜索时间复杂度为O(logn)
1.3AVL树的实现 – Java代码
public class AVLTree {//定义节点static class TreeNode{public int val;public TreeNode left;public TreeNode right;public int bf;//平衡因子public TreeNode parent;//父节点public TreeNode(int val) {this.val = val;}}public TreeNode root;public boolean insert(int val){//先按照二叉搜索树的方式插入新节点TreeNode node = new TreeNode(val);if(root == null){root = node;return true;}TreeNode cur = root;TreeNode parent = null;while(cur != null){if(cur.val > val){parent = cur;cur = cur.left;} else if (cur.val == val) {return false;//AVL树中不存在数值相同的节点,如果找到相等节点直接返回false}else{//cur.val < valparent = cur;cur = cur.right;}}if(parent.val > val){parent.left = node;cur = parent.left;}else{parent.right = node;cur = parent.right;}//不要忘了更新新节点的父亲节点node.parent = parent;//调整节点的平衡因子while(parent != null){//更新双亲节点的平衡因子if(cur == parent.right){parent.bf++;} else if (cur == parent.left) {parent.bf--;}//满足AVL树的性质,插入成功if(parent.bf == 0){break;} else if (parent.bf == -1 || parent.bf == 1) {//继续向上查找cur = parent;parent = parent.parent;} else {//平衡因子为2if(parent.bf == 2){if(cur.bf == -1){rotateRL(parent);}else{//左旋rotateL(parent);}}else if (parent.bf == -2){//平衡因子为-2if(cur.bf == -1){//右旋rotateR(parent);}else{rotateLR(parent);}}break;}}return true;}private void rotateRL(TreeNode parent) {//记录TreeNode subR = parent.right;TreeNode subRL = subR.left;int bf = subRL.bf;rotateR(parent.right);rotateL(parent);if(bf == -1){subRL.bf = 0;subR.bf = 1;parent.bf = 0;}else if(bf == 1){subR.bf = 0;parent.bf = -1;subRL.bf = 0;} else if (bf == 0) {subR.bf = 0;parent.bf = 0;subRL.bf = 0;}}private void rotateLR(TreeNode parent) {//记录TreeNode subL = parent.left;TreeNode subLR = subL.right;int bf = subLR.bf;rotateL(parent.left);rotateR(parent);if(bf == -1){subLR.bf = 0;subL.bf = 0;parent.bf = 1;}else if(bf == 1){subL.bf = -1;parent.bf = 0;subLR.bf = 0;} else if (bf == 0) {subLR.bf = 0;subL.bf = 0;parent.bf = 0;}}private void rotateR(TreeNode parent) {TreeNode subL = parent.left;TreeNode subLR = subL.right;parent.left = subLR;if(subLR != null){subLR.parent = parent;}subL.right = parent;TreeNode pParent = parent.parent;parent.parent = subL;if(root == parent){root = subL;root.parent = null;}else{if(parent == pParent.right){pParent.right = subL;}else{pParent.left = subL;}subL.parent = pParent;}parent.bf = subL.bf = 0;}private void rotateL(TreeNode parent) {TreeNode subR = parent.right;TreeNode subRL = subR.left;parent.right = subRL;if(subRL != null){subRL.parent = parent;}subR.left = parent;TreeNode pParent = parent.parent;parent.parent = subR;if(parent == root){root = subR;root.parent = null;}else{if(parent == pParent.right){pParent.right = subR;}else{pParent.left = subR;}subR.parent = pParent;}parent.bf = subR.bf = 0;}//AVL树的验证public void inOrder(TreeNode root){if(root == null){return;}inOrder(root.left);System.out.print(root.val + " ");inOrder(root.right);}//求树的高度public int height(TreeNode root){if(root == null){return 0;}int left = height(root.left);int right = height(root.right);return left > right ? left + 1 : right +1;}//判断树的高度差public boolean isBalance(TreeNode root){if(root == null){return true;}int leftH = height(root.left);int rightH = height(root.right);//检查平衡因子是否有问题if(rightH - leftH != root.bf){System.out.println(root.val + "平衡因子异常");return false;}return Math.abs(leftH-rightH)<=1 && isBalance(root.left) && isBalance(root.right);}
}
注解:
当前节点的平衡因子:当前节点右子树高度 - 左子树高度
1.4AVL树的性能分析
- AVL可以保证高效的查找时间复杂度,为O(logN)
- 如果需要对于AVL树的结构做一些修改的操作,性能非常低下,比如:插入时要维护其绝对平衡,旋转次数比较多,更差的是在删除时,有可能一直要让旋转持续到根的位置。
- 适用场景:适用于需要一种查询高效且有序的数据结构,而且数据的个数是静态的,可以考虑AVL树,但是一个结构需要经常修改,就不太合适
2.红黑树
2.1概念
红黑树,是一种二叉搜索树,但每个节点上增加一个存储位表示节点的颜色,可以是RED或者BLACK。通过对任何一条从根到叶子的路径上各个节点的着色方式的限制,红黑树确保没有一条路径会比其他路径长出两倍,因而是接近平衡的
2.2红黑树的性质
- 最长路径最多是最短路径的二倍
- 每一个节点不是红色就是黑色
- 根节点是黑色的
- 如果一个节点是红色的,则它的两个孩子节点是黑色的
没有两个连续的红色节点
- 对于每一个节点,从该节点到其所有后代叶节点的简单路径上,均包含
相同数目的黑色节点
- 每一个叶子节点都是黑色的(此处叶子节点指的是空节点)
注意:一般情况下,一个正常的红黑树不会出现上述情况,可以用来验证性质1
2.3红黑树的实现
//定义一个枚举
public enum Color {RED,BLACK
}
public class RBTree {//红黑树节点static class RBTreeNode{public int val;public RBTreeNode left;public RBTreeNode right;public RBTreeNode parent;Color color;//节点默认为红色,若为黑色,容易产生不平衡因素--需要新增节点public RBTreeNode(int val) {this.val = val;this.color = Color.RED;}}public RBTreeNode root;public boolean insert(int val){RBTreeNode node = new RBTreeNode(val);if(root == null){root = node;root.color = Color.BLACK;//注意将节点置为黑色,根节点return true;}RBTreeNode cur = root;RBTreeNode parent = null;while(cur != null){if(cur.val > val){parent = cur;cur = cur.left;}else if(cur.val < val){parent = cur;cur = cur.right;}else{return false;//红黑树中不存在相同节点}}if(parent.val > val){parent.left = node;node.parent = parent;//不要忘记确定parent的指向} else if (parent.val < val) {parent.right = node;node.parent = parent;}cur = node;//cur指向新插入的节点//插入节点默认为红色,主要是看插入节点的父节点是否为红色while(parent != null && parent.color == Color.RED){RBTreeNode grandFather = parent.parent;//若满足parent为红色,则节点一定存在if(parent == grandFather.left){RBTreeNode uncle = grandFather.right;//情况1if(uncle!= null && uncle.color == Color.RED){parent.color = Color.BLACK;uncle.color = Color.BLACK;grandFather.color = Color.RED;//判断parent是否为空,即grandparent是否就等于rootif(grandFather == root){grandFather.color = Color.BLACK;//将根节点置为空break;}//继续向上修改cur = grandFather;parent = cur.parent;}else{//情况3if(parent.right == cur){rotateL(parent);//交换RBTreeNode tmp = cur;cur = parent;parent = tmp;}//情况2rotateR(grandFather);parent.color = Color.BLACK;grandFather.color = Color.RED;}}else{//parent == grandFather.rightRBTreeNode uncle = grandFather.left;if(uncle != null && uncle.color == Color.RED){uncle.color = parent.color = Color.BLACK;grandFather.color = Color.RED;if(grandFather == root){grandFather.color = Color.BLACK;}cur = grandFather;parent = cur.parent;}else{//情况3if (cur == parent.right){rotateR(parent);//交换RBTreeNode tmp = cur;cur = parent;parent = tmp;}//情况2rotateL(grandFather);grandFather.color = Color.RED;parent.color = Color.BLACK;}}}return true;}private void rotateR(RBTreeNode parent) {RBTreeNode subL = parent.left;RBTreeNode subLR = subL.right;parent.left = subLR;if(subLR != null){subLR.parent = parent;}subL.right = parent;RBTreeNode pParent = parent.parent;parent.parent = subL;if(root == parent){root = subL;root.parent = null;}else{if(parent == pParent.right){pParent.right = subL;}else{pParent.left = subL;}subL.parent = pParent;}}private void rotateL(RBTreeNode parent) {RBTreeNode subR = parent.right;RBTreeNode subRL = subR.left;parent.right = subRL;if(subRL != null){subRL.parent = parent;}subR.left = parent;RBTreeNode pParent = parent.parent;parent.parent = subR;if(parent == root){root = subR;root.parent = null;}else{if(parent == pParent.right){pParent.right = subR;}else{pParent.left = subR;}subR.parent = pParent;}}//中序遍历public void inOrder(RBTreeNode root){if(root == null){return;}inOrder(root.left);System.out.print(root.val + " ");inOrder(root.right);}//检测其是否满足红黑树的性质public boolean isValidRBTree(RBTreeNode root) {//如果是空树,满足if (root == null) {return true;}//根节点不是黑色的,不是红黑树if (root.color != Color.BLACK) {System.out.println("违反了红黑树的性质:根节点不是黑色");return false;}//获取单条路径中黑色节点的个数RBTreeNode cur = root;int blackCount = 0;while (cur != null) {if (cur.color == Color.BLACK) {blackCount++;}cur = cur.left;}return _isValidRBtree(root, 0, blackCount);}public boolean _isValidRBtree(RBTreeNode root, int pathCount, int blackCount){if(root == null){return true;}//遇到黑色的,pathCount++if(root.color == Color.BLACK){pathCount++;}RBTreeNode parent = root.parent;if(parent != null && parent.color == Color.RED && root.color == Color.RED){System.out.println("有连在一起的红色节点");return false;}if(root.left == null && root.right == null){if(pathCount != blackCount){System.out.println("路径中黑色节点数量不一致");return false;}}return _isValidRBtree(root.left, pathCount, blackCount) && _isValidRBtree(root.right, pathCount, blackCount);}
}
2.4AVL树和红黑树的比较
树类型 | AVL树 | 红黑树 |
---|---|---|
查找效率 | O(logN) | O(logN) |
特点 | 追求绝对平衡,插入、删除需要进行大量旋转 | 不追求绝对平衡,相对而言降低了插入和旋转的次数 |
在增删结构中性能比较 | 相对较低 | 更高的效率 |
2.5红黑树的应用
树类型 | AVL树 | 红黑树 |
---|---|---|
查找效率 | O(logN) | O(logN) |
特点 | 追求绝对平衡,插入、删除需要进行大量旋转 | 不追求绝对平衡,相对而言降低了插入和旋转的次数 |
在增删结构中性能比较 | 相对较低 | 更高的效率 |
2.5红黑树的应用
Java集合框架中:TreeMap、TreeMap底层使用的就是红黑树