1. AVL平衡树的介绍:AVL平衡树是由G.M.Adelson-Velsky和E.M.Landis两人创建,因此命名为AVL。AVL平衡树是一种最早的自平衡二分搜索树结构,满二叉树一定是平衡二叉树,高度最低;完全二叉树也是平衡二叉树。
2. AVL平衡树的定义:对于AVL平衡树而言,其任意一个节点,左子树和右子树的高度差都不能超过1。
3. AVL平衡树的平衡因子:节点的左右子树的高度差。
4. AVL平衡树的调整:当向树中添加或删除节点时,会对树的结构发生改变,因此就需要进行平衡调整。
5. 平衡调整的方式:
1>. 左旋转:即当插入的元素在不平衡结点的右侧的右侧时,可以用左旋转进行平衡调整。
2>. 右旋转:即当插入的元素在不平衡结点的左侧的左侧时,可以用右旋转进行平衡调整。
3>. 左右旋转:即当插入的元素在不平衡结点的左侧的右侧时,可以先用左旋转,再用右旋转进行平衡调整。
4>. 右左旋转:即当插入的元素在不平衡结点的右侧的左侧时,可以先用右旋转,再用左旋转进行平衡调整。
6. 映射接口的定义:
public interface Map<K, V> {
//向map中添加键值对
public void put(K key, V value);
//删除map中指定key的键值对
public V remove(K key);
//判断二分搜索树中是否包含指定key
public boolean contains(K key);
//通过key从map中获取key对应的值
public V get(K key);
//修改指定key对应的值
public void set(K key, V value);
//获取map中有效元素的个数
public int size();
//判断map是否为空
public boolean isEmpty();
//获取map中的键的集合 因为map中键key是唯一的 所以用集合set进行存储
public Set<K> keySet();
//获取map中的值的列表 因为map中键value不是唯一的 所以用列表list进行存储
public List<V> values();
//获取map中键值对的集合 因为map中键值对(key:value)是唯一的 所以用集合set进行存储
public Set<Entry<K, V>> entrySet();
//定义一个获取键值对Entry<K, V>的接口
public interface Entry<K, V> extends Comparable<Entry<K, V>>{
//获取键值对的键
public K getKey();
//获取键值对的值
public V getValue();
}
}
7. 底层通过AVL平衡树实现的映射数据结构的实现:
//底层通过AVL平衡树实现的映射
/*
* AVL平衡树是最早的一种二分搜索树之一 因此AVL平衡树实现的映射中的key也应具有比较性
* 只有一个树的每个节点的平衡因子的绝对值 小于等于1 该树才能称为平衡树
* 平衡因子:当前节点的左子树高度减右子树高度的差值小于1 或者 大于-1
* 节点高度:节点的左右子树中高度的最大值 加1
*
* 每次进行添加和删除节点后 都应更新节点的高度 并且进行平衡调整
* 在AVL平衡树中平衡调整有四种方法 右旋转 左旋转 左右旋转 右左旋转
* 右旋转:适用于不平衡节点位于当前节点左侧的左侧
* 左旋转:适用于不平衡节点位于当前节点右侧的右侧
* 左右旋转:适用于不平衡节点位于当前节点左侧的右侧
* 右左旋转:适用于不平衡节点位于当前节点右侧的左侧
*/
public class AVLTreeMap<K extends Comparable<K>, V> implements Map<K, V>{
//创建AVL平衡树的节点 由五部分构成
//键key 值value 节点高度height 左孩子指针 右孩子指针
private class Node{
private K key; //表示节点的键
private V value; //表示节点的值
private int height; //表示结点的高度 默认高度为1
private Node leftChild; //指针域 指向节点的左孩子
private Node rightChild; //指针域 指向节点的右孩子
public Node(K key, V value) {
this.key = key;
this.value = value;
height = 1;
leftChild = null;
rightChild = null;
}
}
private Node root; //表示AVL平衡树的根节点
private int size; //表示AVL平衡树的有效元素的个数
public AVLTreeMap() {
root = null;
size = 0;
}
//获取以node节点为根节点的二分搜索树中指定key的节点
private Node getNode(Node node, K key) {
if(node == null) {
return null;
}
if(key.compareTo(node.key) < 0) {
return getNode(node.leftChild, key);
}else if(key.compareTo(node.key) > 0) {
return getNode(node.rightChild, key);
}else {
return node;
}
}
//获取指定节点的高度
private int getHight(Node node) {
if(node == null) {
return 0;
}
return node.height;
}
//获取指定节点的平衡因子 即该节点的左子树高度减去右子树高度
private int getBalanceFactor(Node node) {
if(node == null) {
return 0;
}
return getHight(node.leftChild) - getHight(node.rightChild);
}
//判断该树是否是一个二分搜索树 即判断中序遍历后的结果是否是递增的即可
public boolean isBinarySearchTree() {
ArrayList<K> list = new ArrayList<K>();
inOrderKey(root, list);
for(int i = 1; i < list.size(); i++) {
if(list.get(i - 1).compareTo(list.get(i)) > 0) {
return false;
}
}
return true;
}
//对AVL树进行中序遍历
private void inOrderKey(Node node, List<K> list) {
if(node == null) {
return;
}
inOrderKey(node.leftChild, list);
list.add(node.key);
inOrderKey(node.rightChild, list);
}
//判断该树是否是一个AVL平衡树 向外提供的
public boolean isAVLTree() {
return isAVLTree(root);
}
//判断以node节点为根节点的树是否是AVL平衡树
//即当前节点的平衡因子的绝对值小于等于1 并且 当前节点的是左子树和右子树都是AVL平衡树
private boolean isAVLTree(Node node) {
if(node == null) {
return true;
}
int balanceFactor = getBalanceFactor(node);
if(Math.abs(balanceFactor) > 1) {
return false;
}
return isAVLTree(node.leftChild) && isAVLTree(node.rightChild);
}
//右旋转(左侧的左侧) 将y结点进行右旋转 并返回旋转后新树的根
private Node rightRotate(Node y) {
Node x = y.leftChild;
Node T3 = x.rightChild;
x.rightChild = y;
y.leftChild = T3;
//旋转后更新旋转的节点的高度
y.height = Math.max(getHight(y.leftChild), getHight(y.rightChild)) + 1;
x.height = Math.max(getHight(x.leftChild), getHight(x.rightChild)) + 1;
return x;
}
//左旋转(右侧的右侧) 将y结点进行左旋转 并返回旋转后新树的根
private Node leftRotate(Node y) {
Node x = y.rightChild;
Node T3 = x.leftChild;
x.leftChild = y;
y.rightChild = T3;
//旋转后更新旋转的节点的高度
y.height = Math.max(getHight(y.leftChild), getHight(y.rightChild)) + 1;
x.height = Math.max(getHight(x.leftChild), getHight(x.rightChild)) + 1;
return x;
}
//向AVL平衡树中添加键值对 向外提供的
@Override
public void put(K key, V value) {
root = put(root, key, value);
}
//向以node为根节点的AVL平衡树中添加键值对 并返回添加后的新树的根节点
private Node put(Node node, K key, V value) {
if(node == null) {
size++;
return new Node(key, value);
}
if(key.compareTo(node.key) < 0) {
node.leftChild = put(node.leftChild, key, value);
}else if(key.compareTo(node.key) > 0) {
node.rightChild = put(node.rightChild, key, value);
}else {
node.value = value;
}
//当前结点的高度需要更新
node.height = Math.max(getHight(node.leftChild), getHight(node.rightChild)) + 1;
//判断当前结点是否是平衡的 如果不平衡就要进行平衡调整
int balanceFactor = getBalanceFactor(node);
//balanceFactor > 1说明以当前节点为根的左子树不平衡
//getBalanceFactor(node.leftChild) >= 0说明是向当前节点的左孩子的左孩子添加了元素
//导致以当前节点为根的树不平衡 因此为左左不平衡
//通过对当前节点右旋转调整
if(balanceFactor > 1 && getBalanceFactor(node.leftChild) >= 0) {
return rightRotate(node);
}
//balanceFactor > 1说明以当前节点为根的左子树不平衡
//getBalanceFactor(node.leftChild) < 0说明是向当前节点的左孩子的右孩子添加了元素
//导致以当前节点为根的树不平衡 因此为左右不平衡
//通过先对当前节点的左孩子进行左旋转 再对当前节点右旋转调整
if(balanceFactor > 1 && getBalanceFactor(node.leftChild) < 0) {
node.leftChild = leftRotate(node.leftChild);
return rightRotate(node);
}
//balanceFactor < -1说明以当前节点为根的右子树不平衡
//getBalanceFactor(node.leftChild) >= 0说明是向当前节点的右孩子的左孩子添加了元素
//导致以当前节点为根的树不平衡 因此为右左不平衡
//通过先对当前节点的右孩子右旋转 再对当前节点左旋转调整
if(balanceFactor < -1 && getBalanceFactor(node.rightChild) >= 0) {
node.rightChild = rightRotate(node.rightChild);
return leftRotate(node);
}
//balanceFactor < -1说明以当前节点为根的右子树不平衡
//getBalanceFactor(node.leftChild) < 0说明是向当前节点的右孩子的右孩子添加了元素
//导致以当前节点为根的树不平衡 因此为右右不平衡
//通过对当前节点左旋转调整
if(balanceFactor < -1 && getBalanceFactor(node.rightChild) < 0) {
return leftRotate(node);
}
return node;
}
//删除AVL平衡树中指定key的键值对 向外提供的
@Override
public V remove(K key) {
Node ret = getNode(root, key);
if(ret != null) {
root = remove(root, key);
return ret.value;
}
return null;
}
//删除以node为根节点的AVL平衡树中指定key的键值对 并返回删除后新树的根
/*
* 删除节点与二分搜索树中的删除节点操作类似 但是不同点为:
* 删除节点后要向上一层返回删除后当前新树的根 但是在AVL平衡树中
* 不可以直接向上返回 因为删除节点后树结构就发生了改变 就需要进行平衡调整
* 所以删除节点后返回的新根 可以放在重新定义的节点中存着
* 对其进行高度的更新 以及平衡的判断 若不平衡 则进行平衡调整
* 平衡调整结束 再向上一层返回
*/
private Node remove(Node node, K key) {
if(node == null) {
return null;
}
Node retNode = null; //用于存放想上一层返回的节点
if(key.compareTo(node.key) < 0) {
node.leftChild = remove(node.leftChild, key);
retNode = node; //将要向上一层返回的节点存储起来
}else if(key.compareTo(node.key) > 0) {
node.rightChild = remove(node.rightChild, key);
retNode = node; //将要向上一层返回的节点存储起来
}else {
if(node.leftChild == null) {
Node rightNode = node.rightChild;
node.rightChild = null;
size--;
retNode = rightNode; //将要向上一层返回的节点存储起来
}else if(node.rightChild == null) {
Node leftNode = node.leftChild;
node.leftChild = null;
size--;
retNode = leftNode; //将要向上一层返回的节点存储起来
}else {
Node successor = getMinNum(node.rightChild);
successor.rightChild = remove(node.rightChild, successor.key);
successor.leftChild = node.leftChild;
node.leftChild = node.rightChild = null;
retNode = successor; //将要向上一层返回的节点存储起来
}
}
//判断向上一层返回的节点是否为空 若删除的节点为叶子节点 则有可能为空
if(retNode == null) {
return null;
}
//更新要向上一层返回的节点的高度
retNode.height = Math.max(getHight(retNode.leftChild), getBalanceFactor(retNode.rightChild)) + 1;
//获取要向上一层返回的节点平衡因子
int balanceFactor = getBalanceFactor(retNode);
//判断要向上一层返回的节点是否平衡 若不平衡 则进行平衡调整
/*
* 若要向上一层返回的节点的平衡因子大于1 则表示以该节点为根节点的左子树不平衡
* 并且该节点的左孩子的平衡因子大于等于0 则表示是左左不平衡
* 则将该节点右旋转进行平衡调整
*/
if(balanceFactor > 1 && getBalanceFactor(retNode.leftChild) >= 0) {
return rightRotate(retNode);
}
/*
* 若要向上一层返回的节点的平衡因子大于1 则表示以该节点为根节点的左子树不平衡
* 并且该节点的左孩子的平衡因子小于0 则表示是左右不平衡
* 则先将该节点的左孩子进行左旋转 再将该节点右旋转进行平衡调整
*/
if(balanceFactor > 1 && getBalanceFactor(retNode.leftChild) < 0) {
retNode.leftChild = leftRotate(retNode.leftChild);
return rightRotate(retNode);
}
/*
* 若要向上一层返回的节点的平衡因子小于-1 则表示以该节点为根节点的右子树不平衡
* 并且该节点的右孩子的平衡因子大于等于0 则表示是右左不平衡
* 则先将该节点的右孩子进行右旋转 再将该节点左旋转进行平衡调整
*/
if(balanceFactor < -1 && getBalanceFactor(retNode.leftChild) >= 0) {
retNode.rightChild = rightRotate(retNode.rightChild);
return leftRotate(retNode);
}
/*
* 若要向上一层返回的节点的平衡因子小于-1 则表示以该节点为根节点的右子树不平衡
* 并且该节点的右孩子的平衡因子小于0 则表示是右右不平衡
* 则将该节点左旋转进行平衡调整
*/
if(balanceFactor < -1 && getBalanceFactor(retNode.leftChild) < 0) {
return leftRotate(retNode);
}
return retNode;
}
//获取以node为根节点的AVL平衡树的最小值
private Node getMinNum(Node node) {
if(node.leftChild == null) {
return node;
}else {
return getMinNum(node.leftChild);
}
}
//判断AVL平衡树中是否包含指定key
@Override
public boolean contains(K key) {
return getNode(root, key) != null;
}
//通过key从AVL平衡树中获取key对应的值
@Override
public V get(K key) {
Node node = getNode(root, key);
if(node != null) {
return node.value;
}
return null;
}
//修改指定key对应的值
@Override
public void set(K key, V value) {
Node node = getNode(root, key);
if(node == null) {
throw new IllegalArgumentException("key-value is not exist");
}
node.value = value;
}
//获取AVL平衡树中有效元素的个数
@Override
public int size() {
return size;
}
//判断AVL平衡树是否为空
@Override
public boolean isEmpty() {
return size == 0 && root == null;
}
//对AVL平衡树进行前序遍历 向外提供的
public void preOrder() {
preOrder(root);
}
//对以node为根节点的AVL平衡树进行前序遍历
private void preOrder(Node node) {
if(node == null) {
return;
}
System.out.println(node.key);
preOrder(node.leftChild);
preOrder(node.rightChild);
}
//获取map中的键的集合 因为map中键key是唯一的 所以用集合set进行存储
@Override
public Set<K> keySet() {
TreeSet<K> set = new TreeSet<K>();
inOrderKeySet(root, set);
return set;
}
//中序遍历以node节点为根节点的二分搜索树中的键 并将结果存放在指定集合set中
private void inOrderKeySet(Node node, TreeSet<K> set) {
if(node == null) {
return;
}
inOrderKeySet(node.leftChild, set);
set.add(node.key);
inOrderKeySet(node.rightChild, set);
}
//获取map中的值的列表 因为map中键value不是唯一的 所以用列表list进行存储
@Override
public List<V> values() {
LinkedList<V> list = new LinkedList<V>();
inOrderValues(root, list);
return list;
}
//中序遍历以node节点为根节点的二分搜索树的值 并将结果存放在指定列表list中
private void inOrderValues(Node node, LinkedList<V> list) {
if(node == null) {
return;
}
inOrderValues(node.leftChild, list);
list.add(node.value);
inOrderValues(node.rightChild, list);
}
//获取map中键值对的集合 因为map中键值对(key:value)是唯一的 所以用集合set进行存储
@Override
public Set<Entry<K, V>> entrySet() {
TreeSet<Entry<K, V>> set = new TreeSet<Entry<K,V>>();
inOrderEntries(root, set);
return set;
}
//中序遍历以node节点为根节点的二分搜索树的键值对 并将结果存放在指定集合set中
private void inOrderEntries(Node node, TreeSet<Entry<K, V>> set) {
if(node == null) {
return;
}
inOrderEntries(node.leftChild, set);
set.add(new AVLEntry(node.key, node.value));
inOrderEntries(node.rightChild, set);
}
//定义一个获取键值对Entry<K, V>的实现类 来封装键值对
/*
* 用treeSet来存储键值对 treeSet底层又是通过二分搜索树实现的
* 因此treeSet存储的内容必须具有可比性 所以键值对的实现类中必须重写compareTo方法
* 键值对中的键又要具有可比性 因此键也要继承于Comparable
*/
private class AVLEntry implements Entry<K, V>{
public K key;
public V value;
public AVLEntry(K key, V value) {
this.key = key;
this.value = value;
}
@Override
public K getKey() { //获取键值对的键
return key;
}
@Override
public V getValue() { //获取键值对的值
return value;
}
@Override
public String toString() { //规定键值对的输出格式
return key + ":" + value;
}
@Override
public int compareTo(Entry<K, V> o) { //规定键的比较规则
return this.key.compareTo(o.getKey());
}
}
}
8. 代码的测试:
public class TestAVLTreeMap {
public static void main(String[] args) {
AVLTreeMap<Integer, String> map = new AVLTreeMap<Integer, String>();
map.put(6,"666");
map.put(4,"444");
map.put(3,"333");
map.put(7,"777");
map.put(5,"555");
map.put(9,"999");
map.put(8,"888");
map.put(1,"111");
map.put(2,"222");
System.out.println("AVL平衡树键的集合:" + map.keySet());
System.out.println("AVL平衡树值的集合:" + map.values());
System.out.println("AVL平衡树键值对的集合:" + map.entrySet());
System.out.println("====添加节点后的前序遍历===");
map.preOrder();
map.remove(6);
System.out.println("AVL平衡树键的集合:" + map.keySet());
System.out.println("AVL平衡树值的集合:" + map.values());
System.out.println("AVL平衡树键值对的集合:" + map.entrySet());
System.out.println("====删除节点后的前序遍历===");
map.preOrder();
//判断删除节点后 是否还是一棵AVL平衡树
System.out.println(map.isAVLTree());
//判断删除节点后 是否还是一棵二分搜索树
System.out.println(map.isBinarySearchTree());
}
}
9. 代码的运行结果: