二叉搜索树 (BST),是一种很常用的二叉树。它的定义时:一个二叉树中,任意节点的值要
>=
左子树所有节点的值,而且<=
右子树的所有节点的值。
本文主要介绍BST的基本操作:判断合法性,增,删,改,查。
1.构建二叉搜索树
给你一个整数数组 nums
,其中元素已经按 升序
排列,请你将其转换为一棵高度平衡 二叉搜索树
。
高度平衡 二叉树是一棵满足「每个节点的左右两个子树的高度差的绝对值不超过 1 」的二叉树。
分析问题:
根据递归的思路,本质上就是寻找分割点,分割点作为当前节点,然后递归左区间和又区间。 根据题目是升序
,可以联想到中序遍历
。
给定二叉搜索树的中序遍历,是否可以唯一地确定二叉搜索树?答案是否定的。如果没有要求二叉搜索树的高度平衡,则任何一个数字都可以作为二叉搜索树的根节点,因此可能的二叉搜索树有多个。
如果增加一个限制条件,即要求二叉搜索树的高度平衡,是否可以唯一地确定二叉搜索树?答案仍然是否定的。
直观地看,我们可以选择中间数字作为二叉搜索树的根节点,这样分给左右子树的数字个数相同或只相差 1
,可以使得树保持平衡。 如果数组长度是奇数,则根节点的选择是唯一的,如果数组长度是偶数,则可以选择中间位置左边的数字作为根节点或者选择中间位置右边的数字作为根节点,选择不同的数字作为根节点则创建的平衡二叉搜索树也是不同的。
解法:中序遍历
选择任意一个中间位置数字作为根节点,则根节点的下标为mid = (left+right)/2
和mid = (left+right+1)/2
两者中随机选择一个,此处的除法为整数除法。
class Solution {
Random rand = new Random();
public TreeNode sortedArrayToBST(int[] nums) {
return bst(nums, 0, nums.length - 1);
}
// left:0 rigth:length-1 定义左右指针
public TreeNode bst(int[] nums, int left, int right) {
if (left > right) {
return null;
}
// 选择任意一个中间位置数字作为根节点
int mid = (left + right + rand.nextInt(2)) / 2; //0,1
TreeNode root = new TreeNode(nums[mid]);
root.left = bst(nums, left, mid - 1);
root.right = bst(nums, mid + 1, right);
return root;
}
}
2.判断BST的合法性
root
需要做的不只是和左右节点比较,而是要和整棵左子树和右子树所有节点比较。
对于这种情况可以使用辅助函数
,增加函数参数列表,在参数中携带额外信息:
boolean isValidBST(TreeNode root){
return isValidBST(root,null,null);
}
boolean isValidBst(TreeNode root,TreeNode min, TreeNode max){
if(root == null){return true;}
if(min != null && root.val <= min.val){return false;}
if(max != null && root.val >= max.val){return false;}
return isValidBst(root.left,min,root)&& isValidBST(root.right,root,max);
}
相当于给子树上的所有节点添加了一个min
和max
边界,约束root
的左子树节点值不超过root
的值,右子树节点值不小于root
的值,也就符合BST定义。
胡
3.在BST中插入一个数
TreeNode insertIntoBST(TreeNode root, int val){
//找到空位置,插入新节点
if(root == null){return new TreeNode(val);}
//如果已经存在,则不需要重复插入,直接返回即可
if(root.val == val){return root;}
//如果 val大,则应该插入到右子树上面
if(root.val < val){root.right = insertIntoBST(root.right,val); }
//如果val小,则应该插入到左子树上面
if(root.val >val){root.left = insertIntoBST(root.left,val);}
return root;
}
4.在BST中删除一个数
先写出基础的框架思路:
TreeNode deleteNode(TreeNode root, int key){
if(val.val == key){
//找到啦,删除操作;
}
else if(root.val >key){
//去左子树寻找
root.left = deleteNode(root.left,key);
}else if(root.val < key){
//去右子树寻找 key
root.right = deleteNode(root.right,key);
}
return root;
}
如何删除节点,是一个难点,因为删除节点的同时不能破坏BST的性质。
分为三种情况:
1.删除的节点恰好是末端节点,两个子节点都为空
if(root.left == null && root.right == null){
return null;
}
2. 删除的几点只有一个非空子节点,需要让这个孩子接替自己的位置
if(root.left == null){return root.right;}
if(root.right == null){return root.left;}
3.要删除的节点有两个子节点,为了不破坏BST的性质,必须找到左子树中最大的节点后者右子树中最小的节点来接替自己。
if(root.left != null && root.right != null){
//找到右子树中最小的节点
TreeNode minNode = getMin(roor.right);
//把root改为minNode
root.val = minNode.val;
//删除minNode
root.right = deleteNode(root.right,minNode.val);
}
最终代码:
TreeNode deleteNode(TreeNode root, int key){
if(root == null) {return null;}
if(root.val == key){
//处理第一种和第二种情况
if(root.left == null){return root.right;}
if(root.right == null){return root.left;}
//处理第三种情况
TreeNode minNode = getMin(root.right);
root.val = minNode.val;
root.right = deleteNode(root.right,minNode.val);
}else if(root.val > key){root.left = deleteNode(root.left,key);}
else if(root.val < key){root.right = deleteNode(root.right, key);}
return root;
}
TreeNode getMin(TreeNode node){
while(node.left != null){node = node.left;}
return node;
}
技巧总结:
- 二叉树算法设计的总路线:把当前节点的事做好,其他的递归。
- 如果当前节点会对下面的子节点产生影响,可以通过辅助函数增长参数列表。
- BST遍历框架:
void BST(TreeNode root, target){
if(root.val == target){//找到目标,相应的操作}
if(root.val > target){ BST(root.left,target);}
if(roor.val < target){ BST(root.right, target);}
}