平衡二叉树java_AVL树(平衡二叉树)

什么是平衡二叉树?

为什么叫AVL树?

因为AVL树是由 G.M.Adelson-Velsky 和 E.M.Landis 这两位俄罗斯科学家在1962年的论文中首次提出,是最早的自平衡二分搜索树结构。

由于AVL树是自平衡二分搜索树,所以本质上还是二分搜素树,也就是二分搜索树的性质AVL树都满足,由于二分搜索树在添加有序元素时,会退化成链表,造成时间复杂度为O(n),但AVL树是不会出现这种情况的,因为AVL树通过自平衡来解决了退化成链表的问题,关于二分搜索树,你可以看我之前二分搜索树(Binary Search Tree)这篇文章。

平衡二叉树:对于任意一个节点,左子树和右子树的高度差都不能超过1。

0c87d80aa084bb36a8c9e7297d56bbe7.png

为了更好的维护AVL树的自平衡,我们可以在每个节点中,标注该节点的高度,并计算该节点的平衡因子。平衡因子就是左子树的高度减去右子树的高度。

8e67f6bd1283623004c8895f3f7f699d.png

现在让我们来基于二分搜索树,代码实现一个AVL树,这里先实现一个二分搜索树,代码如下:

/**

* AVL树是基于之前实现的二分搜索树,只不过加了自平衡机制

* 因此AVL树中的元素仍然必须具有可比较性

* 这里把AVL树设计成键值对的形式,方便后续基于AVL树实现Set和Map

*/

public class AVLTree,V> {

//节点

private class Node{

public K key;

public V value;

public Node left, right;

//当前节点的高度

public int height;

public Node(K key, V value){

this.key = key;

this.value = value;

left = null;

right = null;

height = 1;

}

}

private Node root;

private int size;

public AVLTree(){

root = null;

size = 0;

}

public boolean isEmpty(){

return this.size == 0;

}

//获取节点node的高度

public int getNodeHight(Node node){

if (node == null)

return 0;

return node.height;

}

//获取节点node的平衡因子

public int getBalanceFactor(Node node){

if (node == null)

return 0;

//平衡因子:左子树的高度 - 右子树的高度

return getNodeHight(node.left) - getNodeHight(node.right);

}

// 向二分搜索树中添加新的元素(key, value)

public void add(K key, V value){

root = add(root, key, value);

}

// 向以node为根的二分搜索树中插入元素(key, value),递归算法

// 返回插入新节点后二分搜索树的根

private Node add(Node node, K key, V value){

if(node == null){

size ++;

return new Node(key, value);

}

if(key.compareTo(node.key) < 0)

node.left = add(node.left, key, value);

else if(key.compareTo(node.key) > 0)

node.right = add(node.right, key, value);

else // key.compareTo(node.key) == 0

node.value = value;

// 更新height

node.height = 1 + Math.max(getNodeHight(node.left), getNodeHight(node.right));

// 计算平衡因子

int balanceFactor = getBalanceFactor(node);

if(Math.abs(balanceFactor) > 1)

System.out.println("unbalanced : " + balanceFactor);

return node;

}

// 返回以node为根节点的二分搜索树中,key所在的节点

private Node getNode(Node node, K key){

if(node == null)

return null;

if(key.equals(node.key))

return node;

else if(key.compareTo(node.key) < 0)

return getNode(node.left, key);

else // if(key.compareTo(node.key) > 0)

return getNode(node.right, key);

}

public boolean contains(K key){

return getNode(root, key) != null;

}

public V get(K key){

Node node = getNode(root, key);

return node == null ? null : node.value;

}

public void set(K key, V newValue){

Node node = getNode(root, key);

if(node == null)

throw new IllegalArgumentException(key + " doesn't exist!");

node.value = newValue;

}

// 返回以node为根的二分搜索树的最小值所在的节点

private Node minimum(Node node){

if(node.left == null)

return node;

return minimum(node.left);

}

// 删除掉以node为根的二分搜索树中的最小节点

// 返回删除节点后新的二分搜索树的根

private Node removeMin(Node node){

if(node.left == null){

Node rightNode = node.right;

node.right = null;

size --;

return rightNode;

}

node.left = removeMin(node.left);

return node;

}

// 从二分搜索树中删除键为key的节点

public V remove(K key){

Node node = getNode(root, key);

if(node != null){

root = remove(root, key);

return node.value;

}

return null;

}

private Node remove(Node node, K key) {

if (node == null)

return null;

if (key.compareTo(node.key) < 0) {

node.left = remove(node.left, key);

return node;

} else if (key.compareTo(node.key) > 0) {

node.right = remove(node.right, key);

return node;

} else { // key.compareTo(node.key) == 0

// 待删除节点左子树为空的情况

if (node.left == null) {

Node rightNode = node.right;

node.right = null;

size--;

return rightNode;

}

// 待删除节点右子树为空的情况

if (node.right == null) {

Node leftNode = node.left;

node.left = null;

size--;

return leftNode;

}

// 待删除节点左右子树均不为空的情况

// 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点

// 用这个节点顶替待删除节点的位置

Node successor = minimum(node.right);

successor.right = removeMin(node.right);

successor.left = node.left;

node.left = node.right = null;

return successor;

}

}

}

由上述代码可以看出,我们并没有实现AVL树的自平衡机制,只是在二分搜索树的基础上,加入了对高度的维护,和获取平衡因子的方法。因为AVL树是对于二分搜索树的一种改进,只不过解决了退化成链表的问题,AVL树也是二分搜索树,所以也需要满足二分搜索树的性质。我们可以根据二分搜索树的中序遍历是顺序的性质,来判断是否是二分搜索树。代码实现如下:

// 判断该二叉树是否是一棵二分搜索树

public boolean isBST(){

List keys = new ArrayList<>();

inOrder(root, keys);

for(int i = 1 ; i < keys.size() ; i ++)

if(keys.get(i - 1).compareTo(keys.get(i)) > 0)

return false;

return true;

}

//二分搜素树的中序遍历 -- 递归实现

private void inOrder(Node node, List keys){

if(node == null)

return;

inOrder(node.left, keys);

keys.add(node.key);

inOrder(node.right, keys);

}

//判断该二叉树是否是一颗平衡二叉树

public boolean isBalanced(){

return isBalanced(root);

}

//判断以Node为根的二叉树是否是一棵平衡二叉树,递归算法

private boolean isBalanced(Node node) {

if (node == null)

return true;

int balanceFactor = getBalanceFactor(node);

//判断当前节点的平衡因子是否大于1

if(Math.abs(balanceFactor) > 1)

return false;

return isBalanced(node.left) && isBalanced(node.right);

}

//获取节点node的高度

public int getNodeHight(Node node){

if (node == null)

return 0;

return node.height;

}

在什么时候维护平衡?

加入节点后,沿着节点向上维护平衡性。

插入的元素在不平衡节点左侧的左侧(LL)

156cc625a72426b4208ba40bb116521b.png

对于这种情况我们就需要对这个不平衡节点进行右旋转(顺时针旋转)

ec997ca10abfb06319ace7050023b832.png

右旋转代码实现:

// 对节点y进行向右旋转操作,返回旋转后新的根节点x

// y x

// / \ / \

// x T4 向右旋转 (y) z y

// / \ - - - - - - - -> / \ / \

// z T3 T1 T2 T3 T4

// / \

// T1 T2

private Node rightRotate(Node y) {

Node x = y.left;

Node T3 = x.right;

//右旋转

x.right = y;

y.left = T3;

// 更新height

y.height = Math.max(getNodeHight(y.left), getNodeHight(y.right)) + 1;

x.height = Math.max(getNodeHight(x.left), getNodeHight(x.right)) + 1;

return x;

}

并在ALV树的添加方法和删除方法代码中,对数的平衡性进行维护:

// 平衡维护

if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)

return rightRotate(node);

插入的元素在不平衡节点右侧的右侧(RR)

a6552f1c1d27670c51a948ebd35f2255.png

对于这种情况我们就需要对这个不平衡节点进行左旋转

代码实现:

// 对节点y进行向左旋转操作,返回旋转后新的根节点x

// y x

// / \ / \

// T1 x 向左旋转 (y) y z

// / \ - - - - - - - -> / \ / \

// T2 z T1 T2 T3 T4

// / \

// T3 T4

private Node leftRotate(Node y) {

Node x = y.right;

Node T2 = x.left;

// 向左旋转过程

x.left = y;

y.right = T2;

// 更新height

y.height = Math.max(getNodeHight(y.left), getNodeHight(y.right)) + 1;

x.height = Math.max(getNodeHight(x.left), getNodeHight(x.right)) + 1;

return x;

}

在我们的添加方法和删除方法中对树的平衡性进行维护:

if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)

return leftRotate(node);

插入的元素在不平衡节点左侧的右侧(LR)

对于这种情况我们需要先进行左旋转操作,转成LL的情况,再进行右旋转:

bf2873c06e9c5f7c8755666634530c77.png

由于我们前面已经对左旋转何有旋转都已经代码实现了,所以对该情况,只需要添加和删除方法中,对树的平衡性进行维护即可:

//LR

if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {

node.left = leftRotate(node.left);

return rightRotate(node);

}

插入的元素在不平衡节点右侧的左侧(RL)

对于这种情况我们需要先进行右旋转操作,转成LL的情况,再进行左旋转:

dfffcd26511837522739630fc2272292.png

在添加和删除方法中,对树的平衡性进行维护:

//RL

if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {

node.right = rightRotate(node.right);

return leftRotate(node);

}

下面是本文实现的AVL平衡二叉树的的全部代码:

import java.util.ArrayList;

import java.util.List;

/**

* AVL树是基于之前实现的二分搜索树,只不过加了自平衡机制

* 因此AVL树中的元素仍然必须具有可比较性

* 这里把AVL树设计成键值对的形式,方便后续基于AVL树实现Set和Map

*/

public class AVLTree,V> {

//节点

private class Node{

public K key;

public V value;

public Node left, right;

//当前节点的高度

public int height;

public Node(K key, V value){

this.key = key;

this.value = value;

left = null;

right = null;

height = 1;

}

}

private Node root;

private int size;

public AVLTree(){

root = null;

size = 0;

}

public int getSize(){

return size;

}

public boolean isEmpty(){

return this.size == 0;

}

// 判断该二叉树是否是一棵二分搜索树

public boolean isBST(){

List keys = new ArrayList<>();

inOrder(root, keys);

for(int i = 1 ; i < keys.size() ; i ++)

if(keys.get(i - 1).compareTo(keys.get(i)) > 0)

return false;

return true;

}

//二分搜素树的中序遍历 -- 递归实现

private void inOrder(Node node, List keys){

if(node == null)

return;

inOrder(node.left, keys);

keys.add(node.key);

inOrder(node.right, keys);

}

//判断该二叉树是否是一颗平衡二叉树

public boolean isBalanced(){

return isBalanced(root);

}

//判断以Node为根的二叉树是否是一棵平衡二叉树,递归算法

private boolean isBalanced(Node node) {

if (node == null)

return true;

int balanceFactor = getBalanceFactor(node);

//判断当前节点的平衡因子是否大于1

if(Math.abs(balanceFactor) > 1)

return false;

return isBalanced(node.left) && isBalanced(node.right);

}

//获取节点node的高度

public int getNodeHight(Node node){

if (node == null)

return 0;

return node.height;

}

//获取节点node的平衡因子

public int getBalanceFactor(Node node){

if (node == null)

return 0;

//平衡因子:左子树的高度 - 右子树的高度

return getNodeHight(node.left) - getNodeHight(node.right);

}

// 对节点y进行向右旋转操作,返回旋转后新的根节点x

// y x

// / \ / \

// x T4 向右旋转 (y) z y

// / \ - - - - - - - -> / \ / \

// z T3 T1 T2 T3 T4

// / \

// T1 T2

private Node rightRotate(Node y) {

Node x = y.left;

Node T3 = x.right;

//右旋转

x.right = y;

y.left = T3;

// 更新height

y.height = Math.max(getNodeHight(y.left), getNodeHight(y.right)) + 1;

x.height = Math.max(getNodeHight(x.left), getNodeHight(x.right)) + 1;

return x;

}

// 对节点y进行向左旋转操作,返回旋转后新的根节点x

// y x

// / \ / \

// T1 x 向左旋转 (y) y z

// / \ - - - - - - - -> / \ / \

// T2 z T1 T2 T3 T4

// / \

// T3 T4

private Node leftRotate(Node y) {

Node x = y.right;

Node T2 = x.left;

// 向左旋转过程

x.left = y;

y.right = T2;

// 更新height

y.height = Math.max(getNodeHight(y.left), getNodeHight(y.right)) + 1;

x.height = Math.max(getNodeHight(x.left), getNodeHight(x.right)) + 1;

return x;

}

// 向二分搜索树中添加新的元素(key, value)

public void add(K key, V value){

root = add(root, key, value);

}

// 向以node为根的二分搜索树中插入元素(key, value),递归算法

// 返回插入新节点后二分搜索树的根

private Node add(Node node, K key, V value){

if(node == null){

size ++;

return new Node(key, value);

}

if(key.compareTo(node.key) < 0)

node.left = add(node.left, key, value);

else if(key.compareTo(node.key) > 0)

node.right = add(node.right, key, value);

else // key.compareTo(node.key) == 0

node.value = value;

// 更新height

node.height = 1 + Math.max(getNodeHight(node.left), getNodeHight(node.right));

// 计算平衡因子

int balanceFactor = getBalanceFactor(node);

// if(Math.abs(balanceFactor) > 1)

// System.out.println("unbalanced : " + balanceFactor);

// 平衡维护

//LL

if (balanceFactor > 1 && getBalanceFactor(node.left) >= 0)

return rightRotate(node);

//RR

if (balanceFactor < -1 && getBalanceFactor(node.right) <= 0)

return leftRotate(node);

//LR

if (balanceFactor > 1 && getBalanceFactor(node.left) < 0) {

node.left = leftRotate(node.left);

return rightRotate(node);

}

//RL

if (balanceFactor < -1 && getBalanceFactor(node.right) > 0) {

node.right = rightRotate(node.right);

return leftRotate(node);

}

return node;

}

// 返回以node为根节点的二分搜索树中,key所在的节点

private Node getNode(Node node, K key){

if(node == null)

return null;

if(key.equals(node.key))

return node;

else if(key.compareTo(node.key) < 0)

return getNode(node.left, key);

else // if(key.compareTo(node.key) > 0)

return getNode(node.right, key);

}

public boolean contains(K key){

return getNode(root, key) != null;

}

public V get(K key){

Node node = getNode(root, key);

return node == null ? null : node.value;

}

public void set(K key, V newValue){

Node node = getNode(root, key);

if(node == null)

throw new IllegalArgumentException(key + " doesn't exist!");

node.value = newValue;

}

// 返回以node为根的二分搜索树的最小值所在的节点

private Node minimum(Node node){

if(node.left == null)

return node;

return minimum(node.left);

}

// 删除掉以node为根的二分搜索树中的最小节点

// 返回删除节点后新的二分搜索树的根

private Node removeMin(Node node){

if(node.left == null){

Node rightNode = node.right;

node.right = null;

size --;

return rightNode;

}

node.left = removeMin(node.left);

return node;

}

// 从二分搜索树中删除键为key的节点

public V remove(K key){

Node node = getNode(root, key);

if(node != null){

root = remove(root, key);

return node.value;

}

return null;

}

private Node remove(Node node, K key) {

if (node == null)

return null;

Node retNode;

if (key.compareTo(node.key) < 0) {

node.left = remove(node.left, key);

// return node;

retNode = node;

} else if (key.compareTo(node.key) > 0) {

node.right = remove(node.right, key);

// return node;

retNode = node;

} else { // key.compareTo(node.key) == 0

// 待删除节点左子树为空的情况

if (node.left == null) {

Node rightNode = node.right;

node.right = null;

size--;

// return rightNode;

retNode = rightNode;

}

// 待删除节点右子树为空的情况

else if (node.right == null) {

Node leftNode = node.left;

node.left = null;

size--;

// return leftNode;

retNode = leftNode;

}

// 待删除节点左右子树均不为空的情况

// 待删除节点左右子树均不为空的情况

else{

// 找到比待删除节点大的最小节点, 即待删除节点右子树的最小节点

// 用这个节点顶替待删除节点的位置

Node successor = minimum(node.right);

//successor.right = removeMin(node.right);

successor.right = remove(node.right, successor.key);

successor.left = node.left;

node.left = node.right = null;

// return successor;

retNode = successor;

}

}

if(retNode == null)

return null;

// 更新height

retNode.height = 1 + Math.max(getNodeHight(retNode.left), getNodeHight(retNode.right));

// 计算平衡因子

int balanceFactor = getBalanceFactor(retNode);

// 平衡维护

// LL

if (balanceFactor > 1 && getBalanceFactor(retNode.left) >= 0)

return rightRotate(retNode);

// RR

if (balanceFactor < -1 && getBalanceFactor(retNode.right) <= 0)

return leftRotate(retNode);

// LR

if (balanceFactor > 1 && getBalanceFactor(retNode.left) < 0) {

retNode.left = leftRotate(retNode.left);

return rightRotate(retNode);

}

// RL

if (balanceFactor < -1 && getBalanceFactor(retNode.right) > 0) {

retNode.right = rightRotate(retNode.right);

return leftRotate(retNode);

}

return retNode;

}

public static void main(String[] args){

System.out.println("Pride and Prejudice");

ArrayList words = new ArrayList<>();

if(FileOperation.readFile("pride-and-prejudice.txt", words)) {

System.out.println("Total words: " + words.size());

AVLTree map = new AVLTree<>();

for (String word : words) {

if (map.contains(word))

map.set(word, map.get(word) + 1);

else

map.add(word, 1);

}

System.out.println("Total different words: " + map.getSize());

System.out.println("Frequency of PRIDE: " + map.get("pride"));

System.out.println("Frequency of PREJUDICE: " + map.get("prejudice"));

System.out.println("is BST : " + map.isBST());

System.out.println("is Balanced : " + map.isBalanced());

for(String word: words){

map.remove(word);

if(!map.isBST() || !map.isBalanced())

throw new RuntimeException();

}

}

System.out.println();

}

}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值