前端算法入门之路(十五)(手撕AVL树)

175 阅读6分钟

平衡二叉排序树

二叉排序树

二叉排序树又叫二叉搜索树、二叉查找树
性质:1、左子树小于根节点 2、右子树大于根节点
用途:解决与排名相关的检索需求

二叉排序树删除操作

  • 叶子节点:可以直接删除
  • 出度为1:删除后提升该节点的唯一子树
  • 出度为2:找到删除节点的前驱或者后继,前驱或者后继节点度肯定为1或0,将前驱或后继节点赋值给删除节点,然后删除前驱或者后继节点就可以把问题转化为上面两个问题

平衡二叉排序树

为了防止二叉排序树退化成链表,查找效率直接变为O(n),所以要维持二叉排序树左右子树的平衡
性质:左右子树的高度差不大于1 失衡类型:LL型,左子树高度失衡,左子树的左子树高度大于左子树的右子树
RR型,右子树高度失衡,右子树的右子树高度大于右子树的左子树
LR型,左子树高度失衡,左子树的右子树高度大于左子树的左子树
RL型,右子树高度失衡,右子树的左子树高度大于右子树的右子树

平衡方案:LL型,大右旋
=》

LR型,先小左旋,把问题转化为LL型,再大右旋

image.png RL和RR型失衡对称调整即可

手撕平衡二叉排序树

// 设置虚拟节点,可以很方便的处理边界情况
let NIL = {left: null, right: null, h: 0}
class Node {
    constructor(val) {
        this.val = val
        this.left = this.right = NIL
        this.h = 1
    }
}
// 插入操作
function insert(root, val) {
    if (root == NIL) return new Node(val)
    if (val < root.val) {
        root.left = insert(root.left, val)
    } else {
        root.right = insert(root.right, val)
    }
    update_height(root)
    root = maintain(root)
    return root
}
// 删除操作
function delNode(root, val) {
    if (root == NIL) return root
    if (val < root.val) root.left = delNode(root.left, val)
    else if (val > root.val) root.right = delNode(root.right, val)
    else {
        if (root.left == NIL || root.right == NIL) {
            let temp = root.left == NIL ? root.right : root.left
            delete root
            return temp
        } else {
            let temp = getPreNode(root)
            root.val = temp.val
            root.left = delNode(root.left, temp.val)
        }
    }
    update_height(root)
    root = maintain(root)
    return root
}
// 获取前驱节点
function getPreNode(root) {
    let temp = root.left
    while (temp.right != NIL) temp = temp.right
    return temp
}
// 更新节点高度
function update_height(root) {
    if (root.left == NIL || root.right == NIL) {
        let temp = root.left == NIL ? root.right : root.left
        root.h = temp.h + 1
    } else {
        root.h = Math.max(root.left.h, root.right.h) + 1
    }
}
// 失衡调整
function maintain(root) {
    if (Math.abs(root.left.h - root.right.h) < 2) return root
    if (root.left.h > root.right.h) { // L型
        if (root.left.right.h > root.left.left.h) { // LR型
            root.left = left_rotate(root.left)
        }
        root = right_rotate(root) // LL型
    } else {   // R型
        if (root.right.left.h > root.right.right.h) { // RL型
            root.right = right_rotate(root.right)
        }
        root = left_rotate(root) // RR型
    }
    return root
}
// 左旋
function left_rotate(root) {
    let new_root = root.right
    root.right = new_root.left
    new_root.left = root
    update_height(root)
    update_height(new_root)
    return new_root
}
// 右旋
function right_rotate(root) {
    let new_root = root.left
    root.left = new_root.right
    new_root.right = root
    update_height(root)
    update_height(new_root)
    return new_root
}
// 中序遍历
function in_order(root, buff) {
    if (root == NIL) return
    in_order(root.left, buff)
    buff.push(root.val)
    in_order(root.right, buff)
}

LeetCode肝题

  1. 面试题 04.06. 后继者
// 定义一个pre节点保存上一个值,中序遍历的过程中,如果pre == p这个时候root就是要求的后继节点
var inorderSuccessor = function(root, p) {
    let pre = new TreeNode(null), ans = null
    let inorder = function(root) {
        if (!root) return root
        inorder(root.left)
        if (pre.val == p.val) {
            ans = root
            pre = root
            return
        }
        pre = root
        inorder(root.right)
    }
    inorder(root)
    return ans
};
    1. 删除二叉搜索树中的节点
// 分类型删除,先找到key节点,判断该节点有无子树,如果只有一个或没有子树,删除当前节点返回子树
// 如果有两个子树,那么需要找到节点的前驱或后继,把节点的值改为前驱的值,然后去左子树把前驱节点删除
var getPreNode = function(root) {
    let temp = root.left
    while(temp.right) temp = temp.right
    return temp
}
var deleteNode = function(root, key) {
    if (!root) return root
    if (key < root.val) root.left = deleteNode(root.left, key)
    else if (key > root.val) root.right = deleteNode(root.right, key)
    else {
        if (!root.left || !root.right) {
            let temp = root.left ? root.left : root.right
            delete root
            return temp
        } else {
            let temp = getPreNode(root)
            root.val = temp.val
            root.left = deleteNode(root.left, root.val)
        }
    }
    return root
};
    1. 将二叉搜索树变平衡
// 将二叉搜索树中序遍历为一个排序数组,再将数组尽量靠中间的节点作为根节点组装成一个数,就是平衡二叉排序树
var balanceBST = function(root) {
    let arr = []
    let inorder = (root) => {
        if(!root) return root
        inorder(root.left)
        arr.push(root)
        inorder(root.right)
    }
    let buildTree = (arr, i, j) => {
        if (i > j) return null
        let mid = (i + j) >> 1
        let root = arr[mid]
        root.left = buildTree(arr, i, mid - 1)
        root.right = buildTree(arr, mid + 1, j)
        return root
    }
    inorder(root)
    return buildTree(arr, 0, arr.length - 1)
};
    1. 将有序数组转换为二叉搜索树
// 同上,用中间值当做根节点
var sortedArrayToBST = function(nums) {
    let buildTree = (nums, i, j) => {
        if (i > j) return null
        let mid = (i + j) >> 1
        let root = new TreeNode(nums[mid])
        root.left = buildTree(nums, i, mid - 1)
        root.right = buildTree(nums, mid + 1, j)
        return root
    }
    return buildTree(nums, 0, nums.length - 1)
};
    1. 验证二叉搜索树
// 中序遍历看是否为升序
var isValidBST = function(root) {
    let pre, flag = true
    let inorder = (root) => {
        if (!root) return root
        inorder(root.left)
        if (pre && pre.val >= root.val) {
            flag = false
            return
        }
        pre = root
        inorder(root.right)
    }
    inorder(root)
    return flag
};
// 结构化思维
var isValidBST = function(root) {
    let pre
    let inorder = (root) => {
        if (!root) return true
        if(!inorder(root.left)) return false
        if (pre && pre.val >= root.val) return false
        pre = root
        if(!inorder(root.right)) return false
        return true
    }
    return inorder(root)
};
    1. 二叉搜索树中的众数
// 记录一下各个数字出现的次数,和最高次数相比,如果等于就把节点加入ans数组,大于就把ans数组清空,加入节点值,更新最高次数
var findMode = function(root) {
    let ans = [], cut = 0, vis = {}, dfs = (root) => {
        if (!root) return
        vis[root.val] = vis[root.val] ? vis[root.val] + 1 : 1
        if (vis[root.val] == cut) ans.push(root.val)
        if (vis[root.val] > cut) {
            ans.length = 0
            cut = vis[root.val]
            ans.push(root.val)
        }
        dfs(root.left)
        dfs(root.right)
    }
    dfs(root)
    return ans
};
  1. 面试题 17.12. BiNode
// 中序遍历从最左边的节点修改right指针,然后将left置为null
var convertBiNode = function(root) {
    if (!root) return root
    let pre, head, inorder = (root) => {
        if (!root) return root
        inorder(root.left)
        if (pre) {
            pre.right = root
        } else head = root
        root.left = null
        pre = root
        inorder(root.right)
    }
    inorder(root)
    return head
};
  1. 剑指 Offer 33. 二叉搜索树的后序遍历序列
// 后续遍历序列当做二叉搜索树,找到左子树和右子树的区间递归去遍历可以得到中序遍历的结果,r位置则是根节点位置
var verifyPostorder = function(postorder) {
    let pre = -1, inorder = (nums, l, r) => {
        if (l > r) return true
        let ind = l
        while(nums[ind] < nums[r]) ind++
        if (!inorder(nums, l, ind - 1)) return false
        if (pre != -1 && nums[r] < nums[pre]) return false
        pre = r
        if (!inorder(nums, ind, r - 1)) return false
        return true
    }
    return inorder(postorder, 0, postorder.length - 1)
};
    1. 前序遍历构造二叉搜索树
// 就是二叉搜索树构建的过程,较小的值放左子树,较大的值放右子树
let insert = (root, val) => {
    if (!root) return new TreeNode(val)
    if (val < root.val) root.left = insert(root.left, val)
    else root.right = insert(root.right, val)
    return root
}
var bstFromPreorder = function(preorder) {
    let root = null
    for(let i = 0; i < preorder.length; i++) {
        root = insert(root, preorder[i])
    }
    return root
};
    1. 完全二叉树的结点个数
// 赋予递归函数一个明确的意义:查询完全二叉树的结点个数
var countNodes = function(root) {
    if(!root) return 0
    // 树的节点个数 = 左子树的数量 + 右子树的数量 + 1
    return countNodes(root.left) + countNodes(root.right) + 1
};
  1. 剑指 Offer 54. 二叉搜索树的第 k 大结点
    二叉搜索树:右子树的值总是大于根节点,左子树的值总是小于根节
    所以二叉搜索树的中序遍历结果是一个有序数组
// 获取前序遍历结果
var inorder = function(root, ans) {
    if (!root) return
    root.left && inorder(root.left,ans)
    ans.push(root.val)
    root.right && inorder(root.right,ans)
}
var kthLargest = function(root, k) {
    let ans = []
    inorder(root, ans)
    return ans[ans.length -k]
};
  1. 剑指 Offer 26. 树的子结构
// 赋予递归函数一个明确的意义:A树B树是否完全相同
var is_match = function(A, B) {
    if (B == null) return true  // 如果B树为空则说明相同
    if (A == null) return false  // 如果A树为空则说明不相同
    if (A.val != B.val) return false  // 如果值不相等则说明不相同
    return is_match(A.left, B.left) && is_match(A.right, B.right)    // 比较左子树和右子树
}
// 赋予递归函数一个明确的意义:B树是否是A树的子树
var isSubStructure = function(A, B) {
    if (A == null) return false
    if (B == null) return false
    if (A.val == B.val && is_match(A, B)) return true    // 如果当前值相等且两树相同,返回true
    return isSubStructure(A.left, B) || isSubStructure(A.right, B)    // 否则递归比较A的左子树与B或者A的右子树与B
};
    1. 二叉树最大宽度
// 层序给树的每个节点一个编号, 根节点为i,左子树为2i,右子树为2i+1
var widthOfBinaryTree = function(root) {
    if (!root) return 0
    // 定义一个最大值和一个二维数组,数组存的是每一层的编号和节点
    let max = 1, que = [[0, root]]
    while(que.length) {
        let smaller = que[0][0], bigger = que[que.length - 1][0]
        max = Math.max(max, bigger - smaller + 1)
        let arr = []
        for (const [index, item] of que) {
            // 左子树的序号可以等价于(父节点的序号-父节点层最小序号)*2
            // 右子树的序号可以等价于(父节点的序号-父节点层最小序号)*2 + 1
            item.left && arr.push([2*(index - smaller), item.left])
            item.right && arr.push([2*(index - smaller)+1, item.right])
        }
        que = arr
    }
    return max
};