AVL树(高度平衡的二叉搜索树)

AVL树(高度平衡的二叉搜索树)

AVL树全称是平衡二叉搜索树,相比于红黑树,它是一种高度平衡的二叉搜索树,所有节点的左右子树高度差不超过1。

完整代码在最下面

树结点

/**
 * parent  父节点
 * left    左子树
 * right   右子树
 * height  高度,叶子结点高度为1
*/
class TreeNode {
    int key; // 结点值、键值
    TreeNode parent;
    TreeNode left;
    TreeNode right;
    int height;
    // int size; 树的大小,若有此属性,可以在o(logN)时间内找到排第k的元素
}

初始化

1.可以初始化一棵空树;2.也可以用一个已经排好序的数组来初始化一棵树(leetCode108题)

/**
 * nums  已排好序的数组
*/
public TreeNode bulidTree(int[] nums) {
    return recursive(nums, 0, nums.length);
}

// 取[left, right)区间的中点mid作为根结点
// [left,mid)构建左子树,[mid+1, right)构建右子树
private TreeNode recursive(int[] nums, int left, int right) {
    if (left == right) {
        return null;
    }
    int mid = (left + right) / 2;
    TreeNode root = new TreeNode(nums[mid]);
    root.left = recursive(nums, left, mid);
    root.right = recursive(nums, mid + 1, right);
    return root;
}

查找

一个具有 N N N 个节点的AVL树,其高度是 log ⁡ N + 1 \log N+1 logN+1,因此二分查找的复杂度是 o ( log ⁡ N ) o(\log N) o(logN) 。这里举5种查找的应用场景:

  1. 查找确定值
  2. 查找确定值的下确界、上确界
  3. 查找给定结点的前驱(precursor)、后继(successor)结点
  4. 查找新结点的插入位置
  5. 在root中查找排第k的元素(需要用到size属性,若结点的定义中无size属性,则复杂度就是 o ( N ) o(N) o(N)

查找确定值:给定一个待查找的值 key ,在根节点 root 中的二分查找的流程如下:

若根结点值偏大,则向左走,偏大则向右走,相等就返回;没有找到就返回null

public TreeNode getNode(int key) {
    TreeNode node = root;
    while (node != null) {
        int cmp = node.key - key;
        if (cmp > 0) {
            // node偏大,要缩小
            node = node.left;
        } else if (cmp < 0) {
            // node偏小,要变大
            node = node.right;
        } else {
            // 相等就是找到了
            return node;
        }
    }
    // 没有找到
    return null;
}

查找给定结点的前驱后继,实际上就是找出中序遍历的前驱后继。以查找 node 结点的后继为例,若 node.right 不为空,则后继结点为右子树的最左侧结点;若 node.right 为空,则后继结点在上面,向上找,第一个具有左子树的祖先就是后继结点,可以用下图的“对称性”来辅助记忆,会发现后继结点的两种情况是轴对称的。
AVL树(高度平衡的二叉搜索树)_第1张图片

// 前驱
private TreeNode precursor(TreeNode node) {
    if (node == null) {
        return null;
    } else if (node.left != null) {
        // 左子节点不为空,前驱在左子树的最右侧节点上
        TreeNode p = node.left;
        while (p.right != null) {
            p = p.right;
        }
        return p;
    } else {
        // 左子节点为空,前驱在上面
        TreeNode p = node.parent;
        TreeNode ch = node;
        while (p != null && p.left == ch) {
            ch = p;
            p = p.parent;
        }
        // p可能为null
        return p;
    }
}

// 后继
private TreeNode successor(TreeNode node) {
    if (node == null) {
        return null;
    } else if (node.right != null) {
        TreeNode p = node.right;
        while (p != null) {
            p = p.left;
        }
        return p;
    } else {
        // 后继在上面
        TreeNode p = node.parent;
        TreeNode ch = p;
        while (p != null && p.right == ch) {
            ch = p;
            p = p.parent;
        }
        return p;
    }
}

查找下确界、上确界:给定一个key值,查找它的系确界

public TreeNode floorEntry(int key) {
    TreeNode node = root;
    while (node != null) {
        int cmp = node.key - key;
        if (cmp < 0) {
            // node.key比key小,说明node就是一个下界
            if (node.right != null)
                node = node.right;
            else
                return node;
        } else if (cmp > 0) {
            if (node.left != null) {
                node = node.left;
            } else {
                // 向上找到,第一个右转的结点就是下确界
                TreeNode p = node.parent;
                TreeNode ch = node;
                while (p != null && p.left == ch) {
                    ch = p;
                    p = p.parent;
                }
                return p;
            }
        } else {
            return node;
        }
    }
    return null;
}

查找排第k的结点(k从0开始计数):查找排第k的结点,TreeNode的定义中需要多维护一个变量size,表示树的结点个数。

// 调用方法前,k保证满足:0 <= k < root.size
public TreeNode getKthEntry(int k) {
    TreeNode node = root;
    while (node != null) {
        int rank = getRank(node);
        if (rank > k) {
            node = node.left;
        } else if (rank < k) {
            node = node.right;
            k -= rank + 1;
        } else {
            return node;
        }
    }
    return null;
}
// node结点的排名
private int getRank(TreeNode node) {
    return node.left == null ? 0 : node.left.size;
}

插入

在树中插入一个新的结点,设结点值为key,有三个步骤

  1. 查找插入位置,即找到该新结点的父节点
  2. 判断该父节点:①若父节点的值等于key,则无需再插入,直接返回;②若父节点的值小于key,则插入到父节点的right;③若父节点的值大于key,则插入到父节点的left
  3. 由新插入的点开始,逐级向上更新其所有祖先节点的高度 height 。在更新过程中,若发现某个祖先节点的高度不平衡,则需要通过旋转来调整。
public void insert(int key) {
    // 1.查找插入位置
    TreeNode node = root;
    if (node == null) {
        // 空树
        root = new TreeNode(key);
        root.height = 1;
        // 更新树的属性,例如size,height等
        return;
    }

    // 查找插入的位置
    TreeNode parent;
    while (node != null) {
        parent = node;
        int cmp = node.key - key;
        if (cmp > 0) {
            node = node.left;
        } else if (cmp < 0) {
            node = node.right;
        } else {
            // 已有相同的节点,无需插入
            return;
        }
    }
    
    // 2.判断插入位置
    TreeNode e = new TreeNode(key);
    e.parent = parent;
    e.height = 1;
    if (parent.key > key) parent.left = e;
    else parent.right = e;
    
    // 3.更新祖先节点的高度,并修复平衡性
    fixStructure(parent);
    // 更新树的属性,例如树的size,height等
    size++;
}

删除

删除树中的一个节点,设要删除的是key,有三个步骤:

  1. 查找到待删除的节点,记为 node

  2. node 分情况讨论:

    node 是叶子结点,或 node 只有左子树,或 node 只有右子树

    node 同时有左、右子树,即 node.leftnode.right 都不为空。

    处理方法:对于①采用**“独子继承”**策略,用node的子树去取代 node ;对于②,由于 node.right不为空,那么在中序遍历中, node 的后继结点必然在 node.right最左侧的子树上,将这个后继结点 succesor 的值赋给node,然后删除这个后继结点 successor,由于这个 successor 没有左子树的,这时就变成了第①种情形。

  3. 更新祖先节点高度和修复平衡性:删除了node,高度和平衡性会受到影响的是 node 的祖先节点。
    AVL树(高度平衡的二叉搜索树)_第2张图片

public boolean remove(int key) {
    // 1.查找出要删除的结点
    TreeNode node = getNode(key);
    if (node == null) {
        // 没有找到,返回false,表示无需执行删除
        return false;
    }
    // 2.删除node结点
    if (node.left != null && node.right != null) {
        // node同时存在左右子树,则用后继结点代替
        TreeNode successor = successorOf(node);
        node.key = successor.key;
        node = successor;
    }
    // 独子继承:
    TreeNode replacement = node.left != null ? node.left : node.right;
    TreeNode parent = node.parent;
    if (replacement != null) {
        replacement.parent = parent;
        if (parent == null)
            // parent为空,说明node为root结点,继承root的位置
            root = replacement;
        else if (parent.left == node)
            parent.left = replacement;
        else
            parent.right = replacement;
    } else {
        // replacement为空,说明node是叶子结点
        if (parent == null)
            root = null; // 变成空树
        else if (parent.left == node)
            parent.left = null;
        else 
            parent.right = null;
    }
    // null out处理node
    node.left = node.right = node.parent = null;
    
    // 3.更新祖先节点的高度,并修复平衡性
    fixStructure(parent);
    // 更新树的属性,例如树的size,height等
    size--;
    return ture;
}

恢复平衡性:旋转

上述提到的插入、删除操作的最后一个步骤是更新高度并修复平衡性。先引入平衡因子的概念,并定义空结点 null 的平衡因子为0,定义非空结点的平衡因子为左子树高度减去右子树高度,如下式。
B F ( n o d e ) = h e i g h t ( n o d e . l e f t ) − h e i g h t ( n o d e . r i g h t ) BF(node) = height(node.left) - height(node.right) BF(node)=height(node.left)height(node.right)
所以,一棵平衡二叉查找树的平衡因子是(1,0,-1),其左、右子树的平衡因子也是(1,0,-1)。
AVL树(高度平衡的二叉搜索树)_第3张图片
往平衡因子为1或-1的子树中插入或删除一个结点后,有可能出现以下四种不同类型的不平衡情况,及其处理方法分别是:

  1. (2,1)不平衡——右旋
  2. (-2,-1)不平衡——左旋
  3. (2,-1)不平衡——先左旋变为(2,1)不平衡,再右旋
  4. (-2,1)不平衡——先右旋变为(-2,-1),再左旋
    AVL树(高度平衡的二叉搜索树)_第4张图片
    修复(2,1)不平衡的示意图如下:
    AVL树(高度平衡的二叉搜索树)_第5张图片
    修复不平衡会导致参与旋转的点的高度发生变化,旋转完成后要更新其高度。
    AVL树(高度平衡的二叉搜索树)_第6张图片
// 从node结点开始,逐级向下更新并修复所有祖先节点的平衡性
private void fixStructure(TreeNode node) {
    while (node != null) {
        // 判断node的不平衡的类型
        int bf = getBF(node);
        if (bf == 2) {
            int bfLeftSon = getBF(node.left);
            if (bfLeftSon < 0) {
                // (2,-1)不平衡要先左旋变成(2,1)不平衡
                rotateLeft(node.left);
            }
            // 右旋处理(2,1)不平衡
            rotateRight(node);
            // 更新高度:先更新node,再更新p
            calHelght(node.parent.left);
            calHelght(node.parent.right);
            calHelght(node.parent);
            node = node.parent.parent;
        } else if (bf == -2) {
            int bfRightSon = getBF(node.right);
            // (-2,1)不平衡要先右旋变成(-2,-1)不平衡
            if (bfRightSon > 0) {
                rotateRight(rightSon);
            }
            // 左旋处理(-2,-1)不平衡
            rotateLeft(node);
            // 更新高度:先更新node,再更新p
            calHelght(node.parent.left);
            calHelght(node.parent.right);
            calHelght(node.parent);
            node = node.parent.parent;
        } else {
            // node结点平衡
            calHeight(node);
            node = node.parent;
        }
    }
}

private int calHeight(TreeNode node) {
    if (node == null) {
        return 0;
    }
    return 1 + Math.max(heightOf(node.left), heightOf(node.right));
}

private int heightOf(TreeNode node) {
    return node == null ? 0 : node.height;
}

结点旋转操作

上述修复过程中,涉及到结点的两种旋转:右旋、左旋。

什么样的结点才能右旋/左旋呢?
一个结点,只要它的左子树不为null,那么它就能右旋;同理,一个结点,只要它的右子树不为null,那么它就能左旋。

对结点进行旋转操作能达到什么样的效果?
对一个结点进行右旋,会使得左子树的高度减一,右子树的高度加一,最终树的平衡因子减2;相反,对一个结点进行左旋,会使得左子树的高度加一,右子树的高度减一,最终树的平衡因子加2。
AVL树(高度平衡的二叉搜索树)_第7张图片
右旋 node 结点,共三个步骤:① node 结点的左子结点 p 继承 node 结点的位置,成为 node.parent 的子结点,②而 node 结点成为 p 的右子结点,③原来的 p.right 结点成为 node 结点的左子结点,用代码描述如下:

// 调用右旋方法前,保证node及node.left不为null
private void rotateRight(TreeNode node) {
    TreeNode p = node.left;
    TreeNode parent = node.parent;
    TreeNode lrSon = p.right;
    
    // 1.p继承node的位置
    p.parent = parent;
    if (parent == null)
        root = p;
    else if (parent.left == node)
        parent.left = p;
    else
        parent.right = p;
    // 2.node成为p的右子
    p.right = node;
    node.parent = p;
    // 3.lrSon成为node的左子
    node.left = lrSon;
    if (lrSon != null) 
        lrSon.parent = node;
}

// 调用左旋方法前,保证node及node.right不为null
private void rotateLeft(TreeNode node) {
    TreeNode p = node.right;
    TreeNode parent = node.parent;
    TreeNode rlSon = p.left;
    // 1.p继承node的位置
    p.parent = parent;
    if (parent == null)
        root = p;
    else if (parent.left == node)
        parent.left = p;
    else
        parent.right = p;
    // 2.node成为p的左子
    p.left = node;
    node.parent = p;
    // 3.rlSon成为node的右子
    node.right = rlSon;
    if (rlSon != null) 
        rlSon.parent = node;
}

附完整java代码

/**
 * AVL树
 *
 * @author wjw
 * @version 1.0
 * @date 2021/10/31 10:33
 */
public class AVLTree {

    private TreeNode root;

    private int size;

    public AVLTree() {

    }

    /**
     * 用已排好序的数组来初始化树
     *
     * @param sortedArr
     */
    public AVLTree(int[] sortedArr) {
        root = buildTree(sortedArr, 0, sortedArr.length);
        size = sortedArr.length;
    }

    /**
     * 使用sortedArr[left,right)区间递归构建AVL树
     *
     * @param sortedArr
     * @param left
     * @param right
     * @return
     */
    private TreeNode buildTree(int[] sortedArr, int left, int right) {
        if (left == right) {
            return null;
        }
        int mid = left + (right - left) / 2;
        TreeNode root = new TreeNode(sortedArr[mid]);
        root.left = buildTree(sortedArr, left, mid);
        root.right = buildTree(sortedArr, mid + 1, right);
        if (root.left != null) {
            root.left.parent = root;
        }
        if (root.right != null) {
            root.right.parent = root;
        }

        calHeight(root);
        return root;
    }

    /*
     * 查询部分的代码块:
     * 1.查找特定值
     * 2.查找特定结点的上、下确界
     * 3.前驱、后继
     *
     * */

    /**
     * 1.查找特定值
     *
     * @param key
     * @return
     */
    public boolean contains(int key) {
        return getEntry(key) != null;
    }

    /**
     * 2 a查找下确界
     *
     * @param key
     * @return
     */
    public Integer floorKey(int key) {
        TreeNode floor = floorEntry(key);
        return floor == null ? null : floor.key;
    }

    /**
     * 2 b查找上确界
     *
     * @param key
     * @return
     */
    public Integer ceilingKey(int key) {
        TreeNode ceil = ceilingEntry(key);
        return ceil == null ? null : ceil.key;
    }

    /**
     * 3 a前驱
     *
     * @param node
     * @return
     */
    private TreeNode precursor(TreeNode node) {
        if (node == null) {
            return null;
        }
        if (node.left != null) {
            // 左子结点不为空,前驱在左子树的最右侧
            TreeNode p = node.left;
            while (p.right != null) {
                p = p.right;
            }
            return p;
        } else {
            // 在上面,找到第一次右转的点
            TreeNode p = node.parent;
            TreeNode ch = node;
            while (p != null && p.left == ch) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }

    /**
     * 3 b后继
     *
     * @param node
     * @return
     */
    private TreeNode successor(TreeNode node) {
        if (node == null) {
            return null;
        }
        if (node.right != null) {
            TreeNode p = node.right;
            while (p.left != null) {
                p = p.left;
            }
            return p;
        } else {
            // 在上面找到第一次左转的点
            TreeNode p = node.parent;
            TreeNode ch = node;
            while (p != null && p.right == ch) {
                ch = p;
                p = p.parent;
            }
            return p;
        }
    }


    // 查找下确界结点
    private TreeNode floorEntry(int key) {
        TreeNode node = root;
        while (node != null) {
            int cmp = node.key - key;
            if (cmp < 0) {
                // node是一个下界:可以向右就向右,否则直接返回
                if (node.right != null) {
                    node = node.right;
                } else {
                    return node;
                }
            } else if (cmp > 0) {
                if (node.left != null) {
                    node = node.left;
                } else {
                    // 不能向左走了,就回头向上找到第一次右转的点并返回
                    TreeNode p = node.parent;
                    TreeNode ch = node;
                    while (p != null && p.left == ch) {
                        ch = p;
                        p = p.parent;
                    }
                    return p;
                }
            } else {
                return node;
            }
        }
        return null;
    }

    private TreeNode ceilingEntry(int key) {
        TreeNode node = root;
        while (node != null) {
            int cmp = node.key - key;
            if (cmp > 0) {
                // node就是一个上界
                if (node.left != null) {
                    node = node.left;
                } else {
                    return node;
                }
            } else if (cmp < 0) {
                if (node.right != null) {
                    node = node.right;
                } else {
                    // 向上找到第一次左转的结点
                    TreeNode p = node.parent;
                    TreeNode ch = node;
                    while (p != null && p.right == ch) {
                        ch = p;
                        p = p.parent;
                    }
                    return p;
                }
            } else {
                return node;
            }
        }
        return null;
    }

    // 二分查找特定结点
    private TreeNode getEntry(int key) {
        TreeNode node = root;
        while (node != null) {
            int cmp = node.key - key;
            if (cmp > 0) {
                node = node.left;
            } else if (cmp < 0) {
                node = node.right;
            } else {
                return node;
            }
        }
        return null;
    }


    /*
     * 插入部分的代码块:
     * 0.先判断root是否空树
     * 1.找到插入位置
     * 2.判断是否重复后再插入
     * 3.修复插入点所有祖先节点的平衡性及更新高度
     *
     * */

    /**
     * 插入新元素
     *
     * @param key
     */
    public void insert(int key) {
        // 判断是否为空树
        if (root == null) {
            root = new TreeNode(key);
            root.height = 1;
            size = 1;
            return;
        }
        // 找到插入位置
        TreeNode node = root;
        TreeNode parent = null;
        while (node != null) {
            parent = node;
            int cmp = node.key - key;
            if (cmp > 0) {
                node = node.left;
            } else if (cmp < 0) {
                node = node.right;
            } else {
                // 已存在key,无需插入
                return;
            }
        }
        // 插入
        TreeNode ch = new TreeNode(key);
        ch.height = 1;
        ch.parent = parent;
        if (key < parent.key) {
            parent.left = ch;
        } else {
            parent.right = ch;
        }

        // 修复祖先节点的平衡性并更新高度
        fixStructure(parent);
        // 维护全局变量
        size++;
    }


    /**
     * 删除元素
     *
     * @param key
     * @return
     */
    public boolean remove(int key) {
        // 1.查找是否有该元素
        TreeNode node = getEntry(key);
        if (node == null) {
            // 不存在,无需执行删除操作
            return false;
        }
        // 2.执行删除
        // node同时存在左右子,用后继代替node
        if (node.left != null && node.right != null) {
            TreeNode successor = successor(node);
            node.key = successor.key;
            node = successor;
        }
        // 独子继承:子结点取代node的位置
        TreeNode replacement = node.left != null ? node.left : node.right;
        TreeNode parent = node.parent;
        if (replacement != null) {
            replacement.parent = parent;
            if (parent == null) {
                // node原来是根结点
                root = replacement;
            } else if (parent.left == node) {
                parent.left = replacement;
            } else {
                parent.right = replacement;
            }
        } else {
            // replacement为空,说明node是叶子结点
            if (parent == null) {
                root = null; // 变为空树
            } else if (parent.left == node) {
                parent.left = null;
            } else {
                parent.right = null;
            }
        }
        // 删除:null out处理node结点
        node.left = node.right = node.parent = null;

        // 3.修复平衡性及更新高度
        fixStructure(parent);
        // 维护全局变量
        size--;
        return true;
    }


    /**
     * 修复node路径上所有祖先节点的平衡性并更新高度
     *
     * @param node
     */
    private void fixStructure(TreeNode node) {
        while (node != null) {
            // 判断不平衡类型
            int bf = getBF(node);
            if (bf == 2) {
                int leftSonBF = getBF(node.left);
                if (leftSonBF < 0) {
                    // (2,-1)不平衡要先左旋变成(2,1)不平衡
                    rotateLeft(node.left);
                }
                // 右旋处理(2,1)不平衡
                rotateRight(node);
                // node.parent取代了原来node的位置
                calHeight(node.parent.left);
                calHeight(node.parent.right);
                calHeight(node.parent);
                node = node.parent.parent;
            } else if (bf == -2) {
                int rightSonBF = getBF(node.right);
                if (rightSonBF > 0) {
                    // (-2,1)不平衡要先右旋变成(-2,-1)不平衡
                    rotateRight(node.right);
                }
                // 左旋处理(-2,-1)不平衡
                rotateLeft(node);
                // 更新高度
                calHeight(node.parent.left);
                calHeight(node.parent.right);
                calHeight(node.parent);
                node = node.parent.parent;
            } else {
                // 已平衡,只更新高度
                calHeight(node);
                node = node.parent;
            }
        }
    }


    // 左旋:保证node.right不为null
    private void rotateLeft(TreeNode node) {
        TreeNode parent = node.parent;
        TreeNode p = node.right;
        TreeNode rlSon = p.left;

        // 1.p取代node
        p.parent = parent;
        if (parent == null) {
            // 说明node原来为root结点
            root = p;
        } else if (parent.left == node) {
            parent.left = p;
        } else {
            parent.right = p;
        }
        // 2.node成为p的左子
        node.parent = p;
        p.left = node;
        // 3.rlSon成为node的右子
        node.right = rlSon;
        if (rlSon != null) {
            rlSon.parent = node;
        }
    }

    // 右旋:保证node.left不为null
    private void rotateRight(TreeNode node) {
        TreeNode parent = node.parent;
        TreeNode p = node.left;
        TreeNode lrSon = p.right;
        // 1.p取代node的位置
        p.parent = parent;
        if (parent == null) {
            root = p;
        } else if (parent.left == node) {
            parent.left = p;
        } else {
            parent.right = p;
        }

        // 2.node成为p的右子
        node.parent = p;
        p.right = node;
        // 3.lrSon成为node的左子
        node.left = lrSon;
        if (lrSon != null) {
            lrSon.parent = node;
        }
    }


    /**
     * 树的结点个数
     *
     * @return
     */
    public int size() {
        return size;
    }

    /**
     * 计算结点的平衡因子
     *
     * @param node
     * @return
     */
    private int getBF(TreeNode node) {
        return node == null ? 0 : heightOf(node.left) - heightOf(node.right);
    }


    /**
     * 计算结点高度
     *
     * @param node
     * @return
     */
    private void calHeight(TreeNode node) {
        if (node == null) {
            return;
        }
        node.height = 1 + Math.max(heightOf(node.left), heightOf(node.right));
    }

    private int heightOf(TreeNode node) {
        return node == null ? 0 : node.height;
    }

    /**
     * 结点
     */
    private class TreeNode {

        int key;
        TreeNode parent;
        TreeNode left;
        TreeNode right;
        int height; // 叶子结点的高度为1

        TreeNode() {

        }

        TreeNode(int key) {
            this.key = key;
        }
    }

}

你可能感兴趣的:(数据结构,数据结构,二叉树)