平衡二叉排序树
二叉排序树
二叉排序树又叫二叉搜索树、二叉查找树
性质:1、左子树小于根节点 2、右子树大于根节点
用途:解决与排名相关的检索需求
二叉排序树删除操作
- 叶子节点:可以直接删除
- 出度为1:删除后提升该节点的唯一子树
- 出度为2:找到删除节点的前驱或者后继,前驱或者后继节点度肯定为1或0,将前驱或后继节点赋值给删除节点,然后删除前驱或者后继节点就可以把问题转化为上面两个问题
平衡二叉排序树
为了防止二叉排序树退化成链表,查找效率直接变为O(n),所以要维持二叉排序树左右子树的平衡
性质:左右子树的高度差不大于1
失衡类型:LL型,左子树高度失衡,左子树的左子树高度大于左子树的右子树
RR型,右子树高度失衡,右子树的右子树高度大于右子树的左子树
LR型,左子树高度失衡,左子树的右子树高度大于左子树的左子树
RL型,右子树高度失衡,右子树的左子树高度大于右子树的右子树
平衡方案:LL型,大右旋
LR型,先小左旋,把问题转化为LL型,再大右旋
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肝题
- 面试题 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
};
-
- 删除二叉搜索树中的节点
// 分类型删除,先找到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
};
-
- 将二叉搜索树变平衡
// 将二叉搜索树中序遍历为一个排序数组,再将数组尽量靠中间的节点作为根节点组装成一个数,就是平衡二叉排序树
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)
};
-
- 将有序数组转换为二叉搜索树
// 同上,用中间值当做根节点
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)
};
-
- 验证二叉搜索树
// 中序遍历看是否为升序
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)
};
-
- 二叉搜索树中的众数
// 记录一下各个数字出现的次数,和最高次数相比,如果等于就把节点加入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
};
- 面试题 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
};
- 剑指 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)
};
-
- 前序遍历构造二叉搜索树
// 就是二叉搜索树构建的过程,较小的值放左子树,较大的值放右子树
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
};
-
- 完全二叉树的结点个数
// 赋予递归函数一个明确的意义:查询完全二叉树的结点个数
var countNodes = function(root) {
if(!root) return 0
// 树的节点个数 = 左子树的数量 + 右子树的数量 + 1
return countNodes(root.left) + countNodes(root.right) + 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]
};
- 剑指 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
};
-
- 二叉树最大宽度
// 层序给树的每个节点一个编号, 根节点为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
};