目录
普通二叉搜索树的问题
普通二叉树(二叉查找树)在操作的时间复杂度上不一定遵循O(㏒n),也有可能是O(n),这是为什么呢?在上一篇中,我们明明插入都按照一定规则比较的呀,其实那是因为我们在上篇测试时执行了随机插入的操作,如果此时利用上篇实现的二叉搜索树将一段已排序好的数据一个个插入后,就会发现如下情况了:
从图中我们可以发现,把已排序的1-9数据进行正序和倒序插入后,树的结构已变成单向左子树或者右子树了,如果我们在往里插入已排序的数据,那么单向左子树或者右子树越来越长,此时已跟单链表没有什么区别了,因此对此结构的操作时间复杂度自然就由O(㏒n)变成O(n)了,这也就是普通二叉查找树不是严格意义上O(㏒n)的原因。那么该如何解决这个问题呢?事实上一种解决的办法就是要有一个称为平衡的附加结构条件即:任何结点的深度不得过深,而这种数据结构就是我们本篇要分析的平衡二叉树(AVL),它本身也是一种二叉查找树,只不过不会出现前面我们分析的情形罢了,接下来我们就来分析一下这棵平衡二叉树。
AVL树定义
对于一棵BST树而言,不仅有查找操作,也有插入、删除等改变树的形态的操作。随着不断地插入、删除,BST树有可能会退化成链表的形式,使得查找的时间复杂度变成O(N),这种情形下,BST树的结构非常不平衡了。为了保持树的平衡,需要对树的形态做一些限制,因此,引入了AVL树,以保证树的左右子树高度之差的绝对值小于等于1。
一棵AVL树是其每个结点的左子树和右子树的高度最多相差1的二叉查找树(空树的高度为-1),这个差值也称为平衡因子(其取值可以是1,0,-1,平衡因子是某个结点左右子树层数的差值,有的书上定义是左边减去右边,有的书上定义是右边减去左边,这样可能会有正负的区别,但是这个并不影响我们对平衡二叉树的讨论)。如下图
AVL树和节点的结构
叶子节点比普通的二叉树多了一个height,默认值为0,height表达的是这个节点以下的链接数的深度
所以没有子节点的height=0,有一个子节点为1
public class TreeNode {
int val;
int height=0;//如果这个节点没有子节点,那么height=0
public TreeNode left;
public TreeNode right;
public TreeNode(int x) {
val = x;
left=null;
right=null;
}
树里有一个root
public class AVLBinarySearchTree {
public TreeNode root;
AVL树高度计算方法
总共4个方法,得到高度2个,计算高度2个
calHeightOne
能够这个节点的子节点的height都正确,只要重新set这个节点的height
而calHeightAll
不能确定这个节点及以下所有子节点的height,重新set全部子节点的height
//直接返回这个节点的height,防止null的情况的错误,null返回-1
public static int getHeight(TreeNode node){
if(node==null){
return -1;
}
return node.height;
}
//返回这个节点左子树-右子树的height的差,如果节点为null返回0
//如果为负,则左子树矮,为正,右子树矮
public static int getHeightDifference(TreeNode node){
if(node==null){
return 0;
}
return getHeight(node.left)-getHeight(node.right);
}
//不确定这个节点及以下所有子节点的height,重新set他们的height
//此处的height表达的是这个节点以下的链接数的深度
//所以没有子节点为(-1+1)=0,有一个子节点为max(-1,0)+1=1
public static int calHeightAll(TreeNode node){
if(node==null){
return -1;
}
int nowHeight=Math.max(calHeightAll(node.left), calHeightAll(node.right))+1;
node.height=nowHeight;
return nowHeight;
}
//确定这个节点的子节点的height,重新set这个节点的height
public static int calHeightOne(TreeNode node){
if(node==null){
return -1;
}
int nowHeight=Math.max(getHeight(node.left), getHeight(node.right))+1;
node.height=nowHeight;
return nowHeight;
}
AVL树旋转
在每一次插入数值之后,树的平衡性都可能被破坏,这时可以通过一个简单的操作来矫正平衡–旋转。
旋转的目的就是减少高度,通过降低整棵树的高度来平衡。哪边的树高,就把那边的树向上旋转。
通过旋转可以降低高度。
所谓的左旋和右旋都是以子树为原点的:如b是a的子树,那么旋转就围绕b来进行。
如果b是a的左子树,那么就围绕b将a向右旋转,看着就像是a直接掉下来了,掉成了b的右子树。
如果b是a的右子树,那么就围绕b将a向左旋转,看着就像是a直接掉下来了,掉成了b的左子树。
插入节点时分四种情况,四种情况对应的旋转方法是不同的:
例如对于被破坏平衡的节点 a 来说:
注意插入方式LL是指在左子树的左子树插入节点,而不是左旋转。
插入方式 | 描述 | 旋转方式 |
LL | 在a的左子树根节点的左子树上插入节点而破坏平衡 | 右旋转 |
RR | 在a的右子树根节点的右子树上插入节点而破坏平衡 | 左旋转 |
LR | 在a的左子树根节点的右子树上插入节点而破坏平衡 | 先左旋后右旋 |
RL | 在a的右子树根节点的左子树上插入节点而破坏平衡 | 先右旋后左旋 |
LL 右旋转
就拿最简单的举例了。
一个简单的AVL树:
这时是平衡的,如果在插入一个元素3,就会变成下面这样,破坏平衡:
被破坏了平衡首先要找到是哪个树被破坏了平衡,然后调整这个树。然后继续往上一个一个的调整。
既然是被新插入的节点3破坏的,那么不平衡的树一定在从新插入的节点3到根节点8的路径上。找离新插入的节点最近的不平衡的树进行调整,上图中就是7.
节点7的左子树 高度为1,右子树为空,高度为-1 ,不平衡。根据表格要进行右旋转。
先把7这颗不平衡的树挑出来:
这棵树是最近的不平衡的树,7的左子树5高度为1,右子树为空,所以右子树高度是-1.两者的高度差达到了2,超过了1.
因为左子树5的高度更高,所以要把左子树5向上提一下,这时旋转就很明显了,抓着5向上一提,7就掉到5的右边了,成了5的右子树。
这个过程就是右旋:
这时继续往上找,发现每个节点都符合了平衡条件,所以整棵树就变成了AVL树。
那如果节点5本来就有了右子树呢?照样右旋转,只要把原来5的右子树变成旋转后的7的左子树就行了。因为5的右子树肯定比5大,但是也肯定比7小的:
其实上面最后旋转成的树是下面这样的:
这棵树的根节点是不平衡的,还需要使用后面的双旋转来调整。
算法 rotateRight(nodeN)
nodeL = nodeN 的左孩子
将nodeN 的左孩子置为 nodeL 的右孩子
将 nodeL 的右孩子置为 nodeN
return nodeL
//右旋转
public TreeNode rotateRight(TreeNode node){
TreeNode leftNode=node.left;
node.left=leftNode.right;
leftNode.right=node;
return leftNode;
}
RR 左旋转
在右子树的右子树上插入节点破坏的平衡需要左旋转来矫正。
左旋转和右旋转类似,都是单旋转,给个流程图。
算法 rotateLeft(nodeN)
nodeR = nodeN 的右孩子
将 nodeN 的右孩子置为 nodeR 的左孩子
将 nodeR 的左孩子置为 nodeN
return nodeR
//左旋转 返回选择后的根节点
public TreeNode rotateLeft(TreeNode node){
TreeNode rightNode=node.right;
node.right=rightNode.left;
rightNode.left=node;
return rightNode;
}
LR 左旋右旋
如果在第一个例子中插入的不是3,而是6,就成了下面的样子,依然说破坏了平衡
被破坏平衡的树依然是7,但是这次就不能通过一次旋转解决了,咋转都不行。
要从6开始到7进行先左旋再右旋才可以矫正平衡:
进行左-右旋转,即先对结点N的孙子进行左旋转,再对结点N的新孩子进行右旋转,算法如下:
算法 rotateLeftRight(nodeN)
nodeL = nodeN 的左孩子
将 nodeN 的左孩子置为由 rotateLeft(nodeN 孩子的孩子)返回的结点
return rotateLeft(nodeN 新的左孩子)
//左旋转 然后右旋转
public TreeNode rotateLeftAndRight(TreeNode node){
TreeNode leftNode=node.left;
node.left=rotateLeft(leftNode);
return rotateRight(node);
}
RL 右旋左旋
当破坏平衡的节点是这个树的右子树的左子树时,要进行先右旋转再左旋转来矫正。
同样是从破坏平衡的那个节点开始旋转,先右旋转后左旋转:
进行右-左旋转,即先对结点N 的孙子(孩子的孩子,"位于新插入元素的那个方向")进行右旋转;再对结点N 的新孩子进行左旋转,算法如下:
算法 rotateRightLeft(nodeN)
nodeR = nodeN 的右孩子
将 nodeN 的右孩子置为由 rotateRight(nodeN 的孩子的孩子)返回的结点
return rotateLeft(nodeN 的新的右孩子)
//右旋转 然后左旋转
public TreeNode rotateRightAndLeft(TreeNode node){
TreeNode rightNode=node.right;
node.right=rotateRight(rightNode);
return rotateLeft(node);
}
平衡节点
可以根据节点的左右节点的高度差确定旋转方式,具体可以看旋转的那个表格
注意:
如果做了旋转,则对node重新进行height的计算,node及node的子节点的height是正确的,让node的上一层只需做calNodeOne的计算就行
//平衡这个node,进行旋转,返回根节点
//如果做了旋转,则对node重新进行height的计算,让node的上一层只需做calNodeOne的计算就行
//node的height已经计算正确
public TreeNode rebalanceNode(TreeNode node){
int difference=getHeightDifference(node);
//左子树高
if(difference>1){
if(getHeightDifference(node.left)>0){
node=rotateRight(node);
}
else{
node=rotateLeftAndRight(node);
}
calHeightAll(node);
}
//右子树高
if(difference<-1){
if(getHeightDifference(node.right)<0){
node=rotateLeft(node);
}
else{
node=rotateRightAndLeft(node);
}
calHeightAll(node);
}
return node;
}
插入节点
用递归的方法,返回节点,让left或right=返回后的节点
//插入节点,如果已存在相同的树,则不插入,
public void insertNode(int x){
if(root==null){
root=new TreeNode(x);
calHeightOne(root);
}
root=insertNode(root,x);
root=rebalanceNode(root);
}
public TreeNode insertNode(TreeNode node,int x){
//如果为空,返回新节点x
if(node==null){
return new TreeNode(x);
}
int now=node.val;
//如果相等,不插入,直接返回
if(now==x){
return node;
}
if(now>x){
node.left=insertNode(node.left, x);
node.left=rebalanceNode(node.left);
calHeightOne(node);
}
else{
node.right=insertNode(node.right, x);
node.right=rebalanceNode(node.right);
calHeightOne(node);
}
return node;
}
删除节点
删除节点需要查找一个节点下的max和min节点的方法
查找最大最小节点
//返回这个node下max的子节点,就是最右的子节点
public TreeNode findMaxNode(TreeNode node){
if(node==null){
return null;
}
while(node.right!=null){
node=node.right;
}
return node;
}
//返回这个node下min的子节点,就是最左的子节点
public TreeNode findMinNode(TreeNode node){
if(node==null){
return null;
}
while(node.left!=null){
node=node.left;
}
return node;
}
删除
如果删除的那个节点左右子树有一个为空,则把下面的接到上面去
如果都不为空
如果tree的左子树比右子树高; 则找出tree的左子树中的最大节点 将该最大节点的值赋值给tree。 删除该最大节点。 这类似于用"tree的左子树中最大节点"做"tree"的替身; 采用这种方式的好处是:删除"tree的左子树中最大节点"之后,AVL树仍然是平衡的。
反之亦然
//如果树中没有值为x,则啥都不干,否则删除这个节点
public void deleteNode(int x){
if(root==null){
return;
}
root=deleteNode(root,x);
root=rebalanceNode(root);
calHeightOne(root);
}
public TreeNode deleteNode(TreeNode node,int x){
//如果为空,返回新节点x
if(node==null){
return null;
}
int now=node.val;
if(now>x){
node.left=deleteNode(node.left, x);
node.left=rebalanceNode(node.left);
calHeightOne(node);
}
if(now<x){
node.right=deleteNode(node.right, x);
node.right=rebalanceNode(node.right);
calHeightOne(node);
}
//如果相等,
if(now==x){
if(node.left!=null&&node.right!=null){
int difference=getHeightDifference(node);
//如果左子树比右子树高,用左子树的max替代node,node的平衡不会被 破坏
if(difference>0){
TreeNode max=findMaxNode(node.left);
int maxValue=max.val;
node.val=maxValue;
node.left=deleteNode(node.left, maxValue);
}
//如果左子树比右子树矮或者相同,用右子树的min替代node,node的平衡不会被 破坏
else{
TreeNode min=findMinNode(node.right);
int minValue=min.val;
node.val=minValue;
node.right=deleteNode(node.right, minValue);
}
}
//左右子树至少一个为空
else{
if(node.left!=null){
node=node.left;
}
else{
node=node.right;
}
}
}
return node;
}
完整代码
主程序
package datastructure.tree.avlbinarysearchtree;
public class Main {
public static void main(String[] args) {
int[] x=new int[]{3,2,1,4,5,6,7};
AVLBinarySearchTree tree=new AVLBinarySearchTree(x);
//tree.insertNode(3);
AVLBinarySearchTree.calHeightAll(tree.root);
AVLBinarySearchTree.printTree(tree.root);
//BinarySearchTree.inOrder(tree.root);
//BinarySearchTree.breadthFirstSearch(tree.root);
//BinarySearchTree.depthFirstSearch(tree.root);
//tree.root=tree.rotateRightAndLeft(tree.root);
//TreeNode now=tree.findNode(2);
//now.printTree(now);
tree.deleteNode(4);
AVLBinarySearchTree.calHeightAll(tree.root);
AVLBinarySearchTree.printTree(tree.root);
}
}
叶节点
package datastructure.tree.avlbinarysearchtree;
import java.util.LinkedList;
import java.util.Queue;
public class TreeNode {
int val;
int height=0;//如果这个节点没有子节点,那么height=0
public TreeNode left;
public TreeNode right;
public TreeNode(int x) {
val = x;
left=null;
right=null;
}
//通过队列 当前下层节点个数 打印每层的节点和null,为了方便打印
public static void printTree(TreeNode root){
if(root == null)
return;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
int current;//当前层 还未打印的结点个数
int next;//下一层结点个数
queue.offer(root);
current = 1;
next = 0;
while(!queue.isEmpty()){
TreeNode currentNode = queue.poll();
if (currentNode!=null) {
System.out.print(currentNode.val+" ");
current--;
}
else{
System.out.print("null ");
current--;
queue.offer(null);
next++;
queue.offer(null);
next++;
if(current ==0){
System.out.println();
current = next;
next = 0;
int temp=0;
for (TreeNode treeNode : queue) {
if(treeNode==null){
temp++;
}
}
if(temp==current){
System.out.println("end");
break;
}
}
continue;
}
if(currentNode.left != null){
queue.offer(currentNode.left);
next++;
}
else{
queue.offer(null);
next++;
}
if(currentNode.right != null){
queue.offer(currentNode.right);
next++;
}
else{
queue.offer(null);
next++;
}
if(current ==0){
System.out.println();
current = next;
next = 0;
int temp=0;
for (TreeNode treeNode : queue) {
if(treeNode==null){
temp++;
}
}
if(temp==current){
System.out.println("end");
break;
}
}
}
}
}
avl树
package datastructure.tree.avlbinarysearchtree;
import java.nio.channels.NetworkChannel;
import java.util.ArrayList;
import java.util.LinkedList;
import java.util.List;
import java.util.Queue;
import java.util.Stack;
public class AVLBinarySearchTree {
public TreeNode root;
//只有一个int,则root为x的节点
public AVLBinarySearchTree(int x){
root=new TreeNode(x);
}
//x为数组,如果长度为0,初始化root为0的节点
//否则逐个插入x的元素
public AVLBinarySearchTree(int[] x){
int length=x.length;
for(int i=0;i<length;i++){
insertNode(x[i]);
}
}
//直接返回这个节点的height,防止null的情况的错误,null返回-1
public static int getHeight(TreeNode node){
if(node==null){
return -1;
}
return node.height;
}
//返回这个节点左子树-右子树的height的差,如果节点为null返回0
//如果为负,则左子树矮,为正,右子树矮
public static int getHeightDifference(TreeNode node){
if(node==null){
return 0;
}
return getHeight(node.left)-getHeight(node.right);
}
//不确定这个节点及以下所有子节点的height,重新set他们的height
//此处的height表达的是这个节点以下的链接数的深度
//所以没有子节点为(-1+1)=0,有一个子节点为max(-1,0)+1=1
public static int calHeightAll(TreeNode node){
if(node==null){
return -1;
}
int nowHeight=Math.max(calHeightAll(node.left), calHeightAll(node.right))+1;
node.height=nowHeight;
return nowHeight;
}
//确定这个节点的子节点的height,重新set这个节点的height
public static int calHeightOne(TreeNode node){
if(node==null){
return -1;
}
int nowHeight=Math.max(getHeight(node.left), getHeight(node.right))+1;
node.height=nowHeight;
return nowHeight;
}
//左旋转 返回选择后的根节点
public TreeNode rotateLeft(TreeNode node){
TreeNode rightNode=node.right;
node.right=rightNode.left;
rightNode.left=node;
return rightNode;
}
//右旋转
public TreeNode rotateRight(TreeNode node){
TreeNode leftNode=node.left;
node.left=leftNode.right;
leftNode.right=node;
return leftNode;
}
//左旋转 然后右旋转
public TreeNode rotateLeftAndRight(TreeNode node){
TreeNode leftNode=node.left;
node.left=rotateLeft(leftNode);
return rotateRight(node);
}
//右旋转 然后左旋转
public TreeNode rotateRightAndLeft(TreeNode node){
TreeNode rightNode=node.right;
node.right=rotateRight(rightNode);
return rotateLeft(node);
}
//平衡这个node,进行旋转,返回根节点
//如果做了旋转,则对node重新进行height的计算,让node的上一层只需做calNodeOne的计算就行
//node的height已经计算正确
public TreeNode rebalanceNode(TreeNode node){
int difference=getHeightDifference(node);
//左子树高
if(difference>1){
if(getHeightDifference(node.left)>0){
node=rotateRight(node);
}
else{
node=rotateLeftAndRight(node);
}
calHeightAll(node);
}
//右子树高
if(difference<-1){
if(getHeightDifference(node.right)<0){
node=rotateLeft(node);
}
else{
node=rotateRightAndLeft(node);
}
calHeightAll(node);
}
return node;
}
//插入节点,如果已存在相同的树,则不插入,
public void insertNode(int x){
if(root==null){
root=new TreeNode(x);
calHeightOne(root);
}
root=insertNode(root,x);
root=rebalanceNode(root);
}
public TreeNode insertNode(TreeNode node,int x){
//如果为空,返回新节点x
if(node==null){
return new TreeNode(x);
}
int now=node.val;
//如果相等,不插入,直接返回
if(now==x){
return node;
}
if(now>x){
node.left=insertNode(node.left, x);
node.left=rebalanceNode(node.left);
calHeightOne(node);
}
else{
node.right=insertNode(node.right, x);
node.right=rebalanceNode(node.right);
calHeightOne(node);
}
return node;
}
//根据int x查找二叉搜索树的节点,如果没找到 返回null,找到了,返回该节点
//now=root 进入true的循环 如果now=null,说明没有返回null,now的值=x,说明找到,返回now
//否则根据now与x的大小,now=now的左右子节点
public TreeNode findNode(int x){
if(root==null){
return null;
}
TreeNode now=root;
while(true){
if(now==null){
return null;
}
if(now.val==x){
return now;
}
if(now.val<x){
now=now.right;
}
else {
now=now.left;
}
}
}
//返回这个node下max的子节点,就是最右的子节点
public TreeNode findMaxNode(TreeNode node){
if(node==null){
return null;
}
while(node.right!=null){
node=node.right;
}
return node;
}
//返回这个node下min的子节点,就是最左的子节点
public TreeNode findMinNode(TreeNode node){
if(node==null){
return null;
}
while(node.left!=null){
node=node.left;
}
return node;
}
//如果树中没有值为x,则啥都不干,否则删除这个节点
public void deleteNode(int x){
if(root==null){
return;
}
root=deleteNode(root,x);
root=rebalanceNode(root);
calHeightOne(root);
}
public TreeNode deleteNode(TreeNode node,int x){
//如果为空,返回新节点x
if(node==null){
return null;
}
int now=node.val;
if(now>x){
node.left=deleteNode(node.left, x);
node.left=rebalanceNode(node.left);
calHeightOne(node);
}
if(now<x){
node.right=deleteNode(node.right, x);
node.right=rebalanceNode(node.right);
calHeightOne(node);
}
//如果相等,
if(now==x){
if(node.left!=null&&node.right!=null){
int difference=getHeightDifference(node);
//如果左子树比右子树高,用左子树的max替代node,node的平衡不会被 破坏
if(difference>0){
TreeNode max=findMaxNode(node.left);
int maxValue=max.val;
node.val=maxValue;
node.left=deleteNode(node.left, maxValue);
}
//如果左子树比右子树矮或者相同,用右子树的min替代node,node的平衡不会被 破坏
else{
TreeNode min=findMinNode(node.right);
int minValue=min.val;
node.val=minValue;
node.right=deleteNode(node.right, minValue);
}
}
//左右子树至少一个为空
else{
if(node.left!=null){
node=node.left;
}
else{
node=node.right;
}
}
}
return node;
}
//递归
public static void preOrder(TreeNode root){
if(root==null){
return;
}
System.out.print(root.val+" ");
preOrder(root.left);
preOrder(root.right);
}
public static void inOrder(TreeNode root){
if(root==null){
return;
}
inOrder(root.left);
System.out.print(root.val+" ");
inOrder(root.right);
}
public static void postOrder(TreeNode root){
if(root==null){
return;
}
postOrder(root.left);
postOrder(root.right);
System.out.print(root.val+" ");
}
//通过队列 当前下层节点个数 打印每层的节点和null
public static void printTree(TreeNode root){
if(root == null)
return;
Queue<TreeNode> queue = new LinkedList<TreeNode>();
int current;//当前层 还未打印的结点个数
int next;//下一层结点个数
queue.offer(root);
current = 1;
next = 0;
while(!queue.isEmpty()){
TreeNode currentNode = queue.poll();
if (currentNode!=null) {
//System.out.print(currentNode.val+" ");
System.out.print(currentNode.val+
"(h="+currentNode.height+")"+
"(d="+getHeightDifference(currentNode)+") ");
current--;
}
else{
System.out.print("null ");
current--;
queue.offer(null);
next++;
queue.offer(null);
next++;
if(current ==0){
System.out.println();
current = next;
next = 0;
int temp=0;
for (TreeNode treeNode : queue) {
if(treeNode==null){
temp++;
}
}
if(temp==current){
System.out.println("end");
break;
}
}
continue;
}
if(currentNode.left != null){
queue.offer(currentNode.left);
next++;
}
else{
queue.offer(null);
next++;
}
if(currentNode.right != null){
queue.offer(currentNode.right);
next++;
}
else{
queue.offer(null);
next++;
}
if(current ==0){
System.out.println();
current = next;
next = 0;
int temp=0;
for (TreeNode treeNode : queue) {
if(treeNode==null){
temp++;
}
}
if(temp==current){
System.out.println("end");
break;
}
}
}
}
//宽度优先遍历 是简化的按层遍历,没有了current和next和打印null
public static void breadthFirstSearch(TreeNode root){
Queue<TreeNode> queue = new LinkedList<TreeNode>();
if(root==null){
return;
}
queue.offer(root);
while(!queue.isEmpty()){
TreeNode now=queue.poll();
System.out.print(now.val+" ");
if(now.left!=null){
queue.offer(now.left);
}
if(now.right!=null){
queue.offer(now.right);
}
}
System.out.println();
}
//深度优先遍历
//用栈 弹出自身 先加入右节点 再加入左节点,这样先弹出左节点,左节点的左右子节点又塞进去,在原右节点上面
public static void depthFirstSearch(TreeNode root){
Stack<TreeNode> stack=new Stack<TreeNode>();
if(root==null){
return;
}
stack.push(root);
while(!stack.isEmpty()){
TreeNode now=stack.pop();
System.out.print(now.val+" ");
if(now.right!=null){
stack.push(now.right);
}
if(now.left!=null){
stack.push(now.left);
}
}
System.out.println();
}
}