咱们从一个数列(1,2,3,4,5,6)
,来讲明构成二叉排序树的一些问题node
1.左子树所有为空, 从形式图所看,更像一个单链表
2.查询速度明显下降(由于须要依次比较),不能发挥BST
的优点,由于每次还须要比较左子树,其查询速度比单链表还慢算法
那么怎么办?ide
那么像这样的数列咱们能够是用解决方案--->平衡二叉树(AVL)
性能
1.平衡二叉树也叫平衡二叉搜索树(Self balancing binary searchtree)又被称为AVL树
, 能够保证查询效率较高。测试
有如下特色:
1.它是一颗空树或它的左右两个子树的高度差绝对超过1
2.左右两个子树都是一棵平衡二叉树。优化
平衡二叉树的经常使用实现:红黑树、AVL(算法)、替罪羊树、Treap、伸展树
等。this
结合前面咱们介绍的AVL树特色分析看看,如今你知道了吗?spa
平衡二叉树之因此将二叉排序树,调整为平衡状态,是为了在二叉排序树近似为链的状况下,加强其查找性能,下降时间复杂度。3d
常见的二叉平衡树调整平衡方法有:LL、LR、RR、RL
code
RR型介绍
当前这种状况图三,链式就须要平衡调整,不然则影响到查询的效率
平衡调整:一个根节点与两左右子节点的
二叉排序树(以下图)
原本Mar节点再插入May时,还保持平衡:知足右子节点大于根节点
当插入麻烦节点Nov
时平衡点被破坏,且Nov节点是在根节点的右子树的右子树上
根据二叉排序的特性:右子树节点比当前节点大
咱们找到Mar、May、Nov的中间数
,它们的大小关系是Mar<May<Nov
此时将May做为根节点
,Mar做为左子树、Nov做为右子树
进行调整
这时,这种插入即称呼为RR插入
,平衡调整也成为RR旋转(右单转)
LL型介绍
当插入麻烦节点Apr
时平衡点被破坏,且Apr节点是在Mar节点的左子树的左子树上
咱们找到Mar、Aug、Apr的中间数
,它们的大小关系是Apr<Aug<Mar
此时将Aug做为根节点
,Apr做为左子树、Mar做为右子树
进行调整
这时,这种插入即称呼为LL插入
,平衡调整也成为LL旋转(左单转)
固然插入的节点也多是左子节点或者右子节点
咱们发现其实进行旋转调整的时候呢
不必定是根节点才进行旋转。在中间的节点Mar也是能够的
LR介绍
当插入麻烦节点Jan
时平衡点被破坏,且Jan节点是在May节点的左子树的右子树上
咱们找到May、Aug、Mar的中间数
,它们的大小关系是Aug<Mar<May
此时将Mar做为根节点
,Aug做为左子树、May做为右子树
且Mar>Aug
、Jan<Mar
根据特性Jan做为Aug右子树
进行调整
这时,这种插入即称呼为LR插入
,平衡调整也成为LR旋转
RL介绍
当插入麻烦节点Feb
时平衡点被破坏,且Feb节点是在Aug节点的右子树的左子树上
咱们找到Jan、Aug、Dec的中间数
,它们的大小关系是Aug<Dec<Jan
此时将Dec做为根节点
,Aug做为左子树、Jan做为右子树
且Jan>Dec
、Feb>Dec
根据特性Feb做为Jan左子树
进行调整
这时,这种插入即称呼为RL插入
,平衡调整也成为RL旋转
即看插入节点把谁破坏了,跟被破坏节点是什么关系?
是左边的左边?右边的右边?仍是左边的右边?右边的左边?
可是须要注意的是:平衡调整后仍为二叉排序树
给你一个数列{4,3,6,5,7,8}
,让你可以高效
的完成对数据的查询和添加
那么按照咱们以前的思路,先构建:一颗二叉排序树
非叶子节点特色:左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
特别说明:若是有相同的值
,能够将该节点放在左子节点或右子节点
那么咱们前面分析了在二叉排序树近似为链的状况下
1.从形式图所看,更像一个单链表
2.查询速度明显下降(由于须要依次比较),不能发挥BST的优点
因此咱们须要进行调整:平衡状态加强其查找性能,下降时间复杂度
咱们发现这颗二叉排序树左子树高度为1,右边子树高度为3
此时不符合平衡二叉树的特色:它是一颗空树或它的左右两个子树的高度差绝对超过1
根据上面介绍的四种平衡调整模式介绍,目前比较符合的是RR旋转
咱们结合上面的图与RR旋转的思路一块儿来分析当前的案例
未插入节点8时,还保持平衡:知足右子节点大于根节点
当插入麻烦节点8
时平衡点被破坏,且节点8是在根节点的右子树的右子树上
根据二叉排序的特性:右子树节点比当前节点大
咱们找到四、六、7的中间数
,它们的大小关系是 4 < 6 < 7
此时将 6 做为根节点
, 4 做为左子树、 7 做为右子树
进行调整
右子树
设置为当前根节点root的右子树的左子树
右子树的右子树
你们有没有发现,节点与子树之间的高度是关键的
并非说加入一个节点得时候就进行旋转,而是左右两个子树的高度差绝对超过1才去进行平衡调整
因此须要先完成事情是:统计当前树的高度、统计与左子树、或右子树的高度
那么咱们用代码实践来统计:树的高度、左子树高度、右子树高度
(节点代码、AVL代码可参考二叉排序树相关代码)
class Node{ int value; Node left; Node right; public Node(int value) { this.value = value; } /** * @param value 但愿删除的结点的值 * @return若是找到返回该结点,不然返回null */ public Node search(int value) { if(value == this.value) { //找到就是该结点 return this; } else if(value < this.value) {//若是查找的值小于当前结点,向左子树递归查找 //若是左子结点为空 if(this.left == null) { return null; } return this.left.search(value); } else { //若是查找的值不小于当前结点,向右子树递归查找 if(this.right == null) { return null; } return this.right. search(value); } } public Node searchParent(int value){ //若是当前节点是须要删除节点的父节点则返回 if((this.left!=null && this.left.value == value) || (this.right!=null && this.right.value == value)){ return this; }else{ //若是查找的值小于当前节点的值,而且当前节点的左子节点不为空 if(value <this.value && this.left!=null){ return this.left.searchParent(value); }else if(value >= this.value && this.right!=null){ //若是查找的值大于等于于当前节点的值,而且当前节点的右子节点不为空 return this.right.searchParent(value); }else { return null;//没有找到父节点,好比说节点7 } } } //添加节点方法 //递归方式添加节点,要知足二叉排序树的要求 //要求是:`左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。` public void add(Node node) { if (node == null) { return; } //判断传入的节点的值,和当前节点值的关系 //添加的节点小于当前节点 if (node.value < this.value) { if (this.left == null) { this.left = node; } else { this.left.add(node); } } else {//添加的节点大于当前节点 if (this.right == null) { this.right = node; } else { this.right.add(node); } } } @Override public String toString() { return "Node{" +"value=" + value +'}'; } //中序遍历 public void infixOrder(){ if(this.left != null){ this.left.infixOrder(); } System.out.println(this); if(this.right != null){ this.right.infixOrder(); } } }
class AVLTree { private Node root; public Node getRoot() { return root; } public void setRoot(Node root) { this.root = root; } //添加节点的方法 public void add(Node node){ if(root == null){ root = node; }else{ root.add(node); } } /** * @param node 传入的节点(当作新二叉排序树的根节点) * return 返回新跟节点的最小节点的值 */ public int delRigthTreeMin(Node node){ Node target = node; //循环的查找左子节点,找到最小值 while(target.left!=null) { target = target.left; } //删除最小值 delNode(target.value); //返回最小值 return target.value; } public void delNode(int value){ if(root == null){ System.out.println("当前根节点为空!没法删除节点!"); return; }else{ //1.须要先找到删除的值的对应节点 Node targetNode = search(value); //若是没有找到须要删除的节点 if(targetNode == null){ System.out.println("对不起!没有找到删除节点信息!"); return; } //若是咱们发现根节点没有左子节点与右子节点 if(root.left == null && root.right == null){ root = null; return; } //找到targetNode 的父节点 Node parent = searchParent(value); //若是删除节点是叶子节点 if(targetNode.left == null && targetNode.right == null){ //判断删除节点是父节点的左子节点仍是右子节点 if(parent.left!= null && parent.left.value == targetNode.value){ parent.left = null; }else if (parent.right!= null && parent.right.value == targetNode.value){ parent.right = null; } }else if (targetNode.left != null && targetNode.right != null){ int minValue = delRigthTreeMin(targetNode.right); targetNode.value = minValue;//重置值 }else{//删除只有一颗子树的节点 //若是删除节点的子节点是左子节点 if(targetNode.left !=null){ if(parent!=null){ //判断删除节点是父节点的左子节点仍是右子节点 if(parent.left.value == targetNode.value){ //将原删除节点的位置给到子节点 parent.left = targetNode.left; }else if (parent.right.value == targetNode.value){ //将原删除节点的位置给到子节点 parent.right = targetNode.left; } }else{ root = targetNode.left; } }else if (targetNode.right != null){ //若是删除节点的子节点是右子节点 if(parent!=null){ //判断删除节点是父节点的左子节点仍是右子节点 if( parent.left.value == targetNode.value){ //将原删除节点的位置给到子节点 parent.left = targetNode.right; }else if (parent.right.value == targetNode.value){ //将原删除节点的位置给到子节点 parent.right = targetNode.right; } }else{ root = targetNode.right; } } } } } //查找须要删除节点的方法 public Node search(int value){ if(root == null){ return null; }else{ return root.search(value); } } //查找须要删除节点的父节点信息 public Node searchParent(int value){ if(root == null){ return null; }else{ return root.searchParent(value); } } //调用中序遍历的方法 public void infixOrder(){ if(root == null){ System.out.println("当前二叉排序根节点为空,没法遍历"); return; }else{ root.infixOrder(); } } }
如上图所示,咱们取的该这颗树的高度是为:3 ,算上根节点则是 4
因此咱们求该树的高度,须要知道左节点与右节点的高度分别是多少
class Node{ //......省略其余关键代码 //返回当前节点的左子树的高度 public int leftHigth(){ if(left == null){ return 0; } return left.hight(); } //返回当前节点的右子树的高度 public int rightHight(){ if(right == null){ return 0; } return right.hight(); } //返回当前节点的高度,若算上根节点则须要 + 1 public int hight(){ return Math.max(left == null? 0 : left.hight(),right == null?0:right.hight()) + 1; } }
有没有发现,咱们须要知道左子树的高度是多少
同时也须要知道左子树的左子树与右边子树是多少.....
这是一个一直递归的过程。
public static void main(String[] args) { int[] arr ={4,3,6,5,7,8}; AVLTree avlTree = new AVLTree(); for(int i = 0; i<arr.length; i++){ avlTree.add(new Node(arr[i])); } //遍历 System.out.println("中序遍历"); avlTree.infixOrder(); //节点高度 System.out.println("算上跟节点高度为:"+avlTree.getRoot().hight()); } 运行结果以下: 中序遍历 Node{value=3} Node{value=4} Node{value=5} Node{value=6} Node{value=7} Node{value=8} 算上跟节点高度为:4
咱们刚刚也说了,算上跟根节点高度就是4,那么咱们看看左子树和右子树
//节点高度 System.out.println("节点高度为:"+avlTree.getRoot().hight()); //节点高度 System.out.println("左节点高度为:"+avlTree.getRoot().leftHigth()); //节点高度 System.out.println("右节点高度为:"+avlTree.getRoot().rightHight()); 运行结果以下: 节点高度为:4 左节点高度为:1 右节点高度为:3
咱们刚刚说到,左右两个子树的高度差绝对超过1才去进行平衡调整,那么当前的状况则须要进行调整:右旋转
class Node{ //......省略其余关键代码 //RR旋转方法 private void RightRotate(){ //建立一个新节点newNode等于当前根节点root,值相等 Node newNode = new Node(value); //新节点newNode的左子树设置为当前根节点root的左子树 newNode.left = left; //新节点newNode的右子树设置为当前根节点root的右子树的左子树 newNode.right = right.left; //当前根节点root的值换为右子节点的值 value = right.value; //当前根节点root的右子树设置成根节点root的右子树的右子树 right = right.right; //当前根节点root的左子树设置为新节点 left = newNode; } //优化添加节点操做 public void add(Node node) { if (node == null) { return; } //判断传入的节点的值,和当前节点值的关系 //添加的节点小于当前节点 if (node.value < this.value) { if (this.left == null) { this.left = node; } else { this.left.add(node); } } else {//添加的节点大于当前节点 if (this.right == null) { this.right = node; } else { this.right.add(node); } } //当添加完一个节点后:若是(右子树的高度 - 左子树的高度)> 1 则执行RR旋转 if(rightHight() - leftHigth() > 1 ){ RightRotate();//执行RR旋转 } } }
接下来咱们实践看看,当添加节点知足条件是否会进行平衡调整
public static void main(String[] args) { int[] arr ={4,3,6,5,7,8}; AVLTree avlTree = new AVLTree(); for(int i = 0; i<arr.length; i++){ avlTree.add(new Node(arr[i])); } //遍历 System.out.println("中序遍历"); avlTree.infixOrder(); //节点高度 System.out.println("节点高度为:"+avlTree.getRoot().hight()); //节点高度 System.out.println("左节点高度为:"+avlTree.getRoot().leftHigth()); //节点高度 System.out.println("右节点高度为:"+avlTree.getRoot().rightHight()); } 运行结果以下: 中序遍历 Node{value=3} Node{value=4} Node{value=5} Node{value=6} Node{value=7} Node{value=8} 节点高度为:3 左节点高度为:2 右节点高度为:2
这时咱们进行平衡调整,从左右两个子树的高度差没有超过1了。
给你一个数列{10,12,8,9,7,6}
,让你可以高效
的完成对数据的查询和添加
那么按照咱们以前的思路,先构建:一颗二叉排序树
非叶子节点特色:左子节点的值比当前节点的值小,右子节点的值比当前节点的值大。
特别说明:若是有相同的值
,能够将该节点放在左子节点或右子节点
咱们发现这颗二叉排序树左子树高度为3,右边子树高度为1
此时不符合平衡二叉树的特色:它是一颗空树或它的左右两个子树的高度差绝对超过1
根据上面介绍的四种平衡调整模式介绍,目前比较符合的是LL旋转
根据咱们前面的示例经验,咱们能够直接进行实现思路分析
左子树
设置为当前根节点root的左子树的右子树
左子树的左子树
示例代码实现
class Node{ //......省略其余关键代码 //LL旋转方法 private void leftRotate(){ //建立一个新节点newNode等于当前根节点root,值相等 Node newNode = new Node(value); //新节点newNode的右子树设置为当前根节点root的右子树 newNode.right = right; //新节点newNode的`左子树`设置为当前根节点root的`左子树的右子树` newNode.left = left.right; //当前根节点root的值换为左子节点的值 value = left.value; //当前根节点root的左子树设置成根节点root的`左子树的左子树` left = left.left; //当前根节点root的右子树设置为新节点 right = newNode; } //优化添加节点操做 public void add(Node node) { if (node == null) { return; } //判断传入的节点的值,和当前节点值的关系 //添加的节点小于当前节点 if (node.value < this.value) { if (this.left == null) { this.left = node; } else { this.left.add(node); } } else {//添加的节点大于当前节点 if (this.right == null) { this.right = node; } else { this.right.add(node); } } //当添加完一个节点后:若是(右子树的高度 - 左子树的高度)> 1 则执行RR旋转 if(rightHight() - leftHigth() > 1 ){ RightRotate();//执行RR旋转 } //当添加完一个节点后:若是(左子树的高度 - 右子树的高度)> 1 则执行LL旋转 if(leftHigth() - rightHight() > 1 ){ leftRotate();//执行LL旋转 } } }
接下来咱们Demo测试一下为调整以前的高度分别是多少
public static void main(String[] args) { //int[] arr ={4,3,6,5,7,8}; int[] arr ={10,12,8,9,7,6}; AVLTree avlTree = new AVLTree(); for(int i = 0; i<arr.length; i++){ avlTree.add(new Node(arr[i])); } //遍历 System.out.println("中序遍历"); avlTree.infixOrder(); //节点高度 System.out.println("节点高度为:"+avlTree.getRoot().hight()); //节点高度 System.out.println("左节点高度为:"+avlTree.getRoot().leftHigth()); //节点高度 System.out.println("右节点高度为:"+avlTree.getRoot().rightHight()); } 运行结果以下: 中序遍历 Node{value=6} Node{value=7} Node{value=10} Node{value=9} Node{value=8} Node{value=12} 节点高度为:3 左节点高度为:2 右节点高度为:2
这时咱们进行平衡调整,从左右两个子树的高度差没有超过1了。
给你一个数列{10,11,7,6,8,9}
,让你可以高效
的完成对数据的查询和添加
那么按照咱们以前的思路,先构建:一颗二叉排序树
咱们发现这颗二叉排序树左子树高度为3,右边子树高度为1
此时不符合平衡二叉树的特色:它是一颗空树或它的左右两个子树的高度差绝对超过1
按照咱们以前思路适合的是LL旋转,那么咱们执行左旋后的样子发现是
那么出现这种问题的缘由是什么呢?
分析1:加入节点九时,左子树(节点7)的树高度 > 右子树(节点11) 的高度
分析2:左子树高度 - 右子树高度 >1 触发LL旋转,就变成上面那样了
那么咱们能够根据上面的LR介绍能够猜到一些思路,解决这个问题.
咱们在符合:执行LL旋转时条件时
思路1.获取左子树的右子树(节点8)高度,取名为:k
思路2.获取左子树(节点7)的高度,取名为:J
思路3.若是 k > J,对左子树进行RR旋转
思路4.若是 k < J,直接进行LL旋转
简单的一句话:若是K>J,则先旋转子树再旋转本身
同时咱们在符合:执行RR旋转时条件时
思路1.获取右子树的左子树高度 ,取名为:U
思路2.获取右子树(节点11)的高度,取名为:L
思路3.若是U > L,对右子树进行LL旋转
思路4.若是U < L,直接进行RR旋转
class Node{ //......省略其余关键代码 //优化添加节点操做 public void add(Node node) { if (node == null) { return; } //判断传入的节点的值,和当前节点值的关系 //添加的节点小于当前节点 if (node.value < this.value) { if (this.left == null) { this.left = node; } else { this.left.add(node); } } else {//添加的节点大于当前节点 if (this.right == null) { this.right = node; } else { this.right.add(node); } } //当添加完一个节点后:若是(右子树的高度 - 左子树的高度)> 1 则执行RR旋转 if(rightHight() - leftHigth() > 1 ){ //获取它的右子树的左子树的高度 取名U,获取它的右子树高度L //若是u > l 执行LL旋转 if(right != null && right.leftHigth()> right.rightHight()){ right.leftRotate();//执行LL旋转 RightRotate();//执行RR旋转 }else{ RightRotate(); } return;//防止接着往下走 } //当添加完一个节点后:若是(左子树的高度 - 右子树的高度)> 1 则执行LL旋转 if(leftHigth() - rightHight() > 1 ){ //获取左子树的右子树高度,取名为:k 获取左子树的高度,取名为:J //若是 k > J,对左子树进行RR旋转 if(left != null && left.rightHight() > left.leftHigth()){ left.RightRotate();//执行RR旋转 leftRotate();//执行LL旋转 }else{ leftRotate();//执行LL旋转 } } } }
接下来让咱们使用Demo验证一下咱们的思路
public static void main(String[] args) { //int[] arr ={4,3,6,5,7,8}; int[] arr ={10,12,8,9,7,6}; AVLTree avlTree = new AVLTree(); for(int i = 0; i<arr.length; i++){ avlTree.add(new Node(arr[i])); } //遍历 System.out.println("中序遍历"); avlTree.infixOrder(); //节点高度 System.out.println("节点高度为:"+avlTree.getRoot().hight()); //节点高度 System.out.println("左节点高度为:"+avlTree.getRoot().leftHigth()); //节点高度 System.out.println("右节点高度为:"+avlTree.getRoot().rightHight()); System.out.println("右节点高度为:"+avlTree.getRoot().rightHight()); System.out.println("当前根节点为:"+avlTree.getRoot()); System.out.println("当前根节点的左节点为:"+avlTree.getRoot().left); System.out.println("当前根节点的右节点为:"+avlTree.getRoot().right); } 运行结果以下: 中序遍历 Node{value=6} Node{value=7} Node{value=8} Node{value=9} Node{value=10} Node{value=11} 节点高度为:3 左节点高度为:2 右节点高度为:2 当前根节点为:Node{value=8} 当前根节点的左节点为:Node{value=7} 当前根节点的右节点为:Node{value=10}